From 8fe3eb64c70bf2aaa44e1f60ec6ab233d5ef4b8b Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 5 Feb 2026 15:04:10 +1300 Subject: [PATCH 1/2] Show options to bypass XML-RPC restrictions --- .../BlogDetailsTableViewModel.swift | 43 +++++- .../BlogDetailsViewController.swift | 10 ++ .../Blog Details/XMLRPCDisabledCell.swift | 38 +---- .../XMLRPCDisabledModalView.swift | 144 ++++++++++++++++++ .../Login/JetpackLoginViewController.swift | 19 ++- .../RESTAPIJetpackLoginViewController.swift | 2 +- 6 files changed, 216 insertions(+), 40 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledModalView.swift diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsTableViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsTableViewModel.swift index 004ce9e18aa2..9f824c9478da 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsTableViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsTableViewModel.swift @@ -5,6 +5,7 @@ import WordPressShared import WordPressSharedObjC import WordPressUI import Support +import SwiftUI private struct Section { let title: String? @@ -507,14 +508,50 @@ private extension BlogDetailsTableViewModel { func configureXMLRPCDisabledCell(tableView: UITableView) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell( withIdentifier: CellIdentifiers.xmlrpcDisabled - ) as? XMLRPCDisabledCell, - let viewController else { + ) as? XMLRPCDisabledCell else { return UITableViewCell() } - cell.configure(with: viewController) + cell.onTapped = { [weak self] in + self?.presentXMLRPCDisabledModal() + } return cell } + + private func presentXMLRPCDisabledModal() { + let content = XMLRPCDisabledModalView( + onConnectJetpack: { [weak self] in + self?.viewController?.dismiss(animated: true) { + self?.presentJetpackConnection() + } + }, + onGoToWPAdmin: { [weak viewController] in + viewController?.dismiss(animated: true) { + viewController?.showViewAdmin() + } + } + ) + + viewController?.present(UIHostingController(rootView: content), animated: true) + } + + private func presentJetpackConnection() { + let controller = UIViewController.jetpackConnection(blog: blog) + controller.promptType = .bypassXMLRPC + controller.completionBlock = { [weak controller, weak self] in + controller?.dismiss(animated: true) { + self?.viewController?.refresh() + } + } + controller.navigationItem.leftBarButtonItem = UIBarButtonItem( + systemItem: .close, + primaryAction: UIAction { [weak controller] _ in + controller?.dismiss(animated: true) + } + ) + let nav = UINavigationController(rootViewController: controller) + viewController?.present(nav, animated: true) + } } private extension BlogDetailsTableViewModel { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.swift index f2facff4a707..f39fe4b49567 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.swift @@ -153,6 +153,7 @@ public class BlogDetailsViewController: UIViewController { public func pulledToRefresh(with refreshControl: UIRefreshControl, onCompletion completion: (() -> Void)?) { let completionBlock = completion ?? {} + checkXMLRPCStatus() updateTableView { [weak refreshControl] in DispatchQueue.main.async { refreshControl?.endRefreshing() @@ -161,6 +162,15 @@ public class BlogDetailsViewController: UIViewController { } } + public func refresh() { + guard let refreshControl = tableView?.refreshControl else { + wpAssertionFailure("Can't get the UIRefreshControl instance") + return + } + refreshControl.beginRefreshing() + pulledToRefreshTriggered(refreshControl) + } + private func preloadBlogData() { // only preload on wifi guard ReachabilityUtils.internetReachability?.isReachableViaWiFi() == true else { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledCell.swift index 9d7ffc29cdfd..8a2496f96f75 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledCell.swift @@ -4,7 +4,7 @@ import SwiftUI import WordPressUI class XMLRPCDisabledCell: UITableViewCell { - private weak var presenterViewController: UIViewController? + var onTapped: (() -> Void)? private lazy var cardView: UIView = { let view = UIView() @@ -18,7 +18,7 @@ class XMLRPCDisabledCell: UITableViewCell { view.addSubview(content) view.pinSubviewToAllEdges(content) - view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(showAlert))) + view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(cardTapped))) return view }() @@ -36,26 +36,8 @@ class XMLRPCDisabledCell: UITableViewCell { contentView.pinSubviewToAllEdges(cardView) } - func configure(with viewController: BlogDetailsViewController) { - presenterViewController = viewController - } - - @objc private func showAlert() { - guard let presenter = presenterViewController else { - return - } - - let alert = AlertView { - AlertHeaderView(title: Strings.alertTitle, description: Strings.alertMessage) - } content: { - Image(systemName: "exclamationmark.triangle") - .font(.system(size: 50)) - .foregroundStyle(.orange) - } actions: { - AlertDismissButton() - } - - alert.present(in: presenter) + @objc private func cardTapped() { + onTapped?() } } @@ -96,16 +78,4 @@ private enum Strings { value: "Some features may be limited", comment: "Subtitle for the XML-RPC disabled card on blog details" ) - - static let alertTitle = NSLocalizedString( - "blogDetails.xmlrpcDisabled.alert.title", - value: "XML-RPC Disabled", - comment: "Alert title for XML-RPC disabled" - ) - - static let alertMessage = NSLocalizedString( - "blogDetails.xmlrpcDisabled.alert.message", - value: "XML-RPC is currently unavailable on your site. The app is transitioning to WordPress REST API, but some features still require XML-RPC. You may experience limited functionality until this transition is complete.", - comment: "Alert message explaining that XML-RPC is disabled on the site and some features may be limited" - ) } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledModalView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledModalView.swift new file mode 100644 index 000000000000..8618e7b916a3 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledModalView.swift @@ -0,0 +1,144 @@ +import SwiftUI +import WordPressUI + +struct XMLRPCDisabledModalView: View { + let onConnectJetpack: () -> Void + let onGoToWPAdmin: () -> Void + + private let learnMoreURL = "https://apps.wordpress.com/support/mobile/login-signup/inaccessible-xml-rpc-connection-error/" + + @Environment(\.dismiss) private var dismiss + @State private var isShowingLearnMore = false + + var body: some View { + NavigationStack { + VStack(spacing: 0) { + headerSection + buttonsSection + learnMoreButton + Spacer() + } + .padding(24) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button { + dismiss() + } label: { + Image(systemName: "xmark") + } + } + } + .sheet(isPresented: $isShowingLearnMore) { + SafariView(url: URL(string: learnMoreURL)!) + } + } + } + + private var headerSection: some View { + VStack(spacing: 16) { + Image(systemName: "exclamationmark.triangle") + .font(.system(size: 50)) + .foregroundStyle(.orange) + VStack(spacing: 6) { + Text(Strings.title) + .font(.title2.weight(.medium)) + Text(Strings.description) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + } + } + .padding(.bottom, 24) + } + + private var buttonsSection: some View { + VStack(spacing: 16) { + VStack(spacing: 4) { + Button { + onConnectJetpack() + } label: { + Text(Strings.connectJetpackTitle) + .font(.headline) + .frame(maxWidth: .infinity) + } + .buttonStyle(.borderedProminent) + .controlSize(.large) + + Text(Strings.connectJetpackDescription) + .font(.caption) + .foregroundStyle(.secondary) + } + + VStack(spacing: 4) { + Button { + onGoToWPAdmin() + } label: { + Text(Strings.goToWPAdminTitle) + .font(.headline) + .frame(maxWidth: .infinity) + } + .buttonStyle(.bordered) + .controlSize(.large) + + Text(Strings.goToWPAdminDescription) + .font(.caption) + .foregroundStyle(.secondary) + } + } + .padding(.horizontal, 24) + .padding(.bottom, 16) + } + + private var learnMoreButton: some View { + Button { + isShowingLearnMore = true + } label: { + Text(Strings.learnMore) + .font(.subheadline) + } + } +} + +private enum Strings { + static let title = NSLocalizedString( + "blogDetails.xmlrpcDisabled.modal.title", + value: "XML-RPC Disabled", + comment: "Title for the XML-RPC disabled modal" + ) + static let description = NSLocalizedString( + "blogDetails.xmlrpcDisabled.modal.description", + value: "XML-RPC is disabled on your site. Some features in the app currently require XML-RPC. Connect Jetpack or enable XML-RPC to access all features.", + comment: "Description explaining options to restore functionality when XML-RPC is disabled" + ) + static let connectJetpackTitle = NSLocalizedString( + "blogDetails.xmlrpcDisabled.modal.connectJetpack.title", + value: "Connect Jetpack", + comment: "Title for the Connect Jetpack option in XML-RPC disabled modal" + ) + static let connectJetpackDescription = NSLocalizedString( + "blogDetails.xmlrpcDisabled.modal.connectJetpack.description", + value: "Unlock all features with a secure connection", + comment: "Description for the Connect Jetpack option in XML-RPC disabled modal" + ) + static let goToWPAdminTitle = NSLocalizedString( + "blogDetails.xmlrpcDisabled.modal.wpAdmin.title", + value: "Enable in WP Admin", + comment: "Title for the WP Admin option in XML-RPC disabled modal" + ) + static let goToWPAdminDescription = NSLocalizedString( + "blogDetails.xmlrpcDisabled.modal.wpAdmin.description", + value: "Enable XML-RPC in your site settings", + comment: "Description for the wp-admin option in XML-RPC disabled modal" + ) + static let learnMore = NSLocalizedString( + "blogDetails.xmlrpcDisabled.modal.learnMore", + value: "Learn more", + comment: "Link text to learn more about XML-RPC being disabled" + ) +} + +#Preview { + XMLRPCDisabledModalView( + onConnectJetpack: { print("Connect Jetpack tapped") }, + onGoToWPAdmin: { print("Go to WP Admin tapped") } + ) +} diff --git a/WordPress/Classes/ViewRelated/Jetpack/Login/JetpackLoginViewController.swift b/WordPress/Classes/ViewRelated/Jetpack/Login/JetpackLoginViewController.swift index 31255ab7e1da..89cba010fd9a 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Login/JetpackLoginViewController.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Login/JetpackLoginViewController.swift @@ -201,6 +201,8 @@ public class JetpackLoginViewController: UIViewController { properties["source"] = "stats" case .notifications: properties["source"] = "notifications" + case .bypassXMLRPC: + properties["source"] = "bypass_xmlrpc" } if let blog { @@ -292,10 +294,11 @@ extension JetpackLoginViewController: JetpackRemoteInstallDelegate { public enum JetpackLoginPromptType { case stats case notifications + case bypassXMLRPC var image: UIImage? { switch self { - case .stats: + case .stats, .bypassXMLRPC: return UIImage(named: "wp-illustration-stats") case .notifications: return UIImage(named: "wp-illustration-notifications") @@ -304,7 +307,7 @@ public enum JetpackLoginPromptType { var imageName: String { switch self { - case .stats: + case .stats, .bypassXMLRPC: return "wp-illustration-stats" case .notifications: return "wp-illustration-notifications" @@ -319,6 +322,12 @@ public enum JetpackLoginPromptType { case .notifications: return NSLocalizedString("To get helpful notifications on your phone from your WordPress site, you'll need to install the Jetpack plugin.", comment: "Message asking the user if they want to set up Jetpack from notifications") + case .bypassXMLRPC: + return NSLocalizedString( + "jetpack.install.allFeatures.description", + value: "To unlock all app features, you'll need to install the Jetpack plugin.", + comment: "Message asking the user to install Jetpack to access all app features" + ) } } @@ -332,6 +341,12 @@ public enum JetpackLoginPromptType { return NSLocalizedString("jetpack.install.connectUser.notifications.description", value: "To get helpful notifications on your phone from your WordPress site, you'll need to connect to your user account.", comment: "Message asking the user if they want to set up Jetpack from notifications") + case .bypassXMLRPC: + return NSLocalizedString( + "jetpack.install.connectUser.bypassXMLRPC.description", + value: "To unlock all app features, you'll need to connect the Jetpack plugin to your user account.", + comment: "Message asking the user to connect Jetpack to access all app features" + ) } } } diff --git a/WordPress/Classes/ViewRelated/Jetpack/Login/RESTAPIJetpackLoginViewController.swift b/WordPress/Classes/ViewRelated/Jetpack/Login/RESTAPIJetpackLoginViewController.swift index b5721832ff13..daf948451683 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Login/RESTAPIJetpackLoginViewController.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Login/RESTAPIJetpackLoginViewController.swift @@ -94,7 +94,7 @@ private struct JetpackConnectionView: View { viewModel.connect() } .buttonStyle(.borderedProminent) - .controlSize(.small) + .controlSize(.regular) .padding(.bottom, 12) } else if viewModel.isCompleted { CompletedAnimationView { From b40083e051f37762215f4dda201a4684a90884f4 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 10 Feb 2026 13:35:50 +1300 Subject: [PATCH 2/2] Use `AlertView` to replace the XMLRPC disabled modal --- .../BlogDetailsTableViewModel.swift | 64 ++++++-- .../XMLRPCDisabledModalView.swift | 144 ------------------ 2 files changed, 54 insertions(+), 154 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledModalView.swift diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsTableViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsTableViewModel.swift index 9f824c9478da..d52d0c271201 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsTableViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsTableViewModel.swift @@ -513,26 +513,47 @@ private extension BlogDetailsTableViewModel { } cell.onTapped = { [weak self] in - self?.presentXMLRPCDisabledModal() + self?.presentXMLRPCDisabledAlert() } return cell } - private func presentXMLRPCDisabledModal() { - let content = XMLRPCDisabledModalView( - onConnectJetpack: { [weak self] in - self?.viewController?.dismiss(animated: true) { + private func presentXMLRPCDisabledAlert() { + guard let viewController else { return } + + let alert = AlertView { + AlertHeaderView( + title: XMLRPCDisabledAlertStrings.title, + description: XMLRPCDisabledAlertStrings.description + ) + } content: { + Image(systemName: "exclamationmark.triangle") + .font(.system(size: 50)) + .foregroundStyle(.orange) + } actions: { + Button { [weak self, weak viewController] in + viewController?.dismiss(animated: true) { self?.presentJetpackConnection() } - }, - onGoToWPAdmin: { [weak viewController] in + } label: { + Text(XMLRPCDisabledAlertStrings.connectJetpack) + .font(.headline) + .frame(maxWidth: .infinity) + } + .buttonStyle(.borderedProminent) + .controlSize(.extraLarge) + + Button { [weak viewController] in + let url = URL(string: "https://apps.wordpress.com/support/mobile/login-signup/inaccessible-xml-rpc-connection-error/")! viewController?.dismiss(animated: true) { - viewController?.showViewAdmin() + UIApplication.shared.open(url) } + } label: { + Text(XMLRPCDisabledAlertStrings.learnMore) } - ) + } - viewController?.present(UIHostingController(rootView: content), animated: true) + alert.present(in: viewController) } private func presentJetpackConnection() { @@ -1552,3 +1573,26 @@ private enum CellIdentifiers { static let extensiveLogging = "BlogDetailsExtensiveLoggingCellIdentifier" static let xmlrpcDisabled = "BlogDetailsXMLRPCDisabledCellIdentifier" } + +private enum XMLRPCDisabledAlertStrings { + static let title = NSLocalizedString( + "blogDetails.xmlrpcDisabled.alert.title", + value: "XML-RPC Disabled", + comment: "Title for the XML-RPC disabled alert" + ) + static let description = NSLocalizedString( + "blogDetails.xmlrpcDisabled.alert.description", + value: "XML-RPC is disabled on your site. Some features in the app currently require XML-RPC. Connect Jetpack or enable XML-RPC to access all features.", + comment: "Description explaining options to restore functionality when XML-RPC is disabled" + ) + static let connectJetpack = NSLocalizedString( + "blogDetails.xmlrpcDisabled.alert.connectJetpack", + value: "Connect Jetpack", + comment: "Button title to connect Jetpack in XML-RPC disabled alert" + ) + static let learnMore = NSLocalizedString( + "blogDetails.xmlrpcDisabled.alert.learnMore", + value: "Learn more", + comment: "Button title to learn more about XML-RPC being disabled" + ) +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledModalView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledModalView.swift deleted file mode 100644 index 8618e7b916a3..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/XMLRPCDisabledModalView.swift +++ /dev/null @@ -1,144 +0,0 @@ -import SwiftUI -import WordPressUI - -struct XMLRPCDisabledModalView: View { - let onConnectJetpack: () -> Void - let onGoToWPAdmin: () -> Void - - private let learnMoreURL = "https://apps.wordpress.com/support/mobile/login-signup/inaccessible-xml-rpc-connection-error/" - - @Environment(\.dismiss) private var dismiss - @State private var isShowingLearnMore = false - - var body: some View { - NavigationStack { - VStack(spacing: 0) { - headerSection - buttonsSection - learnMoreButton - Spacer() - } - .padding(24) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button { - dismiss() - } label: { - Image(systemName: "xmark") - } - } - } - .sheet(isPresented: $isShowingLearnMore) { - SafariView(url: URL(string: learnMoreURL)!) - } - } - } - - private var headerSection: some View { - VStack(spacing: 16) { - Image(systemName: "exclamationmark.triangle") - .font(.system(size: 50)) - .foregroundStyle(.orange) - VStack(spacing: 6) { - Text(Strings.title) - .font(.title2.weight(.medium)) - Text(Strings.description) - .foregroundStyle(.secondary) - .multilineTextAlignment(.center) - } - } - .padding(.bottom, 24) - } - - private var buttonsSection: some View { - VStack(spacing: 16) { - VStack(spacing: 4) { - Button { - onConnectJetpack() - } label: { - Text(Strings.connectJetpackTitle) - .font(.headline) - .frame(maxWidth: .infinity) - } - .buttonStyle(.borderedProminent) - .controlSize(.large) - - Text(Strings.connectJetpackDescription) - .font(.caption) - .foregroundStyle(.secondary) - } - - VStack(spacing: 4) { - Button { - onGoToWPAdmin() - } label: { - Text(Strings.goToWPAdminTitle) - .font(.headline) - .frame(maxWidth: .infinity) - } - .buttonStyle(.bordered) - .controlSize(.large) - - Text(Strings.goToWPAdminDescription) - .font(.caption) - .foregroundStyle(.secondary) - } - } - .padding(.horizontal, 24) - .padding(.bottom, 16) - } - - private var learnMoreButton: some View { - Button { - isShowingLearnMore = true - } label: { - Text(Strings.learnMore) - .font(.subheadline) - } - } -} - -private enum Strings { - static let title = NSLocalizedString( - "blogDetails.xmlrpcDisabled.modal.title", - value: "XML-RPC Disabled", - comment: "Title for the XML-RPC disabled modal" - ) - static let description = NSLocalizedString( - "blogDetails.xmlrpcDisabled.modal.description", - value: "XML-RPC is disabled on your site. Some features in the app currently require XML-RPC. Connect Jetpack or enable XML-RPC to access all features.", - comment: "Description explaining options to restore functionality when XML-RPC is disabled" - ) - static let connectJetpackTitle = NSLocalizedString( - "blogDetails.xmlrpcDisabled.modal.connectJetpack.title", - value: "Connect Jetpack", - comment: "Title for the Connect Jetpack option in XML-RPC disabled modal" - ) - static let connectJetpackDescription = NSLocalizedString( - "blogDetails.xmlrpcDisabled.modal.connectJetpack.description", - value: "Unlock all features with a secure connection", - comment: "Description for the Connect Jetpack option in XML-RPC disabled modal" - ) - static let goToWPAdminTitle = NSLocalizedString( - "blogDetails.xmlrpcDisabled.modal.wpAdmin.title", - value: "Enable in WP Admin", - comment: "Title for the WP Admin option in XML-RPC disabled modal" - ) - static let goToWPAdminDescription = NSLocalizedString( - "blogDetails.xmlrpcDisabled.modal.wpAdmin.description", - value: "Enable XML-RPC in your site settings", - comment: "Description for the wp-admin option in XML-RPC disabled modal" - ) - static let learnMore = NSLocalizedString( - "blogDetails.xmlrpcDisabled.modal.learnMore", - value: "Learn more", - comment: "Link text to learn more about XML-RPC being disabled" - ) -} - -#Preview { - XMLRPCDisabledModalView( - onConnectJetpack: { print("Connect Jetpack tapped") }, - onGoToWPAdmin: { print("Go to WP Admin tapped") } - ) -}