diff --git a/.editorconfig b/.editorconfig index db1bf43b9db9..7f956a19cc9d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,23 @@ +root = true + # Apply to all files [*] +indent_style = space +indent_size = 2 end_of_line = lf insert_final_newline = true +charset = utf-8 trim_trailing_whitespace = true +[*.md] +trim_trailing_whitespace = false + +[*.swift] +indent_size = 4 + +[{*.h,*.m}] +indent_size = 4 + # Ruby specific rules [{*.rb,Fastfile,Gemfile}] indent_style = space diff --git a/Modules/Package.swift b/Modules/Package.swift index 02e73fbad4e3..f1dc01182051 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -11,6 +11,7 @@ let package = Package( .library(name: "AsyncImageKit", targets: ["AsyncImageKit"]), .library(name: "DesignSystem", targets: ["DesignSystem"]), .library(name: "JetpackStatsWidgetsCore", targets: ["JetpackStatsWidgetsCore"]), + .library(name: "SFHFKeychainUtils", targets: ["SFHFKeychainUtils"]), .library(name: "WordPressData", targets: ["WordPressData"]), .library(name: "WordPressFlux", targets: ["WordPressFlux"]), .library(name: "WordPressShared", targets: ["WordPressShared"]), @@ -60,17 +61,34 @@ let package = Package( ]), .target(name: "DesignSystem", swiftSettings: [.swiftLanguageMode(.v5)]), .target(name: "JetpackStatsWidgetsCore", swiftSettings: [.swiftLanguageMode(.v5)]), + // SFHFKeychainUtils is an old Objective-C keychain wrapper. + // The implementatoin predates ARC, hence the dedicated target with ARC disabled, for the time being. + .target( + name: "SFHFKeychainUtils", + cSettings: [.unsafeFlags(["-fno-objc-arc"])] + ), .target(name: "UITestsFoundation", dependencies: [ .product(name: "ScreenObject", package: "ScreenObject"), .product(name: "XCUITestHelpers", package: "ScreenObject"), ], swiftSettings: [.swiftLanguageMode(.v5)]), - .target(name: "WordPressDataObjC"), + .target( + name: "WordPressDataObjC", + dependencies: [ + "SFHFKeychainUtils", + .target(name: "WordPressShared"), + .product(name: "CocoaLumberjack", package: "CocoaLumberjack"), + ] + ), .target( name: "WordPressData", dependencies: [ .target(name: "WordPressDataObjC"), - .target(name: "WordPressSharedObjC") - ] + .target(name: "WordPressSharedObjC"), + .product(name: "CocoaLumberjack", package: "CocoaLumberjack"), + .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack") + ], + // Avoid supporting Sendable and concurrency for the time being... + swiftSettings: [.swiftLanguageMode(.v5)] ), .target(name: "WordPressFlux", swiftSettings: [.swiftLanguageMode(.v5)]), .target(name: "WordPressCore", dependencies: [.target(name: "WordPressShared"), .product(name: "WordPressAPI", package: "wordpress-rs")]), @@ -90,6 +108,11 @@ let package = Package( ), .testTarget(name: "JetpackStatsWidgetsCoreTests", dependencies: [.target(name: "JetpackStatsWidgetsCore")], swiftSettings: [.swiftLanguageMode(.v5)]), .testTarget(name: "DesignSystemTests", dependencies: [.target(name: "DesignSystem")], swiftSettings: [.swiftLanguageMode(.v5)]), + .testTarget( + name: "WordPressDataTests", + dependencies: ["WordPressData"], + swiftSettings: [.swiftLanguageMode(.v5)] + ), .testTarget(name: "WordPressFluxTests", dependencies: ["WordPressFlux"], swiftSettings: [.swiftLanguageMode(.v5)]), .testTarget(name: "AsyncImageKitTests", dependencies: [ .target(name: "AsyncImageKit"), @@ -146,6 +169,7 @@ enum XcodeSupport { ] let shareAndDraftExtensionsDependencies: [Target.Dependency] = [ + "SFHFKeychainUtils", "WordPressShared", "WordPressUI", .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"), @@ -168,6 +192,7 @@ enum XcodeSupport { .xcodeTarget("XcodeTarget_App", dependencies: [ "DesignSystem", "JetpackStatsWidgetsCore", + "SFHFKeychainUtils", "WordPressData", "WordPressFlux", "WordPressShared", @@ -206,6 +231,7 @@ enum XcodeSupport { .product(name: "WordPressEditor", package: "AztecEditor-iOS"), ]), .xcodeTarget("XcodeTarget_WordPressTests", dependencies: testDependencies + [ + "SFHFKeychainUtils", "WordPressShared", .product(name: "Gravatar", package: "Gravatar-SDK-iOS"), .product(name: "Nimble", package: "Nimble"), @@ -216,6 +242,7 @@ enum XcodeSupport { .xcodeTarget("XcodeTarget_ShareExtension", dependencies: shareAndDraftExtensionsDependencies), .xcodeTarget("XcodeTarget_DraftActionExtension", dependencies: shareAndDraftExtensionsDependencies), .xcodeTarget("XcodeTarget_NotificationServiceExtension", dependencies: [ + "SFHFKeychainUtils", "WordPressShared", ]), .xcodeTarget("XcodeTarget_StatsWidget", dependencies: [ diff --git a/WordPress/Classes/Utility/SFHFKeychainUtils.m b/Modules/Sources/SFHFKeychainUtils/SFHFKeychainUtils.m similarity index 100% rename from WordPress/Classes/Utility/SFHFKeychainUtils.m rename to Modules/Sources/SFHFKeychainUtils/SFHFKeychainUtils.m diff --git a/WordPress/Classes/Utility/SFHFKeychainUtils.h b/Modules/Sources/SFHFKeychainUtils/include/SFHFKeychainUtils.h similarity index 98% rename from WordPress/Classes/Utility/SFHFKeychainUtils.h rename to Modules/Sources/SFHFKeychainUtils/include/SFHFKeychainUtils.h index 0bf448028a60..60b1c228af40 100755 --- a/WordPress/Classes/Utility/SFHFKeychainUtils.h +++ b/Modules/Sources/SFHFKeychainUtils/include/SFHFKeychainUtils.h @@ -27,7 +27,7 @@ // OTHER DEALINGS IN THE SOFTWARE. // -#import +#import @interface SFHFKeychainUtils : NSObject { @@ -68,4 +68,4 @@ + (NSArray *> *)getAllPasswordsForAccessGroup:(NSString *)accessGroup error:(NSError **)error; -@end \ No newline at end of file +@end diff --git a/WordPress/Classes/Utility/CoreData/ContextManager+ErrorHandling.swift b/Modules/Sources/WordPressData/ContextManager+ErrorHandling.swift similarity index 99% rename from WordPress/Classes/Utility/CoreData/ContextManager+ErrorHandling.swift rename to Modules/Sources/WordPressData/ContextManager+ErrorHandling.swift index 9d73ee139845..d5c330a35825 100644 --- a/WordPress/Classes/Utility/CoreData/ContextManager+ErrorHandling.swift +++ b/Modules/Sources/WordPressData/ContextManager+ErrorHandling.swift @@ -1,4 +1,5 @@ import CoreData +import CocoaLumberjackSwift // Imported from CoreData.CoreDataErrors private let coreDataKnownErrorCodes = [ diff --git a/WordPress/Classes/Utility/CoreData/ContextManager.swift b/Modules/Sources/WordPressData/ContextManager.swift similarity index 85% rename from WordPress/Classes/Utility/CoreData/ContextManager.swift rename to Modules/Sources/WordPressData/ContextManager.swift index 0f19780d53f9..66ebd0994c3a 100644 --- a/WordPress/Classes/Utility/CoreData/ContextManager.swift +++ b/Modules/Sources/WordPressData/ContextManager.swift @@ -1,3 +1,4 @@ +import CocoaLumberjackSwift import CoreData import Foundation import WordPressData @@ -57,13 +58,18 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift { super.init() mainContext.automaticallyMergesChangesFromParent = true - mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - NullBlogPropertySanitizer(context: mainContext).sanitize() + // FIXME: Temporary disabled to move on with migration. Gives error: + // Reference to var 'NSMergeByPropertyObjectTrumpMergePolicy' is not concurrency-safe because it involves shared mutable state + // mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + // FIXME: We still need to migrate this. Too many dependencies on the models + // NullBlogPropertySanitizer(context: mainContext).sanitize() } public func newDerivedContext() -> NSManagedObjectContext { let context = persistentContainer.newBackgroundContext() - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + // FIXME: Temporary disabled to move on with migration. Gives error: + // Reference to var 'NSMergeByPropertyObjectTrumpMergePolicy' is not concurrency-safe because it involves shared mutable state + // context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy return context } @@ -155,11 +161,7 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift { DDLogWarn("Migration required for persistent store.") - guard let modelFileURL = Bundle.main.url(forResource: "WordPress", withExtension: "momd") else { - fatalError("Can't find WordPress.momd") - } - - guard let versionInfo = NSDictionary(contentsOf: modelFileURL.appendingPathComponent("VersionInfo.plist")) else { + guard let versionInfo = NSDictionary(contentsOf: WordPressData.modelURL.appendingPathComponent("VersionInfo.plist")) else { fatalError("Can't get the object model's version info") } @@ -222,9 +224,7 @@ private extension ContextManager { private extension ContextManager { static func createPersistentContainer(storeURL: URL, modelName: String) -> NSPersistentContainer { - guard var modelFileURL = Bundle.main.url(forResource: "WordPress", withExtension: "momd") else { - fatalError("Can't find WordPress.momd") - } + var modelFileURL = WordPressData.modelURL if modelName != ContextManagerModelNameCurrent { modelFileURL = modelFileURL.appendingPathComponent(modelName).appendingPathExtension("mom") @@ -234,13 +234,15 @@ private extension ContextManager { fatalError("Can't create object model named \(modelName) at \(modelFileURL)") } - let startupEvent = SentryStartupEvent() + // FIXME: Discarding Sentry in the context of this migration 1/5 + // let startupEvent = SentryStartupEvent() do { try migrateDataModelsIfNecessary(storeURL: storeURL, objectModel: objectModel) } catch { DDLogError("Unable to migrate store: \(error)") - startupEvent.add(error: error as NSError) + // FIXME: Discarding Sentry in the context of this migration 2/5 + // startupEvent.add(error: error as NSError) } let storeDescription = NSPersistentStoreDescription(url: storeURL) @@ -254,16 +256,19 @@ private extension ContextManager { } DDLogError("Error opening the database. \(error)\nDeleting the file and trying again") - startupEvent.add(error: error) + // FIXME: Discarding Sentry in the context of this migration 3/5 + // startupEvent.add(error: error) // make a backup of the old database do { try CoreDataIterativeMigrator.backupDatabase(at: storeURL) } catch { - startupEvent.add(error: error) + // FIXME: Discarding Sentry in the context of this migration 4/5 + // startupEvent.add(error: error) } - startupEvent.send(title: "Can't initialize Core Data stack") + // FIXME: Discarding Sentry in the context of this migration 5/5 + // startupEvent.send(title: "Can't initialize Core Data stack") objc_exception_throw( NSException( name: NSExceptionName(rawValue: "Can't initialize Core Data stack"), @@ -279,10 +284,11 @@ private extension ContextManager { extension ContextManager { private static let internalSharedInstance = ContextManager() + /// Tests purpose only - static var overrideInstance: ContextManager? + nonisolated(unsafe) static var overrideInstance: ContextManager? - @objc class func sharedInstance() -> ContextManager { + @objc public class func sharedInstance() -> CoreDataStack { if let overrideInstance { return overrideInstance } @@ -290,8 +296,8 @@ extension ContextManager { return ContextManager.internalSharedInstance } - static var shared: ContextManager { - return sharedInstance() + static var shared: CoreDataStackSwift { + return sharedInstance() as! CoreDataStackSwift } } diff --git a/WordPress/Classes/Utility/CoreData/Migrator/CoreDataIterativeMigrator.swift b/Modules/Sources/WordPressData/CoreDataIterativeMigrator.swift similarity index 95% rename from WordPress/Classes/Utility/CoreData/Migrator/CoreDataIterativeMigrator.swift rename to Modules/Sources/WordPressData/CoreDataIterativeMigrator.swift index d22c071db2e9..e7990317a3ab 100644 --- a/WordPress/Classes/Utility/CoreData/Migrator/CoreDataIterativeMigrator.swift +++ b/Modules/Sources/WordPressData/CoreDataIterativeMigrator.swift @@ -1,5 +1,6 @@ -import Foundation import CoreData +import CocoaLumberjackSwift +import Foundation /// CoreDataIterativeMigrator: Migrates through a series of models to allow for users to skip app versions without risk. /// @@ -271,7 +272,7 @@ private extension CoreDataIterativeMigrator { static func models(for names: [String]) throws -> [NSManagedObjectModel] { let models = try names.map { (name) -> NSManagedObjectModel in - guard let url = urlForModel(name: name, in: nil), + guard let url = WordPressData.urlForModel(name: name, in: nil), let model = NSManagedObjectModel(contentsOf: url) else { let description = "No model found for \(name)" throw error(with: .noModelFound, description: description) @@ -282,25 +283,6 @@ private extension CoreDataIterativeMigrator { return models } - - static func urlForModel(name: String, in directory: String?) -> URL? { - let bundle = Bundle(for: ContextManager.self) - var url = bundle.url(forResource: name, withExtension: "mom", subdirectory: directory) - - if url != nil { - return url - } - - let momdPaths = bundle.paths(forResourcesOfType: "momd", inDirectory: directory) - momdPaths.forEach { (path) in - if url != nil { - return - } - url = bundle.url(forResource: name, withExtension: "mom", subdirectory: URL(fileURLWithPath: path).lastPathComponent) - } - - return url - } } enum IterativeMigratorErrorCodes: Int { diff --git a/WordPress/Classes/Utility/CoreData/CoreDataHelper.swift b/Modules/Sources/WordPressData/Extensions.swift similarity index 96% rename from WordPress/Classes/Utility/CoreData/CoreDataHelper.swift rename to Modules/Sources/WordPressData/Extensions.swift index 489faeb65f6b..ca46ce6e1c7f 100644 --- a/WordPress/Classes/Utility/CoreData/CoreDataHelper.swift +++ b/Modules/Sources/WordPressData/Extensions.swift @@ -1,5 +1,6 @@ import Foundation import CoreData +import CocoaLumberjackSwift import WordPressShared // MARK: - NSManagedObject Default entityName Helper @@ -11,7 +12,7 @@ extension NSManagedObject { /// /// Note: entity().name returns nil as per iOS 10, in Unit Testing Targets. Awesome. /// - @objc class func entityName() -> String { + @objc open class func entityName() -> String { return entity().name ?? classNameWithoutNamespaces() } @@ -88,7 +89,8 @@ extension NSManagedObjectContext { /// /// - Parameter predicate: Defines the conditions that any given object should meet. /// - func firstObject(ofType type: T.Type, matching predicate: NSPredicate) -> T? { + // FIXME: Might be able to return internal after Role is migrated + public func firstObject(ofType type: T.Type, matching predicate: NSPredicate) -> T? { let request = T.safeFetchRequest() request.predicate = predicate request.fetchLimit = 1 @@ -140,7 +142,8 @@ extension NSManagedObjectContext { objects = try fetch(request) as? [T] } catch { DDLogError("Error loading Objects [\(String(describing: T.entityName))") - wpAssertionFailure("CoreData.loadObjects failed", userInfo: ["error": "\(error)"]) + // FIXME: Enabling this will require adding dependencies such as Tracks through the chain... + // wpAssertionFailure("CoreData.loadObjects failed", userInfo: ["error": "\(error)"]) } return objects ?? [] @@ -191,7 +194,7 @@ extension ContextManager.ContextManagerError: LocalizedError, CustomDebugStringC } } -extension CoreDataStack { +public extension CoreDataStack { /// Perform a query using the `mainContext` and return the result. /// /// - Warning: Do not return `NSManagedObject` instances from the closure. @@ -357,8 +360,7 @@ extension CoreDataStack { } private func migrateDatabaseIfNecessary(at databaseLocation: URL) throws { - guard let modelFileURL = Bundle.main.url(forResource: "WordPress", withExtension: "momd"), - let objectModel = NSManagedObjectModel(contentsOf: modelFileURL) else { + guard let objectModel = NSManagedObjectModel(contentsOf: WordPressData.modelURL) else { return } try ContextManager.migrateDataModelsIfNecessary(storeURL: databaseLocation, objectModel: objectModel) diff --git a/Modules/Sources/WordPressData/Models/BloggingPrompt.swift b/Modules/Sources/WordPressData/Models/BloggingPrompt.swift new file mode 100644 index 000000000000..2efba01e130e --- /dev/null +++ b/Modules/Sources/WordPressData/Models/BloggingPrompt.swift @@ -0,0 +1,45 @@ +import CoreData +import WordPressSharedObjC + +public class BloggingPrompt: NSManagedObject { + + /// The unique ID for the prompt, received from the server. + @NSManaged public var promptID: Int32 + + /// The site ID for the prompt. + @NSManaged public var siteID: Int32 + + /// The prompt content to be displayed at entry points. + @NSManaged public var text: String + + /// The attribution source for the prompt. + @NSManaged public var attribution: String + + /// The prompt date. Time information should be ignored. + @NSManaged public var date: Date + + /// Whether the current user has answered the prompt in `siteID`. + @NSManaged public var answered: Bool + + /// The number of users that has answered the prompt. + @NSManaged public var answerCount: Int32 + + /// Contains avatar URLs of some users that have answered the prompt. + @NSManaged public var displayAvatarURLs: [URL] + + /// Contains additional tags that should be appended to the post for this prompt's answer. + @NSManaged public var additionalPostTags: [String]? + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: Self.classNameWithoutNamespaces()) + } + + @nonobjc public class func newObject(in context: NSManagedObjectContext) -> BloggingPrompt? { + return NSEntityDescription.insertNewObject(forEntityName: Self.classNameWithoutNamespaces(), into: context) as? BloggingPrompt + } + + public override func awakeFromInsert() { + self.date = .init(timeIntervalSince1970: 0) + self.displayAvatarURLs = [] + } +} diff --git a/WordPress/Classes/Utility/CoreData/NSManagedObject+Lookup.swift b/Modules/Sources/WordPressData/NSManagedObject+Lookup.swift similarity index 73% rename from WordPress/Classes/Utility/CoreData/NSManagedObject+Lookup.swift rename to Modules/Sources/WordPressData/NSManagedObject+Lookup.swift index d28c9610bca7..a55c7ccb26bb 100644 --- a/WordPress/Classes/Utility/CoreData/NSManagedObject+Lookup.swift +++ b/Modules/Sources/WordPressData/NSManagedObject+Lookup.swift @@ -1,6 +1,8 @@ import CoreData +import CocoaLumberjackSwift -extension NSManagedObject { +// TODO: How to we test this? +public extension NSManagedObject { /// Lookup an object by its NSManagedObjectID /// @@ -9,6 +11,7 @@ extension NSManagedObject { /// - context: An NSManagedObjectContext that contains the associated object. /// - Returns: The `NSManagedObject` subclass associated with the given `objectID`, if it exists. static func lookup(withObjectID objectID: NSManagedObjectID, in context: NSManagedObjectContext) -> Self? { + DDLogInfo("Dummy log to test CocoaLumberjack import") // FIXME: Remove once we have a legit usage return try? context.existingObject(with: objectID) as? Self } } diff --git a/Modules/Sources/WordPressData/Post+CoreDataProperties.swift b/Modules/Sources/WordPressData/Post+CoreDataProperties.swift new file mode 100644 index 000000000000..99d6ffa0f8de --- /dev/null +++ b/Modules/Sources/WordPressData/Post+CoreDataProperties.swift @@ -0,0 +1,27 @@ +import Foundation +import CoreData + +extension Post { + + @NSManaged var commentCount: NSNumber? + @NSManaged var disabledPublicizeConnections: [NSNumber: [String: String]]? + @NSManaged var likeCount: NSNumber? + @NSManaged var postFormat: String? + @NSManaged var postType: String? + @NSManaged var publicID: String? + @NSManaged var publicizeMessage: String? + @NSManaged var publicizeMessageID: String? + @NSManaged var tags: String? + @NSManaged var categories: Set? + @NSManaged var isStickyPost: Bool + + // If the post is created as an answer to a Blogging Prompt, the promptID is stored here. + @NSManaged var bloggingPromptID: String? + + // These were added manually, since the code generator for Swift is not generating them. + // + @NSManaged func addCategoriesObject(_ value: PostCategory) + @NSManaged func removeCategoriesObject(_ value: PostCategory) + @NSManaged func addCategories(_ values: Set) + @NSManaged func removeCategories(_ values: Set) +} diff --git a/Modules/Sources/WordPressData/Post.swift b/Modules/Sources/WordPressData/Post.swift new file mode 100644 index 000000000000..c6b87c321a61 --- /dev/null +++ b/Modules/Sources/WordPressData/Post.swift @@ -0,0 +1,278 @@ +import Foundation +import CoreData +import WordPressData + +@objc(Post) +class Post: AbstractPost { + @objc static let typeDefaultIdentifier = "post" + + struct Constants { + static let publicizeIdKey = "id" + static let publicizeValueKey = "value" + static let publicizeKeyKey = "key" + static let publicizeDisabledValue = "1" + static let publicizeEnabledValue = "0" + } + + enum PublicizeMetadataSkipPrefix: String { + case keyring = "_wpas_skip_" + case connection = "_wpas_skip_publicize_" + + /// Determines the prefix type from the given key. + /// + /// - Parameter key: String. + /// - Returns: A `PublicizeMetadataSkipPrefix` value, or nil if nothing matched. + static func prefix(of key: String) -> PublicizeMetadataSkipPrefix? { + // try to match the `keyring` format first, since it's a substring of the `connection` format. + guard key.hasPrefix(Self.keyring.rawValue) else { + return nil + } + return key.hasPrefix(Self.connection.rawValue) ? .connection : .keyring + } + } + + // MARK: - NSManagedObject + + override class func entityName() -> String { + return "Post" + } + + // MARK: - Format + + @objc func postFormatText() -> String? { + return blog.postFormatText(fromSlug: postFormat) + } + + @objc func setPostFormatText(_ postFormatText: String) { + + assert(blog.postFormats is [String: String]) + guard let postFormats = blog.postFormats as? [String: String] else { + DDLogError("Expected blog.postFormats to be \(String(describing: [String: String].self)).") + return + } + + var formatKey: String? + + for (key, value) in postFormats { + if value == postFormatText { + formatKey = key + break + } + } + + postFormat = formatKey + } + + // MARK: - Categories + + /// Returns categories as a comma-separated list + /// + @objc func categoriesText() -> String { + + guard let allStrings = categories?.map({ return $0.categoryName as String }) else { + return "" + } + + let orderedStrings = allStrings.sorted { (categoryName1, categoryName2) -> Bool in + return categoryName1.localizedCaseInsensitiveCompare(categoryName2) == .orderedAscending + } + + return orderedStrings.joined(separator: ", ") + } + + /// Set the categories for a post + /// + /// - Parameter categoryNames: a `NSArray` with the names of the categories for this post. If + /// a given category name doesn't exist it's ignored. + /// + @objc func setCategoriesFromNames(_ categoryNames: [String]) { + + var newCategories = Set() + + for categoryName in categoryNames { + + assert(blog.categories is Set) + guard let blogCategories = blog.categories as? Set else { + DDLogError("Expected blog.categories to be \(String(describing: Set.self)).") + return + } + + let matchingCategories = blogCategories.filter({ return $0.categoryName == categoryName }) + + if matchingCategories.count > 0 { + newCategories = newCategories.union(matchingCategories) + } + } + + categories = newCategories + } + + // MARK: - Sharing + + @objc func canEditPublicizeSettings() -> Bool { + return !self.hasRemote() || self.status != .publish + } + + // MARK: - PublicizeConnections + + @objc func publicizeConnectionDisabledForKeyringID(_ keyringID: NSNumber) -> Bool { + let isKeyringEntryDisabled = disabledPublicizeConnections?[keyringID]?[Constants.publicizeValueKey] == Constants.publicizeDisabledValue + + // try to check in case there's an entry for the PublicizeConnection that's keyed by the connectionID. + guard let connections = blog.connections as? Set, + let connection = connections.first(where: { $0.keyringConnectionID == keyringID }), + let existingValue = disabledPublicizeConnections?[connection.connectionID]?[Constants.publicizeValueKey] else { + // fall back to keyringID if there is no such entry with the connectionID. + return isKeyringEntryDisabled + } + + let isConnectionEntryDisabled = existingValue == Constants.publicizeDisabledValue + return isConnectionEntryDisabled || isKeyringEntryDisabled + } + + @objc func enablePublicizeConnectionWithKeyringID(_ keyringID: NSNumber) { + // if there's another entry keyed by connectionID references to the same connection, + // we need to make sure that the values are kept in sync. + if let connections = blog.connections as? Set, + let connection = connections.first(where: { $0.keyringConnectionID == keyringID }), + let _ = disabledPublicizeConnections?[connection.connectionID] { + enablePublicizeConnection(keyedBy: connection.connectionID) + } + + enablePublicizeConnection(keyedBy: keyringID) + } + + @objc func disablePublicizeConnectionWithKeyringID(_ keyringID: NSNumber) { + // if there's another entry keyed by connectionID references to the same connection, + // we need to make sure that the values are kept in sync. + if let connections = blog.connections as? Set, + let connectionID = connections.first(where: { $0.keyringConnectionID == keyringID })?.connectionID, + let _ = disabledPublicizeConnections?[connectionID] { + disablePublicizeConnection(keyedBy: connectionID) + + // additionally, if the keyring entry doesn't exist, there's no need create both formats. + // we can just update the dictionary's key from connectionID to keyringID instead. + if disabledPublicizeConnections?[keyringID] == nil, + let updatedEntry = disabledPublicizeConnections?[connectionID] { + disabledPublicizeConnections?.removeValue(forKey: connectionID) + disabledPublicizeConnections?[keyringID] = updatedEntry + return + } + } + + disablePublicizeConnection(keyedBy: keyringID) + } + + /// Marks the Publicize connection with the given id as enabled. + /// + /// - Parameter id: The dictionary key for `disabledPublicizeConnections`. + private func enablePublicizeConnection(keyedBy id: NSNumber) { + guard var connection = disabledPublicizeConnections?[id] else { + return + } + + // if the auto-sharing settings is not yet synced to remote, + // we can just remove the entry since all connections are enabled by default. + guard let _ = connection[Constants.publicizeIdKey] else { + _ = disabledPublicizeConnections?.removeValue(forKey: id) + return + } + + connection[Constants.publicizeValueKey] = Constants.publicizeEnabledValue + disabledPublicizeConnections?[id] = connection + } + + /// Marks the Publicize connection with the given id as disabled. + /// + /// - Parameter id: The dictionary key for `disabledPublicizeConnections`. + private func disablePublicizeConnection(keyedBy id: NSNumber) { + if let _ = disabledPublicizeConnections?[id] { + disabledPublicizeConnections?[id]?[Constants.publicizeValueKey] = Constants.publicizeDisabledValue + return + } + + if disabledPublicizeConnections == nil { + disabledPublicizeConnections = [NSNumber: [String: String]]() + } + + disabledPublicizeConnections?[id] = [Constants.publicizeValueKey: Constants.publicizeDisabledValue] + } + + // MARK: - Comments + + @objc func numberOfComments() -> Int { + return commentCount?.intValue ?? 0 + } + + // MARK: - Likes + + @objc func numberOfLikes() -> Int { + return likeCount?.intValue ?? 0 + } + + // MARK: - AbstractPost + + override func hasCategories() -> Bool { + categories?.isEmpty == false + } + + override func hasTags() -> Bool { + tags?.trim().isEmpty == false + } + + override func authorForDisplay() -> String? { + author ?? blog.account?.displayName + } + + // MARK: - BasePost + + override func contentPreviewForDisplay() -> String { + if let excerpt = mt_excerpt, excerpt.count > 0 { + if let preview = PostPreviewCache.shared.excerpt[excerpt] { + return preview + } + let preview = excerpt.makePlainText() + PostPreviewCache.shared.excerpt[excerpt] = preview + return preview + } else if let content { + if let preview = PostPreviewCache.shared.content[content] { + return preview + } + let preview = content.summarized() + PostPreviewCache.shared.content[content] = preview + return preview + } else { + return "" + } + } + + override func titleForDisplay() -> String { + var title = postTitle?.trimmingCharacters(in: CharacterSet.whitespaces) ?? "" + title = title + .stringByDecodingXMLCharacters() + .strippingHTML() + + if title.count == 0 && contentPreviewForDisplay().count == 0 && !hasRemote() { + title = NSLocalizedString("(no title)", comment: "Lets a user know that a local draft does not have a title.") + } + + return title + } +} + +private final class PostPreviewCache { + static let shared = PostPreviewCache() + + let excerpt = Cache() + let content = Cache() +} + +private final class Cache { + private let lock = NSLock() + private var dictionary: [Key: Value] = [:] + + subscript(key: Key) -> Value? { + get { lock.withLock { dictionary[key] } } + set { lock.withLock { dictionary[key] = newValue } } + } +} diff --git a/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/.xccurrentversion similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/.xccurrentversion diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 10.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 10.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 10.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 10.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 10.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 10.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 10.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 10.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 100.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 100.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 100.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 100.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 101.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 101.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 101.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 101.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 102.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 102.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 102.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 102.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 103.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 103.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 103.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 103.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 104.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 104.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 104.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 104.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 105.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 105.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 105.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 105.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 106.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 106.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 106.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 106.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 107.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 107.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 107.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 107.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 108.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 108.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 108.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 108.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 109.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 109.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 109.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 109.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 11.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 11.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 11.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 11.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 11.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 11.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 11.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 11.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 110.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 110.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 110.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 110.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 111.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 111.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 111.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 111.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 112.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 112.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 112.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 112.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 113.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 113.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 113.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 113.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 114.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 114.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 114.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 114.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 115.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 115.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 115.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 115.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 116.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 116.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 116.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 116.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 117.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 117.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 117.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 117.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 118.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 118.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 118.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 118.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 119.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 119.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 119.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 119.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 12.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 12.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 12.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 12.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 12.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 12.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 12.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 12.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 120.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 120.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 120.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 120.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 121.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 121.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 121.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 121.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 122.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 122.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 122.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 122.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 123.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 123.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 123.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 123.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 124.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 124.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 124.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 124.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 125.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 125.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 125.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 125.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 126.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 126.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 126.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 126.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 127.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 127.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 127.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 127.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 128.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 128.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 128.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 128.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 129.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 129.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 129.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 129.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 13.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 13.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 13.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 13.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 130.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 130.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 130.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 130.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 131.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 131.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 131.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 131.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 132.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 132.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 132.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 132.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 133.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 133.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 133.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 133.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 134.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 134.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 134.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 134.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 135.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 135.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 135.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 135.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 136.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 136.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 136.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 136.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 137.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 137.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 137.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 137.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 138.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 138.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 138.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 138.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 139.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 139.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 139.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 139.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 14.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 14.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 14.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 14.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 140.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 140.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 140.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 140.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 141.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 141.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 141.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 141.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 142.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 142.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 142.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 142.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 143.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 143.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 143.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 143.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 144.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 144.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 144.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 144.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 145.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 145.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 145.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 145.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 146.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 146.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 146.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 146.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 147.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 147.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 147.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 147.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 148.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 148.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 148.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 148.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 149.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 149.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 149.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 149.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 15.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 15.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 15.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 15.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 150.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 150.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 150.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 150.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 152.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 152.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 152.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 152.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 153.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 153.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 153.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 153.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 154.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 154.xcdatamodel/contents similarity index 99% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 154.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 154.xcdatamodel/contents index 2267cb2313e1..14acbaeae04b 100644 --- a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 154.xcdatamodel/contents +++ b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 154.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -215,7 +215,7 @@ - + diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 16.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 16.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 16.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 16.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 17.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 17.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 17.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 17.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 18.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 18.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 18.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 18.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 19.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 19.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 19.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 19.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 2.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 2.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 2.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 2.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 2.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 2.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 2.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 2.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 20.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 20.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 20.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 20.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 21.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 21.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 21.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 21.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 22.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 22.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 22.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 22.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 23.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 23.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 23.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 23.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 24.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 24.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 24.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 24.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 25.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 25.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 25.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 25.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 26.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 26.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 26.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 26.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 27.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 27.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 27.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 27.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 28.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 28.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 28.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 28.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 29.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 29.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 29.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 29.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 3.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 3.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 3.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 3.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 3.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 3.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 3.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 3.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 30.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 30.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 30.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 30.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 31.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 31.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 31.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 31.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 32.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 32.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 32.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 32.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 33.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 33.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 33.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 33.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 34.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 34.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 34.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 34.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 35.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 35.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 35.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 35.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 36.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 36.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 36.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 36.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 37.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 37.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 37.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 37.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 38.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 38.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 38.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 38.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 39.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 39.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 39.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 39.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 4.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 4.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 4.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 4.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 4.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 4.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 4.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 4.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 40.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 40.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 40.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 40.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 41.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 41.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 41.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 41.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 42.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 42.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 42.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 42.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 43.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 43.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 43.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 43.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 44.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 44.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 44.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 44.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 45.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 45.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 45.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 45.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 46.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 46.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 46.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 46.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 47.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 47.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 47.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 47.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 48.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 48.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 48.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 48.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 49.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 49.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 49.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 49.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 5.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 5.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 5.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 5.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 5.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 5.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 5.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 5.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 50.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 50.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 50.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 50.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 51.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 51.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 51.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 51.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 52.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 52.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 52.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 52.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 53.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 53.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 53.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 53.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 54.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 54.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 54.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 54.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 55.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 55.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 55.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 55.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 56.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 56.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 56.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 56.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 57.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 57.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 57.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 57.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 58.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 58.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 58.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 58.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 59.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 59.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 59.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 59.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 6.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 6.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 6.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 6.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 6.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 6.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 6.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 6.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 60.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 60.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 60.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 60.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 61.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 61.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 61.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 61.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 62.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 62.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 62.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 62.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 63.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 63.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 63.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 63.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 64.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 64.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 64.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 64.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 65.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 65.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 65.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 65.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 66.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 66.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 66.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 66.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 67.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 67.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 67.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 67.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 68.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 68.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 68.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 68.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 69.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 69.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 69.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 69.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 7.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 7.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 7.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 7.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 7.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 7.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 7.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 7.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 70.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 70.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 70.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 70.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 71.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 71.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 71.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 71.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 72.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 72.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 72.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 72.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 73.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 73.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 73.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 73.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 74.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 74.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 74.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 74.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 75.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 75.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 75.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 75.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 76.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 76.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 76.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 76.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 77.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 77.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 77.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 77.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 78.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 78.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 78.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 78.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 79.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 79.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 79.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 79.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 8.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 8.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 8.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 8.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 8.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 8.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 8.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 8.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 80.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 80.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 80.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 80.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 81.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 81.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 81.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 81.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 82.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 82.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 82.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 82.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 83.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 83.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 83.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 83.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 84.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 84.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 84.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 84.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 85.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 85.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 85.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 85.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 86.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 86.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 86.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 86.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 87.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 87.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 87.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 87.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 88.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 88.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 88.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 88.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 89.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 89.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 89.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 89.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 9.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 9.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 9.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 9.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 9.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 9.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 9.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 9.xcdatamodel/layout diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 90.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 90.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 90.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 90.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 91.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 91.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 91.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 91.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 92.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 92.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 92.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 92.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 93.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 93.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 93.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 93.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 94.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 95.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 95.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 95.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 95.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 96.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 96.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 96.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 96.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 97.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 97.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 97.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 97.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 98.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 98.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 98.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 98.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 99.xcdatamodel/contents b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 99.xcdatamodel/contents similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress 99.xcdatamodel/contents rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress 99.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress.xcdatamodel/elements b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress.xcdatamodel/elements similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress.xcdatamodel/elements rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress.xcdatamodel/elements diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress.xcdatamodel/layout b/Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress.xcdatamodel/layout similarity index 100% rename from WordPress/Classes/WordPress.xcdatamodeld/WordPress.xcdatamodel/layout rename to Modules/Sources/WordPressData/WordPress.xcdatamodeld/WordPress.xcdatamodel/layout diff --git a/Modules/Sources/WordPressData/WordPressData.swift b/Modules/Sources/WordPressData/WordPressData.swift index 2e8f63502b5a..0552f8fba127 100644 --- a/Modules/Sources/WordPressData/WordPressData.swift +++ b/Modules/Sources/WordPressData/WordPressData.swift @@ -1 +1,29 @@ @_exported import WordPressDataObjC + +public let modelURL: URL = { + guard let url = Bundle.module.url(forResource: "WordPress", withExtension: "momd") else { + fatalError("Cannot find model file.") + } + return url +}() + +public func urlForModel(name: String, in directory: String?) -> URL? { + let bundle = Bundle(for: TemporaryDummyClassToPickUpModule.self) + var url = bundle.url(forResource: name, withExtension: "mom", subdirectory: directory) + + if url != nil { + return url + } + + let momdPaths = bundle.paths(forResourcesOfType: "momd", inDirectory: directory) + momdPaths.forEach { (path) in + if url != nil { + return + } + url = bundle.url(forResource: name, withExtension: "mom", subdirectory: URL(fileURLWithPath: path).lastPathComponent) + } + + return url +} + +private class TemporaryDummyClassToPickUpModule {} diff --git a/WordPress/Classes/Models/AbstractPost.m b/Modules/Sources/WordPressDataObjC/Models/AbstractPost.m similarity index 99% rename from WordPress/Classes/Models/AbstractPost.m rename to Modules/Sources/WordPressDataObjC/Models/AbstractPost.m index 55d68a55515b..fd80a52d270b 100644 --- a/WordPress/Classes/Models/AbstractPost.m +++ b/Modules/Sources/WordPressDataObjC/Models/AbstractPost.m @@ -1,7 +1,7 @@ #import "AbstractPost.h" #import "Media.h" @import WordPressDataObjC; -#import "WordPress-Swift.h" +//#import "WordPress-Swift.h" #import "BasePost.h" @import WordPressKit; diff --git a/WordPress/Classes/Models/BasePost.m b/Modules/Sources/WordPressDataObjC/Models/BasePost.m similarity index 100% rename from WordPress/Classes/Models/BasePost.m rename to Modules/Sources/WordPressDataObjC/Models/BasePost.m diff --git a/WordPress/Classes/Models/Blog/Blog.m b/Modules/Sources/WordPressDataObjC/Models/Blog.m similarity index 95% rename from WordPress/Classes/Models/Blog/Blog.m rename to Modules/Sources/WordPressDataObjC/Models/Blog.m index c33b8986cd5d..a856ec7591da 100644 --- a/WordPress/Classes/Models/Blog/Blog.m +++ b/Modules/Sources/WordPressDataObjC/Models/Blog.m @@ -2,9 +2,11 @@ #import "WPAccount.h" #import "AccountService.h" @import WordPressDataObjC; -#import "Constants.h" -#import "WPUserAgent.h" -#import "WordPress-Swift.h" +// FIXME: These imports break the compilation and we've reached the point where they're so many it's time to address them, or at least try... +//#import "Constants.h" +//#import "WPUserAgent.h" +//#import "WordPress-Swift.h" +@import SFHFKeychainUtils; @import NSObject_SafeExpectations; @import NSURL_IDN; @@ -121,8 +123,9 @@ - (void)prepareForDeletion [self deleteApplicationToken]; } - [_xmlrpcApi invalidateAndCancelTasks]; - [_selfHostedSiteRestApi invalidateAndCancelTasks]; + // FIXME: +// [_xmlrpcApi invalidateAndCancelTasks]; +// [_selfHostedSiteRestApi invalidateAndCancelTasks]; // Remove the self-hosted site cookies from the shared cookie storage. if (self.account == nil && self.url != nil) { @@ -572,9 +575,9 @@ - (BOOL)supports:(BlogFeature)feature return [self supportsSharing]; case BlogFeatureOAuth2Login: return [self isHostedAtWPcom]; - case BlogFeatureMentions: + case BlogFeatureMentions: // TODO: is this used? return [self isAccessibleThroughWPCom]; - case BlogFeatureXposts: + case BlogFeatureXposts: // TODO: is this used? return [self isAccessibleThroughWPCom]; case BlogFeatureReblog: case BlogFeaturePlans: @@ -896,36 +899,39 @@ - (NSString *)stateDescription #pragma mark - api accessor -- (WordPressOrgXMLRPCApi *)xmlrpcApi -{ - NSURL *xmlRPCEndpoint = [NSURL URLWithString:self.xmlrpc]; - if (_xmlrpcApi == nil) { - if (xmlRPCEndpoint != nil) { - _xmlrpcApi = [[WordPressOrgXMLRPCApi alloc] initWithEndpoint:xmlRPCEndpoint - userAgent:[WPUserAgent wordPressUserAgent]]; - } - } - return _xmlrpcApi; -} - -- (WordPressOrgRestApi *)selfHostedSiteRestApi -{ - if (_selfHostedSiteRestApi == nil) { - _selfHostedSiteRestApi = self.account == nil ? [[WordPressOrgRestApi alloc] initWithBlog:self] : nil; - } - return _selfHostedSiteRestApi; -} - -- (WordPressComRestApi *)wordPressComRestApi -{ - if (self.account) { - return self.account.wordPressComRestApi; - } - return nil; -} +// FIXME: +//- (WordPressOrgXMLRPCApi *)xmlrpcApi +//{ +// NSURL *xmlRPCEndpoint = [NSURL URLWithString:self.xmlrpc]; +// if (_xmlrpcApi == nil) { +// if (xmlRPCEndpoint != nil) { +// _xmlrpcApi = [[WordPressOrgXMLRPCApi alloc] initWithEndpoint:xmlRPCEndpoint +// userAgent:[WPUserAgent wordPressUserAgent]]; +// } +// } +// return _xmlrpcApi; +//} +// +//- (WordPressOrgRestApi *)selfHostedSiteRestApi +//{ +// if (_selfHostedSiteRestApi == nil) { +// _selfHostedSiteRestApi = self.account == nil ? [[WordPressOrgRestApi alloc] initWithBlog:self] : nil; +// } +// return _selfHostedSiteRestApi; +//} +// +//- (WordPressComRestApi *)wordPressComRestApi +//{ +// if (self.account) { +// return self.account.wordPressComRestApi; +// } +// return nil; +//} - (BOOL)isAccessibleThroughWPCom { - return self.wordPressComRestApi != nil; + // FIXME: + // return self.wordPressComRestApi != nil; + return YES; } - (BOOL)supportsRestApi { diff --git a/WordPress/Classes/Models/Media.m b/Modules/Sources/WordPressDataObjC/Models/Media.m similarity index 93% rename from WordPress/Classes/Models/Media.m rename to Modules/Sources/WordPressDataObjC/Models/Media.m index 0a300b4fdcc6..f8a86b85c27b 100644 --- a/WordPress/Classes/Models/Media.m +++ b/Modules/Sources/WordPressDataObjC/Models/Media.m @@ -1,6 +1,7 @@ +@import CocoaLumberjack; #import "Media.h" @import WordPressDataObjC; -#import "WordPress-Swift.h" +//#import "WordPress-Swift.h" @implementation Media @@ -159,9 +160,9 @@ - (NSURL *)absoluteURLForLocalPath:(NSString *)localPath cacheDirectory:(BOOL)ca NSError *error; NSURL *mediaDirectory = nil; if (cacheDirectory) { - mediaDirectory = [[MediaFileManager cacheManager] directoryURLAndReturnError:&error]; +// mediaDirectory = [[MediaFileManager cacheManager] directoryURLAndReturnError:&error]; } else { - mediaDirectory = [MediaFileManager uploadsDirectoryURLAndReturnError:&error]; +// mediaDirectory = [MediaFileManager uploadsDirectoryURLAndReturnError:&error]; } if (error) { DDLogInfo(@"Error resolving Media directory: %@", error); @@ -193,13 +194,13 @@ - (void)remove { [self.managedObjectContext performBlockAndWait:^{ [self.managedObjectContext deleteObject:self]; - [[ContextManager sharedInstance] saveContextAndWait:self.managedObjectContext]; +// [[ContextManager sharedInstance] saveContextAndWait:self.managedObjectContext]; }]; } - (void)save { - [[ContextManager sharedInstance] saveContext:self.managedObjectContext]; +// [[ContextManager sharedInstance] saveContext:self.managedObjectContext]; } - (BOOL)hasRemote { diff --git a/WordPress/Classes/Models/PostCategory.m b/Modules/Sources/WordPressDataObjC/Models/PostCategory.m similarity index 100% rename from WordPress/Classes/Models/PostCategory.m rename to Modules/Sources/WordPressDataObjC/Models/PostCategory.m diff --git a/Modules/Sources/WordPressDataObjC/Models/WPAccount.m b/Modules/Sources/WordPressDataObjC/Models/WPAccount.m new file mode 100644 index 000000000000..fc5800f7ffc3 --- /dev/null +++ b/Modules/Sources/WordPressDataObjC/Models/WPAccount.m @@ -0,0 +1,196 @@ +#import "WPAccount.h" +@import CocoaLumberjack; +@import SFHFKeychainUtils; + +@interface WPAccount () + +//@property (nonatomic, strong, readwrite) WordPressComRestApi *wordPressComRestApi; +@property (nonatomic, strong, readwrite) NSString *cachedToken; + +@end + +@implementation WPAccount + +@dynamic username; +@dynamic blogs; +@dynamic defaultBlog; +@dynamic primaryBlogID; +@dynamic uuid; +@dynamic dateCreated; +@dynamic email; +@dynamic emailVerified; +@dynamic displayName; +@dynamic userID; +@dynamic avatarURL; +@dynamic settings; +@synthesize wordPressComRestApi = _wordPressComRestApi; +@synthesize cachedToken; + +#pragma mark - NSManagedObject subclass methods + +- (void)prepareForDeletion +{ + // Only do these deletions in the primary context (no parent) + if (self.managedObjectContext.concurrencyType != NSMainQueueConcurrencyType) { + return; + } + +// [_wordPressComRestApi invalidateAndCancelTasks]; +// _wordPressComRestApi = nil; + self.authToken = nil; +} + +- (void)didTurnIntoFault +{ + [super didTurnIntoFault]; +// _wordPressComRestApi = nil; + self.cachedToken = nil; +} + ++ (NSString *)entityName +{ + return @"Account"; +} + +#pragma mark - Custom accessors + +- (void)setUsername:(NSString *)username +{ + NSString *previousUsername = self.username; + + BOOL usernameChanged = ![previousUsername isEqualToString:username]; + NSString *authToken = nil; + + if (usernameChanged) { + authToken = self.authToken; + self.authToken = nil; + } + + [self willChangeValueForKey:@"username"]; + [self setPrimitiveValue:username forKey:@"username"]; + [self didChangeValueForKey:@"username"]; + + if (usernameChanged) { + self.authToken = authToken; + } +} + +- (NSString *)authToken +{ + if (self.cachedToken != nil) { + return self.cachedToken; + } + + NSString *token = [WPAccount tokenForUsername:self.username]; + self.cachedToken = token; + return token; +} + +- (void)setAuthToken:(NSString *)authToken +{ + self.cachedToken = nil; + + if (authToken) { + NSError *error = nil; +// [SFHFKeychainUtils storeUsername:self.username +// andPassword:authToken +// forServiceName:[WPAccount authKeychainServiceName] +// accessGroup:nil +// updateExisting:YES +// error:&error]; + + if (error) { + DDLogError(@"Error while updating WordPressComOAuthKeychainServiceName token: %@", error); + } + + } else { + NSError *error = nil; +// [SFHFKeychainUtils deleteItemForUsername:self.username +// andServiceName:[WPAccount authKeychainServiceName] +// accessGroup:nil +// error:&error]; + if (error) { + DDLogError(@"Error while deleting WordPressComOAuthKeychainServiceName token: %@", error); + } + } + + // Make sure to release any RestAPI alloc'ed, since it might have an invalid token + _wordPressComRestApi = nil; +} + +- (BOOL)hasAtomicSite { + for (Blog *blog in self.blogs) { + if ([blog isAtomic]) { + return YES; + } + } + return NO; +} + +#pragma mark - Static methods + ++ (NSString *)tokenForUsername:(NSString *)username +{ + NSError *error = nil; + [WPAccount migrateAuthKeyForUsername:username]; + NSString *authToken = [SFHFKeychainUtils getPasswordForUsername:username + andServiceName:[WPAccount authKeychainServiceName] + accessGroup:nil + error:&error]; + if (error) { + DDLogError(@"Error while retrieving WordPressComOAuthKeychainServiceName token: %@", error); + } + + return authToken; +} + ++ (void)migrateAuthKeyForUsername:(NSString *)username +{ + static dispatch_once_t onceToken; +// dispatch_once(&onceToken, ^{ +// if ([AppConfiguration isJetpack]) { +// SharedDataIssueSolver *sharedDataIssueSolver = [SharedDataIssueSolver instance]; +// [sharedDataIssueSolver migrateAuthKeyFor:username]; +// } + }); +} + +//+ (NSString *)authKeychainServiceName +//{ +// return [AppConstants authKeychainServiceName]; +//} + +#pragma mark - API Helpers + +//- (WordPressComRestApi *)wordPressComRestApi +//{ +// if (!_wordPressComRestApi) { +// if (self.authToken.length > 0) { +// __weak __typeof(self) weakSelf = self; +// _wordPressComRestApi = [WordPressComRestApi defaultApiWithOAuthToken:self.authToken +// userAgent:[WPUserAgent wordPressUserAgent] +// localeKey:[WordPressComRestApi LocaleKeyDefault]]; +// [_wordPressComRestApi setInvalidTokenHandler:^{ +// [weakSelf setAuthToken:nil]; +// [WordPressAuthenticationManager showSigninForWPComFixingAuthToken]; +// if (weakSelf.isDefaultWordPressComAccount) { +// // At the time of writing, there is an implicit assumption on what the object parameter value means. +// // For example, the WordPressAppDelegate.handleDefaultAccountChangedNotification(_:) subscriber inspects the object parameter to decide whether the notification was sent as a result of a login. +// // If the object is non-nil, then the method considers the source a login. +// // +// // The code path in which we are is that of an invalid token, and that's neither a login nor a logout, it's more appropriate to consider it a logout. +// // That's because if the token is invalid the app will soon received errors from the API and it's therefore better to force the user to login again. +// [[NSNotificationCenter defaultCenter] postNotificationName:WPAccountDefaultWordPressComAccountChangedNotification object:nil]; +// } +// }]; +// } else { +// dispatch_async(dispatch_get_main_queue(), ^{ +// [WordPressAuthenticationManager showSigninForWPComFixingAuthToken]; +// }); +// } +// } +// return _wordPressComRestApi; +// +//} + +@end diff --git a/WordPress/Classes/Services/AccountService.m b/Modules/Sources/WordPressDataObjC/Services/AccountService.m similarity index 99% rename from WordPress/Classes/Services/AccountService.m rename to Modules/Sources/WordPressDataObjC/Services/AccountService.m index 45f19e57fa45..3f51dbb9a063 100644 --- a/WordPress/Classes/Services/AccountService.m +++ b/Modules/Sources/WordPressDataObjC/Services/AccountService.m @@ -6,7 +6,7 @@ @import WordPressKit; @import WordPressShared; -#import "WordPress-Swift.h" +//#import "WordPress-Swift.h" static NSString * const DefaultDotcomAccountUUIDDefaultsKey = @"AccountDefaultDotcomUUID"; static NSString * const DefaultDotcomAccountPasswordRemovedKey = @"DefaultDotcomAccountPasswordRemovedKey"; diff --git a/WordPress/Classes/Services/BlogService.m b/Modules/Sources/WordPressDataObjC/Services/BlogService.m similarity index 99% rename from WordPress/Classes/Services/BlogService.m rename to Modules/Sources/WordPressDataObjC/Services/BlogService.m index e945ce1f2da7..74e41429537c 100644 --- a/WordPress/Classes/Services/BlogService.m +++ b/Modules/Sources/WordPressDataObjC/Services/BlogService.m @@ -3,12 +3,12 @@ #import "WPAccount.h" #import "AccountService.h" @import WordPressDataObjC; -#import "WPError.h" +//#import "WPError.h" #import "Media.h" #import "PostCategoryService.h" #import "CommentService.h" #import "PostService.h" -#import "WordPress-Swift.h" +//#import "WordPress-Swift.h" #import "PostType.h" @import WordPressKit; @import WordPressShared; diff --git a/Modules/Sources/WordPressDataObjC/Services/CommentService.m b/Modules/Sources/WordPressDataObjC/Services/CommentService.m new file mode 100644 index 000000000000..a2262b2520b1 --- /dev/null +++ b/Modules/Sources/WordPressDataObjC/Services/CommentService.m @@ -0,0 +1,1375 @@ +#import "CommentService.h" +#import "AccountService.h" +#import "Blog.h" +@import WordPressDataObjC; +#import "ReaderPost.h" +#import "WPAccount.h" +#import "PostService.h" +#import "AbstractPost.h" +#import "WordPress-Swift.h" + +@import WordPressShared; + +NSUInteger const WPTopLevelHierarchicalCommentsPerPage = 20; +NSInteger const WPNumberOfCommentsToSync = 100; +static NSTimeInterval const CommentsRefreshTimeoutInSeconds = 60 * 5; // 5 minutes + +@interface CommentService () + +@property (nonnull, strong, nonatomic) CommentServiceRemoteFactory *remoteFactory; + +@end + +@implementation CommentService + +- (instancetype)initWithCoreDataStack:(id)coreDataStack +{ + return [self initWithCoreDataStack:coreDataStack commentServiceRemoteFactory:[CommentServiceRemoteFactory new]]; +} + +- (instancetype)initWithCoreDataStack:(id)coreDataStack + commentServiceRemoteFactory:(CommentServiceRemoteFactory *)remoteFactory +{ + self = [super initWithCoreDataStack:coreDataStack]; + if (self) { + self.remoteFactory = remoteFactory; + } + return self; +} + ++ (NSMutableSet *)syncingCommentsLocks +{ + static NSMutableSet *syncingCommentsLocks; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + syncingCommentsLocks = [NSMutableSet set]; + }); + return syncingCommentsLocks; +} + ++ (BOOL)isSyncingCommentsForBlog:(Blog *)blog { + return [self isSyncingCommentsForBlogID:blog.objectID]; +} + ++ (BOOL)isSyncingCommentsForBlogID:(NSManagedObjectID *)blogID +{ + NSParameterAssert(blogID); + return [[self syncingCommentsLocks] containsObject:blogID]; +} + ++ (BOOL)startSyncingCommentsForBlog:(NSManagedObjectID *)blogID +{ + NSParameterAssert(blogID); + @synchronized([self syncingCommentsLocks]) { + if ([self isSyncingCommentsForBlogID:blogID]){ + return NO; + } + [[self syncingCommentsLocks] addObject:blogID]; + return YES; + } +} + ++ (void)stopSyncingCommentsForBlog:(NSManagedObjectID *)blogID +{ + NSParameterAssert(blogID); + @synchronized([self syncingCommentsLocks]) { + [[self syncingCommentsLocks] removeObject:blogID]; + } +} + ++ (BOOL)shouldRefreshCacheFor:(Blog *)blog +{ + NSDate *lastSynced = blog.lastCommentsSync; + BOOL isSyncing = [self isSyncingCommentsForBlog:blog]; + return !isSyncing && (lastSynced == nil || ABS(lastSynced.timeIntervalSinceNow) > CommentsRefreshTimeoutInSeconds); +} + +#pragma mark Public methods + +#pragma mark Blog-centric methods + +// Create comment +- (Comment *)createCommentForBlog:(Blog *)blog +{ + NSParameterAssert(blog.managedObjectContext != nil); + + Comment *comment = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Comment class]) + inManagedObjectContext:blog.managedObjectContext]; + comment.dateCreated = [NSDate new]; + comment.blog = blog; + return comment; +} + +// Create reply +- (void)createReplyForComment:(Comment *)comment content:(NSString *)content completion:(void (^)(Comment *reply))completion +{ + NSManagedObjectID *parentCommentID = comment.objectID; + NSManagedObjectID * __block replyID = nil; + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *comment = [context existingObjectWithID:parentCommentID error:nil]; + Comment *reply = [self createCommentForBlog:comment.blog]; + reply.postID = comment.postID; + reply.post = comment.post; + reply.parentID = comment.commentID; + reply.status = [Comment descriptionFor:CommentStatusTypeApproved]; + reply.content = content; + [context obtainPermanentIDsForObjects:@[reply] error:nil]; + replyID = reply.objectID; + } completion:^{ + if (completion) { + completion([self.coreDataStack.mainContext existingObjectWithID:replyID error:nil]); + } + } onQueue:dispatch_get_main_queue()]; +} + +// Sync comments +- (void)syncCommentsForBlog:(Blog *)blog + withStatus:(CommentStatusFilter)status + success:(void (^)(BOOL hasMore))success + failure:(void (^)(NSError *error))failure +{ + [self syncCommentsForBlog:blog withStatus:status filterUnreplied:NO success:success failure:failure]; +} + +- (void)syncCommentsForBlog:(Blog *)blog + withStatus:(CommentStatusFilter)status + filterUnreplied:(BOOL)filterUnreplied + success:(void (^)(BOOL hasMore))success + failure:(void (^)(NSError *error))failure +{ + NSManagedObjectID *blogID = blog.objectID; + if (![[self class] startSyncingCommentsForBlog:blogID]){ + // We assume success because a sync is already running and it will change the comments + if (success) { + success(YES); + } + return; + } + + // If the comment status is not specified, default to all. + CommentStatusFilter commentStatus = status ?: CommentStatusFilterAll; + NSDictionary *options = @{ @"status": [NSNumber numberWithInt:commentStatus] }; + + id remote = [self remoteForBlog:blog]; + + [remote getCommentsWithMaximumCount:WPNumberOfCommentsToSync + options:options + success:^(NSArray *comments) { + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Blog *blog = [context existingObjectWithID:blogID error:nil]; + if (!blog) { + return; + } + + NSArray *fetchedComments = comments; + if (filterUnreplied) { + NSString *author = @""; + if (blog.account) { + // See if there is a linked Jetpack user that we should use. + BlogAuthor *blogAuthor = [blog getAuthorWithLinkedID:blog.account.userID]; + author = (blogAuthor) ? blogAuthor.email : blog.account.email; + } else { + BlogAuthor *blogAuthor = [blog getAuthorWithId:blog.userID]; + author = (blogAuthor) ? blogAuthor.email : author; + } + fetchedComments = [self filterUnrepliedComments:comments forAuthor:author]; + } + [self mergeComments:fetchedComments forBlog:blog purgeExisting:YES]; + blog.lastCommentsSync = [NSDate date]; + } completion:^{ + [[self class] stopSyncingCommentsForBlog:blogID]; + + if (success) { + // Note: + // We'll assume that if the requested page size couldn't be filled, there are no + // more comments left to retrieve. However, for unreplied comments, we only fetch the first page (for now). + BOOL hasMore = comments.count >= WPNumberOfCommentsToSync && !filterUnreplied; + success(hasMore); + } + } onQueue:dispatch_get_main_queue()]; + } failure:^(NSError *error) { + [[self class] stopSyncingCommentsForBlog:blogID]; + + if (failure) { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; +} + +- (NSArray *)filterUnrepliedComments:(NSArray *)comments forAuthor:(NSString *)author { + NSMutableArray *marr = [comments mutableCopy]; + + NSMutableArray *foundIDs = [NSMutableArray array]; + NSMutableArray *discardables = [NSMutableArray array]; + + // get ids of comments that user has replied to. + for (RemoteComment *comment in marr) { + if (![comment.authorEmail isEqualToString:author] || !comment.parentID) { + continue; + } + [foundIDs addObject:comment.parentID]; + [discardables addObject:comment]; + } + // Discard the replies, they aren't needed. + [marr removeObjectsInArray:discardables]; + [discardables removeAllObjects]; + + // Get the parents, grandparents etc. and discard those too. + while ([foundIDs count] > 0) { + NSArray *needles = [foundIDs copy]; + [foundIDs removeAllObjects]; + for (RemoteComment *comment in marr) { + if ([needles containsObject:comment.commentID]) { + if (comment.parentID) { + [foundIDs addObject:comment.parentID]; + } + [discardables addObject:comment]; + } + } + // Discard the matches, and keep looking if items were found. + [marr removeObjectsInArray:discardables]; + [discardables removeAllObjects]; + } + + // remove any remaining child comments. + // remove any remaining root comments made by the user. + for (RemoteComment *comment in marr) { + if (comment.parentID.intValue != 0) { + [discardables addObject:comment]; + } else if ([comment.authorEmail isEqualToString:author]) { + [discardables addObject:comment]; + } + } + [marr removeObjectsInArray:discardables]; + + // these are the most recent unreplied comments from other users. + return [NSArray arrayWithArray:marr]; +} + +- (Comment *)oldestCommentForBlog:(Blog *)blog { + NSParameterAssert(blog.managedObjectContext != nil); + + NSString *entityName = NSStringFromClass([Comment class]); + NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName]; + request.predicate = [NSPredicate predicateWithFormat:@"dateCreated != NULL && blog=%@", blog]; + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"dateCreated" ascending:YES]; + request.sortDescriptors = @[sortDescriptor]; + + Comment * __block oldestComment = nil; + [blog.managedObjectContext performBlockAndWait:^{ + oldestComment = [[blog.managedObjectContext executeFetchRequest:request error:nil] firstObject]; + }]; + return oldestComment; +} + +- (void)loadMoreCommentsForBlog:(Blog *)blog + withStatus:(CommentStatusFilter)status + success:(void (^)(BOOL hasMore))success + failure:(void (^)(NSError *))failure +{ + NSManagedObjectID *blogID = blog.objectID; + if (![[self class] startSyncingCommentsForBlog:blogID]){ + // We assume success because a sync is already running and it will change the comments + if (success) { + success(YES); + } + } + + NSMutableDictionary *options = [NSMutableDictionary dictionary]; + + // If the comment status is not specified, default to all. + CommentStatusFilter commentStatus = status ?: CommentStatusFilterAll; + options[@"status"] = [NSNumber numberWithInt:commentStatus]; + + id remote = [self remoteForBlog:blog]; + + if ([remote isKindOfClass:[CommentServiceRemoteREST class]]) { + Comment *oldestComment = [self oldestCommentForBlog:blog]; + if (oldestComment.dateCreated) { + options[@"before"] = [oldestComment.dateCreated WordPressComJSONString]; + options[@"order"] = @"desc"; + } + } else if ([remote isKindOfClass:[CommentServiceRemoteXMLRPC class]]) { + NSUInteger commentCount = [blog.comments count]; + options[@"offset"] = @(commentCount); + } + + [remote getCommentsWithMaximumCount:WPNumberOfCommentsToSync + options:options + success:^(NSArray *comments) { + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Blog *blog = [context existingObjectWithID:blogID error:nil]; + if (!blog) { + return; + } + [self mergeComments:comments forBlog:blog purgeExisting:NO]; + } completion:^{ + [[self class] stopSyncingCommentsForBlog:blogID]; + if (success) { + success(comments.count > 1); + } + } onQueue:dispatch_get_main_queue()]; + } failure:^(NSError *error) { + [[self class] stopSyncingCommentsForBlog:blogID]; + if (failure) { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; +} + +- (void)loadCommentWithID:(NSNumber *)commentID + forBlog:(Blog *)blog + success:(void (^)(Comment *comment))success + failure:(void (^)(NSError *))failure { + + NSManagedObjectID *blogID = blog.objectID; + id remote = [self remoteForBlog:blog]; + + [remote getCommentWithID:commentID + success:^(RemoteComment *remoteComment) { + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Blog *blog = [context existingObjectWithID:blogID error:nil]; + if (!blog) { + return; + } + + Comment *comment = [blog commentWithID:remoteComment.commentID]; + if (!comment) { + comment = [self createCommentForBlog:blog]; + } + + [self updateComment:comment withRemoteComment:remoteComment]; + } completion:^{ + if (success) { + [self.coreDataStack.mainContext performBlock:^{ + Blog *blog = [self.coreDataStack.mainContext existingObjectWithID:blogID error:nil]; + success([blog commentWithID:remoteComment.commentID]); + }]; + } + } onQueue:dispatch_get_main_queue()]; + } failure:^(NSError *error) { + DDLogError(@"Error loading comment for blog: %@", error); + if (failure) { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; +} + +- (void)loadCommentWithID:(NSNumber *)commentID + forPost:(ReaderPost *)post + success:(void (^)(Comment *comment))success + failure:(void (^)(NSError *))failure { + + NSManagedObjectID *postID = post.objectID; + CommentServiceRemoteREST *service = [self restRemoteForSite:post.siteID]; + + [service getCommentWithID:commentID + success:^(RemoteComment *remoteComment) { + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + ReaderPost *post = [context existingObjectWithID:postID error:nil]; + if (!post) { + return; + } + + Comment *comment = [post commentWithID:remoteComment.commentID]; + + if (!comment) { + comment = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Comment class]) inManagedObjectContext:context]; + comment.dateCreated = [NSDate new]; + } + + comment.post = post; + [self updateComment:comment withRemoteComment:remoteComment]; + } completion:^{ + if (success) { + [self.coreDataStack.mainContext performBlock:^{ + ReaderPost *post = [self.coreDataStack.mainContext existingObjectWithID:postID error:nil]; + success([post commentWithID:remoteComment.commentID]); + }]; + } + } onQueue:dispatch_get_main_queue()]; + } failure:^(NSError *error) { + DDLogError(@"Error loading comment for post: %@", error); + if (failure) { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; +} + +// Upload comment +- (void)uploadComment:(Comment *)comment + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + id remote = [self remoteForComment:comment]; + RemoteComment *remoteComment = [self remoteCommentWithComment:comment]; + + NSManagedObjectID *commentObjectID = comment.objectID; + void (^successBlock)(RemoteComment *comment) = ^(RemoteComment *comment) { + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *commentInContext = [context existingObjectWithID:commentObjectID error:nil]; + if (commentInContext) { + [self updateComment:commentInContext withRemoteComment:comment]; + } + } completion:success onQueue:dispatch_get_main_queue()]; + }; + + if (comment.commentID != 0) { + [remote updateComment:remoteComment + success:successBlock + failure:failure]; + } else { + [remote createComment:remoteComment + success:successBlock + failure:failure]; + } +} + +// Approve +- (void)approveComment:(Comment *)comment + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + [self moderateComment:comment + withStatus:CommentStatusTypeApproved + success:success + failure:failure]; +} + +// Unapprove +- (void)unapproveComment:(Comment *)comment + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + [self moderateComment:comment + withStatus:CommentStatusTypePending + success:success + failure:failure]; +} + +// Spam +- (void)spamComment:(Comment *)comment + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + + // If the Comment is not permanently deleted, don't remove it from the local cache as it can still be displayed. + if (!comment.deleteWillBePermanent) { + [self moderateComment:comment + withStatus:CommentStatusTypeSpam + success:success + failure:failure]; + + return; + } + + NSManagedObjectID *commentID = comment.objectID; + + [self moderateComment:comment + withStatus:CommentStatusTypeSpam + success:^{ + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *commentInContext = [context existingObjectWithID:commentID error:nil]; + if (commentInContext != nil){ + [context deleteObject:commentInContext]; + } + } completion:success onQueue:dispatch_get_main_queue()]; + } failure: failure]; +} + +// Trash comment +- (void)trashComment:(Comment *)comment + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + [self moderateComment:comment + withStatus:CommentStatusTypeUnapproved + success:success + failure:failure]; +} + +// Delete comment +- (void)deleteComment:(Comment *)comment + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + // If this comment is local only, just delete. No need to query the endpoint or do any other work. + if (comment.commentID == 0) { + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *commentInContext = [context existingObjectWithID:comment.objectID error:nil]; + if (commentInContext != nil) { + [context deleteObject:commentInContext]; + } + } completion:success onQueue:dispatch_get_main_queue()]; + return; + } + + RemoteComment *remoteComment = [self remoteCommentWithComment:comment]; + id remote = [self remoteForBlog:comment.blog]; + + // If the Comment is not permanently deleted, don't remove it from the local cache as it can still be displayed. + if (!comment.deleteWillBePermanent) { + [remote trashComment:remoteComment success:success failure:failure]; + return; + } + + // For the best user experience we want to optimistically delete the comment. + // However, if there is an error we need to restore it. + NSManagedObjectID *blogObjID = comment.blog.objectID; + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *commentInContext = [context existingObjectWithID:comment.objectID error:nil]; + if (commentInContext != nil) { + [context deleteObject:commentInContext]; + } + } completion:^{ + [remote trashComment:remoteComment success:success failure:^(NSError *error) { + // Failure. Restore the comment. + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Blog *blog = [context objectWithID:blogObjID]; + if (!blog) { + return; + } + + Comment *comment = [self createCommentForBlog:blog]; + [self updateComment:comment withRemoteComment:remoteComment]; + } completion:^{ + if (failure) { + failure(error); + } + } onQueue:dispatch_get_main_queue()]; + }]; + } onQueue:dispatch_get_main_queue()]; +} + +#pragma mark - Post-centric methods + +- (void)syncHierarchicalCommentsForPost:(ReaderPost *)post + page:(NSUInteger)page + success:(void (^)(BOOL hasMore, NSNumber *totalComments))success + failure:(void (^)(NSError *error))failure +{ + [self syncHierarchicalCommentsForPost:post + page:page + topLevelComments:WPTopLevelHierarchicalCommentsPerPage + success:success + failure:failure]; +} + +- (void)syncHierarchicalCommentsForPost:(ReaderPost *)post + topLevelComments:(NSUInteger)number + success:(void (^)(BOOL hasMore, NSNumber *totalComments))success + failure:(void (^)(NSError *error))failure +{ + [self syncHierarchicalCommentsForPost:post + page:1 + topLevelComments:number + success:success + failure:failure]; +} + +- (void)syncHierarchicalCommentsForPost:(ReaderPost *)post + page:(NSUInteger)page + topLevelComments:(NSUInteger)number + success:(void (^)(BOOL hasMore, NSNumber *totalComments))success + failure:(void (^)(NSError *error))failure +{ + NSManagedObjectID *postObjectID = post.objectID; + NSNumber *siteID = post.siteID; + NSNumber *postID = post.postID; + + NSUInteger commentsPerPage = number ?: WPTopLevelHierarchicalCommentsPerPage; + NSUInteger pageNumber = page ?: 1; + + CommentServiceRemoteREST *service = [self restRemoteForSite:siteID]; + [service syncHierarchicalCommentsForPost:postID + page:pageNumber + number:commentsPerPage + success:^(NSArray *comments, NSNumber *totalComments) { + BOOL __block includesNewComments = NO; + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + NSError *error; + ReaderPost *aPost = [context existingObjectWithID:postObjectID error:&error]; + if (!aPost) { + if (failure) { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + return; + } + + includesNewComments = [self mergeHierarchicalComments:comments forPage:page forPost:aPost]; + } completion:^{ + if (!success) { + return; + } + + [self.coreDataStack.mainContext performBlock:^{ + NSError *error; + ReaderPost *aPost = [self.coreDataStack.mainContext existingObjectWithID:postObjectID error:&error]; + if (!aPost) { + if (failure) { + failure(error); + } + return; + } + + // There are no more comments when: + // - There are fewer top level comments in the results than requested + // - Page > 1, the number of top level comments matches those requested, but there are no new comments + // We check this way because the API can return the last page of results instead + // of returning zero results when the requested page is the last + 1. + NSArray *parents = [self topLevelCommentsForPage:page forPost:aPost]; + BOOL hasMore = YES; + if (([parents count] < WPTopLevelHierarchicalCommentsPerPage) || (page > 1 && !includesNewComments)) { + hasMore = NO; + } + + success(hasMore, totalComments); + }]; + } onQueue:dispatch_get_main_queue()]; + } failure:^(NSError *error) { + if (failure) { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; +} + +- (NSInteger)numberOfHierarchicalPagesSyncedforPost:(ReaderPost *)post +{ + NSSet *topComments = [post.comments filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"parentID = 0"]]; + CGFloat page = [topComments count] / WPTopLevelHierarchicalCommentsPerPage; + return (NSInteger)page; +} + +#pragma mark - REST Helpers + +- (NSString *)sanitizeCommentContent:(NSString *)string isPrivateSite:(BOOL)isPrivateSite +{ + NSString *content = string; + content = [RichContentFormatter removeTrailingBreakTags:content]; + content = [RichContentFormatter formatContentString:content isPrivateSite:isPrivateSite]; + return content; +} + +// Edition +- (void)updateCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + content:(NSString *)content + success:(void (^)(RemoteComment *comment))success + failure:(void (^)(NSError *error))failure +{ + CommentServiceRemoteREST *remote = [self restRemoteForSite:siteID]; + [remote updateCommentWithID:commentID + content:content + success:success + failure:failure]; +} + +// Replies +- (void)replyToPost:(ReaderPost *)post + content:(NSString *)content + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + // Create and optimistically save a comment, based on the current wpcom acct + // post and content provided. + BOOL isPrivateSite = post.isBlogPrivate; + [self createHierarchicalCommentWithContent:content withParent:nil postObjectID:post.objectID siteID:post.siteID completion:^(NSManagedObjectID *commentID) { + if (!commentID) { + NSError *error = [NSError errorWithDomain:WKErrorDomain code:WKErrorUnknown userInfo:@{NSDebugDescriptionErrorKey: @"Failed to create a comment for a post"}]; + if (failure) { + failure(error); + } + [WordPressAppDelegate logError:error]; + return; + } + void (^successBlock)(RemoteComment *remoteComment) = ^void(RemoteComment *remoteComment) { + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *comment = [context existingObjectWithID:commentID error:nil]; + if (!comment) { + return; + } + + remoteComment.content = [self sanitizeCommentContent:remoteComment.content isPrivateSite:isPrivateSite]; + + [self updateHierarchicalComment:comment withRemoteComment:remoteComment]; + } completion:success onQueue:dispatch_get_main_queue()]; + }; + + void (^failureBlock)(NSError *error) = ^void(NSError *error) { + // Remove the optimistically saved comment. + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *commentInContext = [context existingObjectWithID:commentID error:nil]; + if (commentInContext != nil) { + [context deleteObject:commentInContext]; + } + } completion:^{ + if (failure) { + failure(error); + } + } onQueue:dispatch_get_main_queue()]; + }; + + CommentServiceRemoteREST *remote = [self restRemoteForSite:post.siteID]; + [remote replyToPostWithID:post.postID + content:content + success:successBlock + failure:failureBlock]; + }]; +} + +- (void)replyToHierarchicalCommentWithID:(NSNumber *)commentID + post:(ReaderPost *)post + content:(NSString *)content + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + // Create and optimistically save a comment, based on the current wpcom acct + // post and content provided. + BOOL isPrivateSite = post.isBlogPrivate; + [self createHierarchicalCommentWithContent:content withParent:nil postObjectID:post.objectID siteID:post.siteID completion:^(NSManagedObjectID *commentObjectID) { + if (!commentObjectID) { + NSError *error = [NSError errorWithDomain:WKErrorDomain code:WKErrorUnknown userInfo:@{NSDebugDescriptionErrorKey: @"Failed to create a comment for a post"}]; + if (failure) { + failure(error); + } + [WordPressAppDelegate logError:error]; + return; + } + void (^successBlock)(RemoteComment *remoteComment) = ^void(RemoteComment *remoteComment) { + // Update and save the comment + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *comment = [context existingObjectWithID:commentObjectID error:nil]; + if (!comment) { + return; + } + + remoteComment.content = [self sanitizeCommentContent:remoteComment.content isPrivateSite:isPrivateSite]; + + [self updateHierarchicalComment:comment withRemoteComment:remoteComment]; + } completion:success onQueue:dispatch_get_main_queue()]; + }; + + void (^failureBlock)(NSError *error) = ^void(NSError *error) { + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *commentInContext = [context existingObjectWithID:commentObjectID error:nil]; + if (!commentInContext) { + return; + } + // Remove the optimistically saved comment. + [context deleteObject:commentInContext]; + ReaderPost *post = (ReaderPost *)commentInContext.post; + post.commentCount = @([post.commentCount integerValue] - 1); + } completion:^{ + if (failure) { + failure(error); + } + } onQueue:dispatch_get_main_queue()]; + }; + + CommentServiceRemoteREST *remote = [self restRemoteForSite:post.siteID]; + [remote replyToCommentWithID:commentID + content:content + success:successBlock + failure:failureBlock]; + }]; +} + +- (void)replyToCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + content:(NSString *)content + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + CommentServiceRemoteREST *remote = [self restRemoteForSite:siteID]; + [remote replyToCommentWithID:commentID + content:content + success:^(RemoteComment * __unused comment){ + if (success){ + success(); + } + } + failure:failure]; +} + + +// Likes +- (void)likeCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + CommentServiceRemoteREST *remote = [self restRemoteForSite:siteID]; + [remote likeCommentWithID:commentID + success:success + failure:failure]; +} + +- (void)unlikeCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + CommentServiceRemoteREST *remote = [self restRemoteForSite:siteID]; + [remote unlikeCommentWithID:commentID + success:success + failure:failure]; +} + +// Moderation +- (void)approveCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + CommentServiceRemoteREST *remote = [self restRemoteForSite:siteID]; + [remote moderateCommentWithID:commentID + status:@"approved" + success:success + failure:failure]; +} + +- (void)unapproveCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + CommentServiceRemoteREST *remote = [self restRemoteForSite:siteID]; + [remote moderateCommentWithID:commentID + status:@"unapproved" + success:success + failure:failure]; +} + +- (void)spamCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + CommentServiceRemoteREST *remote = [self restRemoteForSite:siteID]; + [remote moderateCommentWithID:commentID + status:[Comment descriptionFor:CommentStatusTypeSpam] + success:success + failure:failure]; +} + +- (void)deleteCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + CommentServiceRemoteREST *remote = [self restRemoteForSite:siteID]; + [remote trashCommentWithID:commentID + success:success + failure:failure]; +} + +- (void)toggleLikeStatusForComment:(Comment *)comment + siteID:(NSNumber *)siteID + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + NSManagedObjectID *commentObjectID = comment.objectID; + BOOL isLikedOriginally = comment.isLiked; + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + // toggle the like status and change the like count and save it + Comment *comment = [context existingObjectWithID:commentObjectID error:nil]; + comment.isLiked = !isLikedOriginally; + comment.likeCount = comment.likeCount + (comment.isLiked ? 1 : -1); + } completion:^{ + // This block will reverse the like/unlike action + void (^failureBlock)(NSError *) = ^(NSError *error) { + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *comment = [context existingObjectWithID:commentObjectID error:nil]; + DDLogError(@"Error while %@ comment: %@", comment.isLiked ? @"liking" : @"unliking", error); + + comment.isLiked = isLikedOriginally; + comment.likeCount = comment.likeCount + (comment.isLiked ? 1 : -1); + } completion:^{ + if (failure) { + failure(error); + } + } onQueue:dispatch_get_main_queue()]; + }; + + NSNumber *commentID = [NSNumber numberWithInt:comment.commentID]; + + if (!isLikedOriginally) { + [self likeCommentWithID:commentID siteID:siteID success:success failure:failureBlock]; + } + else { + [self unlikeCommentWithID:commentID siteID:siteID success:success failure:failureBlock]; + } + } onQueue:dispatch_get_main_queue()]; +} + + +#pragma mark - Private methods + +// Deletes orphaned comments. Does not save context. +- (void)deleteUnownedCommentsInContext:(NSManagedObjectContext *)context +{ + NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Comment class])]; + fetchRequest.predicate = [NSPredicate predicateWithFormat:@"post = NULL && blog = NULL"]; + + NSError *error; + NSArray *results = [context executeFetchRequest:fetchRequest error:&error]; + if (error) { + DDLogError(@"Error fetching orphaned comments: %@", error); + } + for (Comment *comment in results) { + [context deleteObject:comment]; + } +} + + + + +#pragma mark - Blog centric methods +// Generic moderation +- (void)moderateComment:(Comment *)comment + withStatus:(CommentStatusType)status + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + NSString *currentStatus = [Comment descriptionFor:status]; + NSString *prevStatus = comment.status; + + if ([prevStatus isEqualToString:currentStatus]) { + if (success) { + success(); + } + return; + } + + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *commentInContext = [context existingObjectWithID:comment.objectID error:nil]; + commentInContext.status = currentStatus; + }]; + + comment.status = currentStatus; + + id remote = [self remoteForComment:comment]; + RemoteComment *remoteComment = [self remoteCommentWithComment:comment]; + [remote moderateComment:remoteComment + success:^(RemoteComment * __unused comment) { + if (success) { + success(); + } + } failure:^(NSError *error) { + DDLogError(@"Error moderating comment: %@", error); + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + Comment *commentInContext = [context existingObjectWithID:comment.objectID error:nil]; + commentInContext.status = prevStatus; + } completion:^{ + if (failure) { + failure(error); + } + } onQueue:dispatch_get_main_queue()]; + }]; +} + +- (void)mergeComments:(NSArray *)comments + forBlog:(Blog *)blog + purgeExisting:(BOOL)purgeExisting +{ + NSParameterAssert(blog.managedObjectContext != nil); + + NSMutableArray *commentsToKeep = [NSMutableArray array]; + for (RemoteComment *remoteComment in comments) { + Comment *comment = [blog commentWithID:remoteComment.commentID]; + if (!comment) { + comment = [self createCommentForBlog:blog]; + } + [self updateComment:comment withRemoteComment:remoteComment]; + [commentsToKeep addObject:comment]; + } + + if (purgeExisting) { + NSSet *existingComments = blog.comments; + if (existingComments.count > 0) { + for (Comment *comment in existingComments) { + // Don't delete unpublished comments + if (![commentsToKeep containsObject:comment] && comment.commentID != 0) { + DDLogInfo(@"Deleting Comment: %@", comment); + [blog.managedObjectContext deleteObject:comment]; + } + } + } + } + + [self deleteUnownedCommentsInContext:blog.managedObjectContext]; +} + +#pragma mark - Post centric methods + +- (NSMutableArray *)ancestorsForCommentWithParentID:(NSNumber *)parentID andCurrentAncestors:(NSArray *)currentAncestors +{ + NSMutableArray *ancestors = [currentAncestors mutableCopy]; + + // Calculate hierarchy and depth. + if (parentID.intValue != 0) { + if ([ancestors containsObject:parentID]) { + NSUInteger index = [ancestors indexOfObject:parentID] + 1; + NSArray *subarray = [ancestors subarrayWithRange:NSMakeRange(0, index)]; + [ancestors removeAllObjects]; + [ancestors addObjectsFromArray:subarray]; + } else { + [ancestors addObject:parentID]; + } + } else { + [ancestors removeAllObjects]; + } + return ancestors; +} + +- (NSString *)hierarchyFromAncestors:(NSArray *)ancestors andCommentID:(NSNumber *)commentID +{ + NSArray *arr = [ancestors arrayByAddingObject:commentID]; + arr = [self formatHierarchyElements:arr]; + return [arr componentsJoinedByString:@"."]; +} + +- (NSArray *)formatHierarchyElements:(NSArray *)hierarchy +{ + NSMutableArray *arr = [NSMutableArray array]; + for (NSNumber *commentID in hierarchy) { + [arr addObject:[self formattedHierarchyElement:commentID]]; + } + return arr; +} + +- (NSString *)formattedHierarchyElement:(NSNumber *)commentID +{ + return [NSString stringWithFormat:@"%010u", [commentID integerValue]]; +} + +- (void)createHierarchicalCommentWithContent:(NSString *)content + withParent:(NSNumber *)parentID + postObjectID:(NSManagedObjectID *)postObjectID + siteID:(NSNumber *)siteID + completion:(void (^)(NSManagedObjectID *commentID))completion +{ + NSManagedObjectID * __block objectID = nil; + [self.coreDataStack performAndSaveUsingBlock:^(NSManagedObjectContext *context) { + ReaderPost *post = [context existingObjectWithID:postObjectID error:nil]; + Comment *comment = [self createHierarchicalCommentWithContent:content withParent:parentID postID:post.postID siteID:siteID inContext:context]; + objectID = comment.objectID; + // This fixes an issue where the comment may not appear for some posts after a successful posting + // More information: https://github.com/wordpress-mobile/WordPress-iOS/issues/13259 + comment.post = post; + } completion:^{ + completion(objectID); + } onQueue:dispatch_get_main_queue()]; +} + +- (Comment *)createHierarchicalCommentWithContent:(NSString *)content withParent:(NSNumber *)parentID postID:(NSNumber *)postID siteID:(NSNumber *)siteID inContext:(NSManagedObjectContext *)context +{ + // Fetch the relevant ReaderPost + NSError *error; + NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([ReaderPost class])]; + fetchRequest.predicate = [NSPredicate predicateWithFormat:@"postID = %@ AND siteID = %@", postID, siteID]; + fetchRequest.fetchLimit = 1; + NSArray *results = [context executeFetchRequest:fetchRequest error:&error]; + if (error) { + DDLogError(@"Error fetching post with id %@ and site %@. %@", postID, siteID, error); + return nil; + } + + ReaderPost *post = [results firstObject]; + if (!post) { + return nil; + } + + // (Insert a new comment into core data. Check for its existance first for paranoia sake. + // In theory a sync could include a newly created comment before the request that created it returned. + Comment *comment = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Comment class]) inManagedObjectContext:context]; + + WPAccount *account = [WPAccount lookupDefaultWordPressComAccountInContext:context]; + comment.author = [account username]; + comment.authorID = [account.userID intValue]; + comment.content = content; + comment.dateCreated = [NSDate date]; + comment.parentID = [parentID intValue]; + comment.postID = [postID intValue]; + comment.postTitle = post.postTitle; + comment.status = [Comment descriptionFor:CommentStatusTypeDraft]; + comment.post = post; + + // Increment the post's comment count. + post.commentCount = @([post.commentCount integerValue] + 1); + + // Find its parent comment (if it exists) + Comment *parentComment; + if (parentID.intValue != 0) { + parentComment = [post commentWithID:parentID]; + } + + // Update depth and hierarchy + [self setHierarchyAndDepthOnComment:comment withParentComment:parentComment]; + + [context obtainPermanentIDsForObjects:@[comment] error:&error]; + if (error) { + DDLogError(@"%@ error obtaining permanent ID for a hierarchical comment %@: %@", NSStringFromSelector(_cmd), comment, error); + } + + return comment; +} + +- (void)setHierarchyAndDepthOnComment:(Comment *)comment withParentComment:(Comment *)parentComment +{ + NSParameterAssert(comment.managedObjectContext != nil); + + // Update depth and hierarchy + NSNumber *commentID = [NSNumber numberWithInt:comment.commentID]; + + if (commentID != 0) { + // A new comment will have a 0 commentID. If 0 is used when formatting the hierarchy, + // the comment will preceed any other comment in its level of the hierarchy. + // Instead we'll pass a number so large as to ensure the comment will appear last in a list. + commentID = @9999999; + } + + if (parentComment) { + comment.hierarchy = [NSString stringWithFormat:@"%@.%@", parentComment.hierarchy, [self formattedHierarchyElement:commentID]]; + comment.depth = parentComment.depth + 1; + } else { + comment.hierarchy = [self formattedHierarchyElement:commentID]; + comment.depth = 0; + } +} + +- (void)updateHierarchicalComment:(Comment *)comment withRemoteComment:(RemoteComment *)remoteComment +{ + NSParameterAssert(comment.managedObjectContext != nil); + + [self updateComment:comment withRemoteComment:remoteComment]; + // Find its parent comment (if it exists) + Comment *parentComment; + if (comment.parentID != 0) { + NSNumber *parentID = [NSNumber numberWithInt:comment.parentID]; + parentComment = [(ReaderPost *)comment.post commentWithID:parentID]; + } + + // Update depth and hierarchy + [self setHierarchyAndDepthOnComment:comment withParentComment:parentComment]; +} + +- (BOOL)mergeHierarchicalComments:(NSArray *)comments forPage:(NSUInteger)page forPost:(ReaderPost *)post +{ + NSParameterAssert(post.managedObjectContext != nil); + + if (![comments count]) { + return NO; + } + + NSMutableSet *visibleCommentIds = [NSMutableSet new]; + NSMutableArray *ancestors = [NSMutableArray array]; + NSMutableArray *commentsToKeep = [NSMutableArray array]; + NSString *entityName = NSStringFromClass([Comment class]); + NSUInteger newCommentCount = 0; + + for (RemoteComment *remoteComment in comments) { + Comment *comment = [post commentWithID:remoteComment.commentID]; + if (!comment) { + newCommentCount++; + comment = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:post.managedObjectContext]; + } + + [self updateComment:comment withRemoteComment:remoteComment]; + + // Calculate hierarchy and depth. + ancestors = [self ancestorsForCommentWithParentID:[NSNumber numberWithInt:comment.parentID] andCurrentAncestors:ancestors]; + comment.hierarchy = [self hierarchyFromAncestors:ancestors andCommentID:[NSNumber numberWithInt:comment.commentID]]; + + // Comments are shown on the thread when (1) it is approved, and (2) its ancestors are approved. + // Having the comments sorted hierarchically ascending ensures that each comment's predecessors will be visited first. + // Therefore, we only need to check if the comment and its direct parent are approved. + // Ref: https://github.com/wordpress-mobile/WordPress-iOS/issues/18081 + BOOL hasValidParent = comment.parentID > 0 && [visibleCommentIds containsObject:@(comment.parentID)]; + if ([comment isApproved] && ([comment isTopLevelComment] || hasValidParent)) { + [visibleCommentIds addObject:@(comment.commentID)]; + } + comment.visibleOnReader = [visibleCommentIds containsObject:@(comment.commentID)]; + + comment.depth = ancestors.count; + comment.post = post; + comment.content = [self sanitizeCommentContent:comment.content isPrivateSite:post.isBlogPrivate]; + [commentsToKeep addObject:comment]; + } + + // Remove deleted comments + // When merging the first fetched page of comments, clear out anything that was previously + // cached and missing from the comments just synced. This provides for a clean slate and + // helps avoid certain cases where some pages might not be resynced, creating gaps in the content. + if (page == 1) { + [self deleteCommentsMissingFromHierarchicalComments:commentsToKeep forPost:post]; + [self deleteUnownedCommentsInContext:post.managedObjectContext]; + } + + // Make sure the post's comment count is at least the number of comments merged. + if ([post.commentCount integerValue] < [commentsToKeep count]) { + post.commentCount = @([commentsToKeep count]); + } + + return newCommentCount > 0; +} + +// Does not save context +- (void)deleteCommentsMissingFromHierarchicalComments:(NSArray *)commentsToKeep forPost:(ReaderPost *)post +{ + for (Comment *comment in post.comments) { + if (![commentsToKeep containsObject:comment]) { + [post.managedObjectContext deleteObject:comment]; + } + } +} + +- (NSArray *)topLevelCommentsForPage:(NSUInteger)page forPost:(ReaderPost *)post +{ + NSString *entityName = NSStringFromClass([Comment class]); + + // Retrieve the starting and ending comments for the specified page. + NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:entityName]; + fetchRequest.predicate = [NSPredicate predicateWithFormat:@"post = %@ AND parentID = 0", post]; + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"hierarchy" ascending:YES]; + fetchRequest.sortDescriptors = @[sortDescriptor]; + [fetchRequest setFetchLimit:WPTopLevelHierarchicalCommentsPerPage]; + NSUInteger offset = WPTopLevelHierarchicalCommentsPerPage * (page - 1); + [fetchRequest setFetchOffset:offset]; + + NSError *error = nil; + NSArray *fetchedObjects = [post.managedObjectContext executeFetchRequest:fetchRequest error:&error]; + if (error) { + DDLogError(@"Error fetching top level comments for page %i : %@", page, error); + } + return fetchedObjects; +} + +- (NSArray *)topLevelComments:(NSUInteger)number forPost:(ReaderPost *)post +{ + NSParameterAssert(post.managedObjectContext != nil); + NSArray * __block commentsToReturn = nil; + [post.managedObjectContext performBlockAndWait:^{ + NSArray *comments = [self topLevelCommentsForPage:1 forPost:post]; + NSInteger count = MIN(comments.count, number); + commentsToReturn = [comments subarrayWithRange:NSMakeRange(0, count)]; + }]; + return commentsToReturn; +} + +#pragma mark - Transformations + +- (void)updateComment:(Comment *)comment withRemoteComment:(RemoteComment *)remoteComment +{ + NSParameterAssert(comment.managedObjectContext != nil); + + comment.commentID = [remoteComment.commentID intValue]; + comment.authorID = [remoteComment.authorID intValue]; + comment.author = remoteComment.author; + comment.author_email = remoteComment.authorEmail; + comment.author_url = remoteComment.authorUrl; + comment.authorAvatarURL = remoteComment.authorAvatarURL; + comment.author_ip = remoteComment.authorIP; + comment.content = remoteComment.content; + comment.rawContent = remoteComment.rawContent; + comment.dateCreated = remoteComment.date; + comment.link = remoteComment.link; + comment.parentID = [remoteComment.parentID intValue]; + comment.postID = [remoteComment.postID intValue]; + comment.postTitle = remoteComment.postTitle; + comment.status = remoteComment.status; + comment.type = remoteComment.type; + comment.isLiked = remoteComment.isLiked; + comment.likeCount = [remoteComment.likeCount intValue]; + comment.canModerate = remoteComment.canModerate; + + // if the post for the comment is not set, check if that post is already stored and associate them + if (!comment.post) { + comment.post = [comment.blog lookupPostWithID:[NSNumber numberWithInt:comment.postID] inContext:comment.managedObjectContext]; + } +} + +- (RemoteComment *)remoteCommentWithComment:(Comment *)comment +{ + RemoteComment *remoteComment = [RemoteComment new]; + remoteComment.commentID = [NSNumber numberWithInt:comment.commentID]; + remoteComment.authorID = [NSNumber numberWithInt:comment.authorID]; + remoteComment.author = comment.author; + remoteComment.authorEmail = comment.author_email; + remoteComment.authorUrl = comment.author_url; + remoteComment.authorAvatarURL = comment.authorAvatarURL; + remoteComment.authorIP = comment.author_ip; + remoteComment.content = comment.content; + remoteComment.date = comment.dateCreated; + remoteComment.link = comment.link; + remoteComment.parentID = [NSNumber numberWithInt:comment.parentID]; + remoteComment.postID = [NSNumber numberWithInt:comment.postID]; + remoteComment.postTitle = comment.postTitle; + remoteComment.status = comment.status; + remoteComment.type = comment.type; + remoteComment.isLiked = comment.isLiked; + remoteComment.likeCount = [NSNumber numberWithInt:comment.likeCount]; + remoteComment.canModerate = comment.canModerate; + return remoteComment; +} + + +#pragma mark - Remotes + +- (id)remoteForComment:(Comment *)comment +{ + NSParameterAssert(comment.managedObjectContext != nil); + + // If the comment is fetched through the Reader API, the blog will always be nil. + // Try to find the Blog locally first, as it should exist if the user has a role on the site. + if (comment.post && [comment.post isKindOfClass:[ReaderPost class]]) { + ReaderPost *readerPost = (ReaderPost *)comment.post; + return [self remoteForBlog:[Blog lookupWithHostname:readerPost.blogURL inContext:comment.managedObjectContext]]; + } + + return [self remoteForBlog:comment.blog]; +} + +- (id)remoteForBlog:(Blog *)blog +{ + return [self.remoteFactory remoteWithBlog:blog]; +} + +- (CommentServiceRemoteREST *)restRemoteForSite:(NSNumber *)siteID +{ + return [self.remoteFactory restRemoteWithSiteID:siteID api:[self apiForRESTRequest]]; +} + +/** + Get the api to use for the request. + */ +- (WordPressComRestApi *)apiForRESTRequest +{ + WPAccount * __block defaultAccount = nil; + [self.coreDataStack.mainContext performBlockAndWait:^{ + defaultAccount = [WPAccount lookupDefaultWordPressComAccountInContext:self.coreDataStack.mainContext]; + }]; + + WordPressComRestApi *api = [defaultAccount wordPressComRestApi]; + //Sergio Estevao: Do we really want to do this? If the call going to be valid if no credential is available? + if (![api hasCredentials]) { + api = [WordPressComRestApi defaultApiWithOAuthToken:nil + userAgent:[WPUserAgent wordPressUserAgent] + localeKey:[WordPressComRestApi LocaleKeyDefault]]; + } + return api; +} + +@end diff --git a/Modules/Sources/WordPressDataObjC/CoreDataService.m b/Modules/Sources/WordPressDataObjC/Services/CoreDataService.m similarity index 100% rename from Modules/Sources/WordPressDataObjC/CoreDataService.m rename to Modules/Sources/WordPressDataObjC/Services/CoreDataService.m diff --git a/WordPress/Classes/Services/PostCategoryService.m b/Modules/Sources/WordPressDataObjC/Services/PostCategoryService.m similarity index 100% rename from WordPress/Classes/Services/PostCategoryService.m rename to Modules/Sources/WordPressDataObjC/Services/PostCategoryService.m diff --git a/WordPress/Classes/Models/AbstractPost.h b/Modules/Sources/WordPressDataObjC/include/AbstractPost.h similarity index 100% rename from WordPress/Classes/Models/AbstractPost.h rename to Modules/Sources/WordPressDataObjC/include/AbstractPost.h diff --git a/WordPress/Classes/Services/AccountService.h b/Modules/Sources/WordPressDataObjC/include/AccountService.h similarity index 100% rename from WordPress/Classes/Services/AccountService.h rename to Modules/Sources/WordPressDataObjC/include/AccountService.h diff --git a/WordPress/Classes/Models/BasePost.h b/Modules/Sources/WordPressDataObjC/include/BasePost.h similarity index 100% rename from WordPress/Classes/Models/BasePost.h rename to Modules/Sources/WordPressDataObjC/include/BasePost.h diff --git a/WordPress/Classes/Models/Blog/Blog.h b/Modules/Sources/WordPressDataObjC/include/Blog.h similarity index 100% rename from WordPress/Classes/Models/Blog/Blog.h rename to Modules/Sources/WordPressDataObjC/include/Blog.h diff --git a/WordPress/Classes/Services/BlogService.h b/Modules/Sources/WordPressDataObjC/include/BlogService.h similarity index 100% rename from WordPress/Classes/Services/BlogService.h rename to Modules/Sources/WordPressDataObjC/include/BlogService.h diff --git a/Modules/Sources/WordPressDataObjC/include/CommentService.h b/Modules/Sources/WordPressDataObjC/include/CommentService.h new file mode 100644 index 000000000000..8c6f136eaae9 --- /dev/null +++ b/Modules/Sources/WordPressDataObjC/include/CommentService.h @@ -0,0 +1,207 @@ +#import +#import "CoreDataService.h" +@import WordPressDataObjC; + +NS_ASSUME_NONNULL_BEGIN + +@import WordPressKit; + +extern NSUInteger const WPTopLevelHierarchicalCommentsPerPage; + +@class Blog; +@class Comment; +@class ReaderPost; +@class BasePost; +@class RemoteUser; +@class CommentServiceRemoteFactory; + +@interface CommentService : CoreDataService + +/// Initializes the instance with a custom service remote provider. +/// +/// @param coreDataStack The `CoreDataStack` this instance will use for interacting with CoreData. +/// @param commentServiceRemoteFactory The factory this instance will use to get service remote instances from. +- (instancetype)initWithCoreDataStack:(id)coreDataStack + commentServiceRemoteFactory:(CommentServiceRemoteFactory *)remoteFactory NS_DESIGNATED_INITIALIZER; + +// Create reply +- (void)createReplyForComment:(Comment *)comment content:(NSString *)content completion:(void (^)(Comment *reply))completion; + +// Sync comments +- (void)syncCommentsForBlog:(Blog *)blog + withStatus:(CommentStatusFilter)status + success:(void (^ _Nullable)(BOOL hasMore))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +- (void)syncCommentsForBlog:(Blog *)blog + withStatus:(CommentStatusFilter)status + filterUnreplied:(BOOL)filterUnreplied + success:(void (^ _Nullable)(BOOL hasMore))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Determine if a recent cache is available ++ (BOOL)shouldRefreshCacheFor:(Blog *)blog; + +// Load extra comments +- (void)loadMoreCommentsForBlog:(Blog *)blog + withStatus:(CommentStatusFilter)status + success:(void (^ _Nullable)(BOOL hasMore))success + failure:(void (^ _Nullable)(NSError * _Nullable))failure; + +// Load a single comment +- (void)loadCommentWithID:(NSNumber *_Nonnull)commentID + forBlog:(Blog *_Nonnull)blog + success:(void (^_Nullable)(Comment *_Nullable))success + failure:(void (^_Nullable)(NSError *_Nullable))failure; + +- (void)loadCommentWithID:(NSNumber *_Nonnull)commentID + forPost:(ReaderPost *_Nonnull)post + success:(void (^_Nullable)(Comment *_Nullable))success + failure:(void (^_Nullable)(NSError *_Nullable))failure; + +// Upload comment +- (void)uploadComment:(Comment *)comment + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Approve comment +- (void)approveComment:(Comment *)comment + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Unapprove (Pending) comment +- (void)unapproveComment:(Comment *)comment + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Spam comment +- (void)spamComment:(Comment *)comment + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Trash comment +- (void)trashComment:(Comment *)comment + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Delete comment +- (void)deleteComment:(Comment *)comment + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Sync a list of comments sorted by hierarchy, fetched by page number. +- (void)syncHierarchicalCommentsForPost:(ReaderPost *)post + page:(NSUInteger)page + success:(void (^ _Nullable)(BOOL hasMore, NSNumber * _Nullable totalComments))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Sync a list of comments sorted by hierarchy, restricted by the specified number of _top level_ comments. +// This method is intended to get a small number of comments. +// Therefore it is restricted to page 1 only. +- (void)syncHierarchicalCommentsForPost:(ReaderPost *)post + topLevelComments:(NSUInteger)number + success:(void (^ _Nullable)(BOOL hasMore, NSNumber * _Nullable totalComments))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Get the specified number of top level comments for the specified post. +// This method is intended to get a small number of comments. +// Therefore it is restricted to page 1 only. +- (NSArray *)topLevelComments:(NSUInteger)number forPost:(ReaderPost *)post; + +// Counts and returns the number of full pages of hierarchcial comments synced for a post. +// A partial set does not count toward the total number of pages. +- (NSInteger)numberOfHierarchicalPagesSyncedforPost:(ReaderPost *)post; + + +/** + REST Helpers: + ============= + + Decoupled from the CoreData Model, the main goal of the following helpers is to + to allow Comment Interaction in scenarios in which the Comment / Blog instances may not be available. +*/ + +// Edit comment +- (void)updateCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + content:(NSString *)content + success:(void (^ _Nullable)(RemoteComment * _Nullable comment))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Replies +- (void)replyToPost:(ReaderPost *)post + content:(NSString *)content + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +- (void)replyToHierarchicalCommentWithID:(NSNumber *)commentID + post:(ReaderPost *)post + content:(NSString *)content + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +- (void)replyToCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + content:(NSString *)content + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Like comment +- (void)likeCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Unlike comment +- (void)unlikeCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Approve comment +- (void)approveCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Unapprove comment +- (void)unapproveCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Spam comment +- (void)spamCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +// Delete comment +- (void)deleteCommentWithID:(NSNumber *)commentID + siteID:(NSNumber *)siteID + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +/** + This method will toggle the like status for a comment and optimistically save it. It will also + trigger either likeCommentWithID or unlikeCommentWithID. In case the request fails, like status + will be reverted back. + + @param siteID is used since the blog might be nil for comment. It's not optional! + */ +- (void)toggleLikeStatusForComment:(Comment *)comment + siteID:(NSNumber *)siteID + success:(void (^ _Nullable)(void))success + failure:(void (^ _Nullable)(NSError * _Nullable error))failure; + +/** + Get a CommentServiceRemoteREST for the given site. + This is public so it can be accessed from Swift extensions. + + @param siteID The ID of the site the remote will be used for. + */ +- (CommentServiceRemoteREST *_Nullable)restRemoteForSite:(NSNumber *_Nonnull)siteID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Modules/Sources/WordPressDataObjC/include/CoreDataStack.h b/Modules/Sources/WordPressDataObjC/include/CoreDataStack.h index 69c79f3a95ab..27cc2076e862 100644 --- a/Modules/Sources/WordPressDataObjC/include/CoreDataStack.h +++ b/Modules/Sources/WordPressDataObjC/include/CoreDataStack.h @@ -7,6 +7,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, strong) NSManagedObjectContext *mainContext; ++ (id)sharedInstance; + - (NSManagedObjectContext *const)newDerivedContext DEPRECATED_MSG_ATTRIBUTE("Use `performAndSave` instead"); - (void)saveContextAndWait:(NSManagedObjectContext *)context; diff --git a/WordPress/Classes/Models/Media.h b/Modules/Sources/WordPressDataObjC/include/Media.h similarity index 100% rename from WordPress/Classes/Models/Media.h rename to Modules/Sources/WordPressDataObjC/include/Media.h diff --git a/WordPress/Classes/Models/PostCategory.h b/Modules/Sources/WordPressDataObjC/include/PostCategory.h similarity index 100% rename from WordPress/Classes/Models/PostCategory.h rename to Modules/Sources/WordPressDataObjC/include/PostCategory.h diff --git a/WordPress/Classes/Services/PostCategoryService.h b/Modules/Sources/WordPressDataObjC/include/PostCategoryService.h similarity index 100% rename from WordPress/Classes/Services/PostCategoryService.h rename to Modules/Sources/WordPressDataObjC/include/PostCategoryService.h diff --git a/WordPress/Classes/Models/PostContentProvider.h b/Modules/Sources/WordPressDataObjC/include/PostContentProvider.h similarity index 100% rename from WordPress/Classes/Models/PostContentProvider.h rename to Modules/Sources/WordPressDataObjC/include/PostContentProvider.h diff --git a/WordPress/Classes/Models/WPAccount.h b/Modules/Sources/WordPressDataObjC/include/WPAccount.h similarity index 100% rename from WordPress/Classes/Models/WPAccount.h rename to Modules/Sources/WordPressDataObjC/include/WPAccount.h diff --git a/WordPress/WordPressTest/ContextManagerTests.swift b/Modules/Tests/WordPressDataTests/ContextManagerTests.swift similarity index 100% rename from WordPress/WordPressTest/ContextManagerTests.swift rename to Modules/Tests/WordPressDataTests/ContextManagerTests.swift diff --git a/WordPress/WordPressTest/CoreDataHelperTests.swift b/Modules/Tests/WordPressDataTests/CoreDataHelperTests.swift similarity index 100% rename from WordPress/WordPressTest/CoreDataHelperTests.swift rename to Modules/Tests/WordPressDataTests/CoreDataHelperTests.swift diff --git a/WordPress/Classes/Extensions/Post+BloggingPrompts.swift b/WordPress/Classes/Extensions/Post+BloggingPrompts.swift index 7aac23b727aa..b2e8ef7963c7 100644 --- a/WordPress/Classes/Extensions/Post+BloggingPrompts.swift +++ b/WordPress/Classes/Extensions/Post+BloggingPrompts.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressData extension Post { diff --git a/WordPress/Classes/Models/Blog/Blog+HomepageSettings.swift b/WordPress/Classes/Models/Blog/Blog+HomepageSettings.swift index 2e451c5f09f9..5c9f6a03b0ae 100644 --- a/WordPress/Classes/Models/Blog/Blog+HomepageSettings.swift +++ b/WordPress/Classes/Models/Blog/Blog+HomepageSettings.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressData enum HomepageType: String { case page diff --git a/WordPress/Classes/Models/Blog/Blog+Lookup.swift b/WordPress/Classes/Models/Blog/Blog+Lookup.swift index dbc57cc3ad6f..8395938ebeef 100644 --- a/WordPress/Classes/Models/Blog/Blog+Lookup.swift +++ b/WordPress/Classes/Models/Blog/Blog+Lookup.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressData /// An extension dedicated to looking up and returning blog objects public extension Blog { diff --git a/WordPress/Classes/Models/Blog/Blog+SelfHosted.swift b/WordPress/Classes/Models/Blog/Blog+SelfHosted.swift index 6855b738eb8c..64b422f916a4 100644 --- a/WordPress/Classes/Models/Blog/Blog+SelfHosted.swift +++ b/WordPress/Classes/Models/Blog/Blog+SelfHosted.swift @@ -1,6 +1,7 @@ import Foundation import CryptoKit import WordPressAPI +import WordPressData extension Blog { @@ -17,7 +18,7 @@ extension Blog { static func createRestApiBlog( with details: WpApiApplicationPasswordDetails, - in contextManager: ContextManager, + in contextManager: CoreDataStackSwift, using keychainImplementation: KeychainAccessible = KeychainUtils() ) async throws -> String { try await contextManager.performAndSave { context in diff --git a/WordPress/Classes/Models/BloggingPrompt+CoreDataClass.swift b/WordPress/Classes/Models/BloggingPrompt+CoreDataClass.swift index b107887267a9..94eb00eec2bc 100644 --- a/WordPress/Classes/Models/BloggingPrompt+CoreDataClass.swift +++ b/WordPress/Classes/Models/BloggingPrompt+CoreDataClass.swift @@ -1,25 +1,9 @@ import Foundation import CoreData +import WordPressData import WordPressKit -public class BloggingPrompt: NSManagedObject { - - @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: Self.classNameWithoutNamespaces()) - } - - @nonobjc public class func newObject(in context: NSManagedObjectContext) -> BloggingPrompt? { - return NSEntityDescription.insertNewObject(forEntityName: Self.classNameWithoutNamespaces(), into: context) as? BloggingPrompt - } - - public override func awakeFromInsert() { - self.date = .init(timeIntervalSince1970: 0) - self.displayAvatarURLs = [] - } - - var promptAttribution: BloggingPromptsAttribution? { - BloggingPromptsAttribution(rawValue: attribution.lowercased()) - } +extension BloggingPrompt { /// Convenience method to map properties from `BloggingPromptRemoteObject`. /// @@ -42,6 +26,10 @@ public class BloggingPrompt: NSManagedObject { } } + var promptAttribution: BloggingPromptsAttribution? { + BloggingPromptsAttribution(rawValue: attribution.lowercased()) + } + func textForDisplay() -> String { return text.stringByDecodingXMLCharacters().trim() } diff --git a/WordPress/Classes/Models/BloggingPrompt+CoreDataProperties.swift b/WordPress/Classes/Models/BloggingPrompt+CoreDataProperties.swift index 47fac71d4113..d512e2beaa0b 100644 --- a/WordPress/Classes/Models/BloggingPrompt+CoreDataProperties.swift +++ b/WordPress/Classes/Models/BloggingPrompt+CoreDataProperties.swift @@ -1,31 +1,4 @@ import Foundation import CoreData -extension BloggingPrompt { - /// The unique ID for the prompt, received from the server. - @NSManaged public var promptID: Int32 - - /// The site ID for the prompt. - @NSManaged public var siteID: Int32 - - /// The prompt content to be displayed at entry points. - @NSManaged public var text: String - - /// The attribution source for the prompt. - @NSManaged public var attribution: String - - /// The prompt date. Time information should be ignored. - @NSManaged public var date: Date - - /// Whether the current user has answered the prompt in `siteID`. - @NSManaged public var answered: Bool - - /// The number of users that has answered the prompt. - @NSManaged public var answerCount: Int32 - - /// Contains avatar URLs of some users that have answered the prompt. - @NSManaged public var displayAvatarURLs: [URL] - - /// Contains additional tags that should be appended to the post for this prompt's answer. - @NSManaged public var additionalPostTags: [String]? -} +// FIXME: Delete diff --git a/WordPress/Classes/Models/Domain.swift b/WordPress/Classes/Models/Domain.swift index d7376470e9ad..1e28a7658df2 100644 --- a/WordPress/Classes/Models/Domain.swift +++ b/WordPress/Classes/Models/Domain.swift @@ -1,5 +1,6 @@ import Foundation import CoreData +import WordPressData import WordPressKit public typealias Domain = RemoteDomain diff --git a/WordPress/Classes/Models/Notifications/Actions/NotificationAction.swift b/WordPress/Classes/Models/Notifications/Actions/NotificationAction.swift index 36140a6d1598..d16840db644a 100644 --- a/WordPress/Classes/Models/Notifications/Actions/NotificationAction.swift +++ b/WordPress/Classes/Models/Notifications/Actions/NotificationAction.swift @@ -1,3 +1,5 @@ +import WordPressData + /// Base Notification Action Command. class DefaultNotificationActionCommand: FormattableContentActionCommand { var on: Bool diff --git a/WordPress/Classes/Models/Post.swift b/WordPress/Classes/Models/Post.swift index 549ee6ff321c..c6b87c321a61 100644 --- a/WordPress/Classes/Models/Post.swift +++ b/WordPress/Classes/Models/Post.swift @@ -1,5 +1,6 @@ import Foundation import CoreData +import WordPressData @objc(Post) class Post: AbstractPost { diff --git a/WordPress/Classes/Models/Role.swift b/WordPress/Classes/Models/Role.swift index c414a1b5c7f2..f1752f23a7a5 100644 --- a/WordPress/Classes/Models/Role.swift +++ b/WordPress/Classes/Models/Role.swift @@ -1,5 +1,6 @@ import Foundation import CoreData +import WordPressData public class Role: NSManagedObject { @NSManaged public var name: String! diff --git a/WordPress/Classes/Models/WPAccount.m b/WordPress/Classes/Models/WPAccount.m deleted file mode 100644 index a40ea9fbf49e..000000000000 --- a/WordPress/Classes/Models/WPAccount.m +++ /dev/null @@ -1,195 +0,0 @@ -#import "WPAccount.h" -#import "WordPress-Swift.h" - -@interface WPAccount () - -@property (nonatomic, strong, readwrite) WordPressComRestApi *wordPressComRestApi; -@property (nonatomic, strong, readwrite) NSString *cachedToken; - -@end - -@implementation WPAccount - -@dynamic username; -@dynamic blogs; -@dynamic defaultBlog; -@dynamic primaryBlogID; -@dynamic uuid; -@dynamic dateCreated; -@dynamic email; -@dynamic emailVerified; -@dynamic displayName; -@dynamic userID; -@dynamic avatarURL; -@dynamic settings; -@synthesize wordPressComRestApi = _wordPressComRestApi; -@synthesize cachedToken; - -#pragma mark - NSManagedObject subclass methods - -- (void)prepareForDeletion -{ - // Only do these deletions in the primary context (no parent) - if (self.managedObjectContext.concurrencyType != NSMainQueueConcurrencyType) { - return; - } - - [_wordPressComRestApi invalidateAndCancelTasks]; - _wordPressComRestApi = nil; - self.authToken = nil; -} - -- (void)didTurnIntoFault -{ - [super didTurnIntoFault]; - _wordPressComRestApi = nil; - self.cachedToken = nil; -} - -+ (NSString *)entityName -{ - return @"Account"; -} - -#pragma mark - Custom accessors - -- (void)setUsername:(NSString *)username -{ - NSString *previousUsername = self.username; - - BOOL usernameChanged = ![previousUsername isEqualToString:username]; - NSString *authToken = nil; - - if (usernameChanged) { - authToken = self.authToken; - self.authToken = nil; - } - - [self willChangeValueForKey:@"username"]; - [self setPrimitiveValue:username forKey:@"username"]; - [self didChangeValueForKey:@"username"]; - - if (usernameChanged) { - self.authToken = authToken; - } -} - -- (NSString *)authToken -{ - if (self.cachedToken != nil) { - return self.cachedToken; - } - - NSString *token = [WPAccount tokenForUsername:self.username]; - self.cachedToken = token; - return token; -} - -- (void)setAuthToken:(NSString *)authToken -{ - self.cachedToken = nil; - - if (authToken) { - NSError *error = nil; - [SFHFKeychainUtils storeUsername:self.username - andPassword:authToken - forServiceName:[WPAccount authKeychainServiceName] - accessGroup:nil - updateExisting:YES - error:&error]; - - if (error) { - DDLogError(@"Error while updating WordPressComOAuthKeychainServiceName token: %@", error); - } - - } else { - NSError *error = nil; - [SFHFKeychainUtils deleteItemForUsername:self.username - andServiceName:[WPAccount authKeychainServiceName] - accessGroup:nil - error:&error]; - if (error) { - DDLogError(@"Error while deleting WordPressComOAuthKeychainServiceName token: %@", error); - } - } - - // Make sure to release any RestAPI alloc'ed, since it might have an invalid token - _wordPressComRestApi = nil; -} - -- (BOOL)hasAtomicSite { - for (Blog *blog in self.blogs) { - if ([blog isAtomic]) { - return YES; - } - } - return NO; -} - -#pragma mark - Static methods - -+ (NSString *)tokenForUsername:(NSString *)username -{ - NSError *error = nil; - [WPAccount migrateAuthKeyForUsername:username]; - NSString *authToken = [SFHFKeychainUtils getPasswordForUsername:username - andServiceName:[WPAccount authKeychainServiceName] - accessGroup:nil - error:&error]; - if (error) { - DDLogError(@"Error while retrieving WordPressComOAuthKeychainServiceName token: %@", error); - } - - return authToken; -} - -+ (void)migrateAuthKeyForUsername:(NSString *)username -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - if ([AppConfiguration isJetpack]) { - SharedDataIssueSolver *sharedDataIssueSolver = [SharedDataIssueSolver instance]; - [sharedDataIssueSolver migrateAuthKeyFor:username]; - } - }); -} - -+ (NSString *)authKeychainServiceName -{ - return [AppConstants authKeychainServiceName]; -} - -#pragma mark - API Helpers - -- (WordPressComRestApi *)wordPressComRestApi -{ - if (!_wordPressComRestApi) { - if (self.authToken.length > 0) { - __weak __typeof(self) weakSelf = self; - _wordPressComRestApi = [WordPressComRestApi defaultApiWithOAuthToken:self.authToken - userAgent:[WPUserAgent wordPressUserAgent] - localeKey:[WordPressComRestApi LocaleKeyDefault]]; - [_wordPressComRestApi setInvalidTokenHandler:^{ - [weakSelf setAuthToken:nil]; - [WordPressAuthenticationManager showSigninForWPComFixingAuthToken]; - if (weakSelf.isDefaultWordPressComAccount) { - // At the time of writing, there is an implicit assumption on what the object parameter value means. - // For example, the WordPressAppDelegate.handleDefaultAccountChangedNotification(_:) subscriber inspects the object parameter to decide whether the notification was sent as a result of a login. - // If the object is non-nil, then the method considers the source a login. - // - // The code path in which we are is that of an invalid token, and that's neither a login nor a logout, it's more appropriate to consider it a logout. - // That's because if the token is invalid the app will soon received errors from the API and it's therefore better to force the user to login again. - [[NSNotificationCenter defaultCenter] postNotificationName:WPAccountDefaultWordPressComAccountChangedNotification object:nil]; - } - }]; - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - [WordPressAuthenticationManager showSigninForWPComFixingAuthToken]; - }); - } - } - return _wordPressComRestApi; - -} - -@end diff --git a/WordPress/Classes/Services/CredentialsService.swift b/WordPress/Classes/Services/CredentialsService.swift index 3118b89ca3e9..09c2f86484cb 100644 --- a/WordPress/Classes/Services/CredentialsService.swift +++ b/WordPress/Classes/Services/CredentialsService.swift @@ -1,3 +1,5 @@ +import SFHFKeychainUtils + protocol CredentialsProvider { func getPassword(username: String, service: String) -> String? } diff --git a/WordPress/Classes/Services/JetpackSocialService.swift b/WordPress/Classes/Services/JetpackSocialService.swift index 579c3473683b..bcf8c59cbcc8 100644 --- a/WordPress/Classes/Services/JetpackSocialService.swift +++ b/WordPress/Classes/Services/JetpackSocialService.swift @@ -19,8 +19,11 @@ import WordPressKit /// Init method for Objective-C. /// - @objc init(contextManager: ContextManager) { - self.coreDataStack = contextManager + @objc init(contextManager: CoreDataStack) { + guard let typeCastStack = contextManager as? CoreDataStackSwift else { + fatalError("Expected a CoreDataStackSwift-conforming type even though this initializer is marked @objc.") + } + self.coreDataStack = typeCastStack } init(coreDataStack: CoreDataStackSwift = ContextManager.shared) { diff --git a/WordPress/Classes/Services/NotificationSupportService.swift b/WordPress/Classes/Services/NotificationSupportService.swift index e7b2121b8e7a..6a1f9d61fb1b 100644 --- a/WordPress/Classes/Services/NotificationSupportService.swift +++ b/WordPress/Classes/Services/NotificationSupportService.swift @@ -1,4 +1,5 @@ import Foundation +import SFHFKeychainUtils @objc open class NotificationSupportService: NSObject { diff --git a/WordPress/Classes/Services/NullBlogPropertySanitizer.swift b/WordPress/Classes/Services/NullBlogPropertySanitizer.swift index 6472f379833c..930665cf1ee6 100644 --- a/WordPress/Classes/Services/NullBlogPropertySanitizer.swift +++ b/WordPress/Classes/Services/NullBlogPropertySanitizer.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressData /// Delete invalid rows in the database whose required blog properties are NULL /// diff --git a/WordPress/Classes/Services/ShareExtensionService.swift b/WordPress/Classes/Services/ShareExtensionService.swift index cd02012c124e..01d0b2042b9a 100644 --- a/WordPress/Classes/Services/ShareExtensionService.swift +++ b/WordPress/Classes/Services/ShareExtensionService.swift @@ -1,4 +1,5 @@ import Foundation +import SFHFKeychainUtils @objc open class ShareExtensionService: NSObject { diff --git a/WordPress/Classes/Services/SharingService.swift b/WordPress/Classes/Services/SharingService.swift index 35be9f1ce855..2cd397f2636d 100644 --- a/WordPress/Classes/Services/SharingService.swift +++ b/WordPress/Classes/Services/SharingService.swift @@ -12,11 +12,12 @@ import WordPressKit private let coreDataStack: CoreDataStackSwift /// The initialiser for Objective-C code. - /// - /// Using `ContextManager` as the argument becuase `CoreDataStackSwift` is not accessible from Objective-C code. @objc - init(contextManager: ContextManager) { - self.coreDataStack = contextManager + init(contextManager: CoreDataStack) { + guard let typeCastStack = contextManager as? CoreDataStackSwift else { + fatalError("Expected a CoreDataStackSwift-conforming type even though this initializer is marked @objc.") + } + self.coreDataStack = typeCastStack } init(coreDataStack: CoreDataStackSwift) { diff --git a/WordPress/Classes/Stores/StatsWidgetsStore.swift b/WordPress/Classes/Stores/StatsWidgetsStore.swift index 95d82a9066b5..fa9db9a0e554 100644 --- a/WordPress/Classes/Stores/StatsWidgetsStore.swift +++ b/WordPress/Classes/Stores/StatsWidgetsStore.swift @@ -1,4 +1,5 @@ import JetpackStatsWidgetsCore +import SFHFKeychainUtils import WidgetKit class StatsWidgetsStore { diff --git a/WordPress/Classes/System/Root View/SplitViewRootPresenter+Welcome.swift b/WordPress/Classes/System/Root View/SplitViewRootPresenter+Welcome.swift index 9589f6e4726e..f34b3cdcdf5c 100644 --- a/WordPress/Classes/System/Root View/SplitViewRootPresenter+Welcome.swift +++ b/WordPress/Classes/System/Root View/SplitViewRootPresenter+Welcome.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import SwiftUI +import WordPressData class WelcomeSplitViewContent: SplitViewDisplayable { let supplementary: UINavigationController @@ -9,7 +10,7 @@ class WelcomeSplitViewContent: SplitViewDisplayable { init(addSite: @escaping (AddSiteMenuViewModel.Selection) -> Void) { supplementary = UINavigationController(rootViewController: UnifiedPrologueViewController()) - let addSiteViewModel = AddSiteMenuViewModel(context: .shared, onSelection: addSite) + let addSiteViewModel = AddSiteMenuViewModel(context: ContextManager.shared, onSelection: addSite) let noSitesViewModel = NoSitesViewModel(appUIType: JetpackFeaturesRemovalCoordinator.currentAppUIType, account: nil) let noSiteView = NoSitesView(addSiteViewModel: addSiteViewModel, viewModel: noSitesViewModel) let noSitesVC = UIHostingController(rootView: noSiteView) diff --git a/WordPress/Classes/System/UITesting/UITestConfigurator.swift b/WordPress/Classes/System/UITesting/UITestConfigurator.swift index 5651cf6a8188..cc2d22ec1d12 100644 --- a/WordPress/Classes/System/UITesting/UITestConfigurator.swift +++ b/WordPress/Classes/System/UITesting/UITestConfigurator.swift @@ -37,7 +37,9 @@ struct UITestConfigurator { private static func resetEverything() { // Remove CoreData DB - ContextManager.shared.resetEverything() + // + // We can afford to force cast here because we are in a test-specific code + (ContextManager.shared as! ContextManager).resetEverything() // Clear user defaults. for key in UserDefaults.standard.dictionaryRepresentation().keys { diff --git a/WordPress/Classes/System/WordPress-Bridging-Header.h b/WordPress/Classes/System/WordPress-Bridging-Header.h index 401dd2930737..c8c2a0a0d5fd 100644 --- a/WordPress/Classes/System/WordPress-Bridging-Header.h +++ b/WordPress/Classes/System/WordPress-Bridging-Header.h @@ -1,5 +1,3 @@ -#import "SFHFKeychainUtils.h" - // WordPress Module #import "ActivityLogViewController.h" @@ -55,7 +53,6 @@ #import "SettingTableViewCell.h" #import "SettingsTextViewController.h" #import "SharingViewController.h" -#import "SFHFKeychainUtils.h" #import "SiteSettingsViewController.h" #import "SourcePostAttribution.h" #import "StatsViewController.h" diff --git a/WordPress/Classes/System/WordPressAppDelegate.swift b/WordPress/Classes/System/WordPressAppDelegate.swift index c060b1448aa2..ecbd694336f4 100644 --- a/WordPress/Classes/System/WordPressAppDelegate.swift +++ b/WordPress/Classes/System/WordPressAppDelegate.swift @@ -1,3 +1,4 @@ +import SFHFKeychainUtils import UIKit import CocoaLumberjackSwift import Reachability diff --git a/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift b/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift index bd61d575de06..bd04075eda9d 100644 --- a/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift +++ b/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift @@ -330,14 +330,14 @@ class WeeklyRoundupBackgroundTask: BackgroundTask { private let eventTracker: NotificationEventTracker let runDateComponents: DateComponents let notificationScheduler: WeeklyRoundupNotificationScheduler - let coreDataStack: ContextManager + let coreDataStack: CoreDataStackSwift init( eventTracker: NotificationEventTracker = NotificationEventTracker(), runDateComponents: DateComponents? = nil, staticNotificationDateComponents: DateComponents? = nil, store: Store = Store(), - coreDataStack: ContextManager = .shared + coreDataStack: CoreDataStackSwift = ContextManager.shared ) { self.coreDataStack = coreDataStack self.eventTracker = eventTracker diff --git a/WordPress/Classes/Utility/Blogging Prompts/PromptRemindersScheduler.swift b/WordPress/Classes/Utility/Blogging Prompts/PromptRemindersScheduler.swift index 43cc737d739c..86642b44a304 100644 --- a/WordPress/Classes/Utility/Blogging Prompts/PromptRemindersScheduler.swift +++ b/WordPress/Classes/Utility/Blogging Prompts/PromptRemindersScheduler.swift @@ -1,5 +1,6 @@ import Foundation import UserNotifications +import WordPressData /// Encapsulates the local notification scheduling logic for Blogging Prompts. /// diff --git a/WordPress/Classes/Utility/FormattableContent/Actions/FormattableContentActionCommand.swift b/WordPress/Classes/Utility/FormattableContent/Actions/FormattableContentActionCommand.swift index 0fab1a0c1713..a02c4f955194 100644 --- a/WordPress/Classes/Utility/FormattableContent/Actions/FormattableContentActionCommand.swift +++ b/WordPress/Classes/Utility/FormattableContent/Actions/FormattableContentActionCommand.swift @@ -1,3 +1,5 @@ +import UIKit + /// Abstracts the logic behind contextual actions that can be applied to FormattableContent. /// protocol FormattableContentActionCommand: CustomStringConvertible { diff --git a/WordPress/Classes/Utility/FormattableContent/FormattableContentStyles.swift b/WordPress/Classes/Utility/FormattableContent/FormattableContentStyles.swift index c9a9fae1bd37..694f22abcab6 100644 --- a/WordPress/Classes/Utility/FormattableContent/FormattableContentStyles.swift +++ b/WordPress/Classes/Utility/FormattableContent/FormattableContentStyles.swift @@ -1,3 +1,4 @@ +import UIKit /// Styles definition to be applied to a single FormattableContent entity. public protocol FormattableContentStyles { diff --git a/WordPress/Classes/Utility/KeychainUtils.swift b/WordPress/Classes/Utility/KeychainUtils.swift index ee71c72dc7c3..95a84b60e0d3 100644 --- a/WordPress/Classes/Utility/KeychainUtils.swift +++ b/WordPress/Classes/Utility/KeychainUtils.swift @@ -1,3 +1,5 @@ +import SFHFKeychainUtils + @objcMembers class KeychainUtils: NSObject { diff --git a/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift b/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift index ffb960159f95..3142a83cc955 100644 --- a/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift +++ b/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift @@ -2,6 +2,7 @@ import UIKit import Combine import AutomatticTracks import AutomatticEncryptedLogs +import WordPressData /// A wrapper around the logging stack – provides shared initialization and configuration for Tracks Crash and Event Logging struct WPLoggingStack { @@ -38,9 +39,9 @@ struct WPLoggingStack { } struct WPCrashLoggingDataProvider: CrashLoggingDataProvider { - private let contextManager: ContextManager + private let contextManager: CoreDataStackSwift - init(contextManager: ContextManager = .shared) { + init(contextManager: CoreDataStackSwift = ContextManager.shared) { self.contextManager = contextManager } diff --git a/WordPress/Classes/Utility/Migration/ContentMigrationCoordinator.swift b/WordPress/Classes/Utility/Migration/ContentMigrationCoordinator.swift index c615cbaf6773..9e7a9dafdde0 100644 --- a/WordPress/Classes/Utility/Migration/ContentMigrationCoordinator.swift +++ b/WordPress/Classes/Utility/Migration/ContentMigrationCoordinator.swift @@ -1,3 +1,5 @@ +import WordPressData + /// Encapsulates logic related to content migration from WordPress to Jetpack. /// @objc class ContentMigrationCoordinator: NSObject { diff --git a/WordPress/Classes/Utility/Migrations/10-11/BlogToAccount.m b/WordPress/Classes/Utility/Migrations/10-11/BlogToAccount.m index e5f484167d85..b48ad63d71c6 100644 --- a/WordPress/Classes/Utility/Migrations/10-11/BlogToAccount.m +++ b/WordPress/Classes/Utility/Migrations/10-11/BlogToAccount.m @@ -5,6 +5,7 @@ @import NSObject_SafeExpectations; @import NSURL_IDN; +@import SFHFKeychainUtils; @implementation BlogToAccount { NSString *_defaultWpcomUsername; diff --git a/WordPress/Classes/Utility/Migrations/10-11/BlogToJetpackAccount.m b/WordPress/Classes/Utility/Migrations/10-11/BlogToJetpackAccount.m index eae7ffca000a..af29a84cf41d 100644 --- a/WordPress/Classes/Utility/Migrations/10-11/BlogToJetpackAccount.m +++ b/WordPress/Classes/Utility/Migrations/10-11/BlogToJetpackAccount.m @@ -3,6 +3,7 @@ #import "WordPress-Swift.h" @import NSObject_SafeExpectations; +@import SFHFKeychainUtils; static NSString * const BlogJetpackKeychainPrefix = @"jetpackblog-"; diff --git a/WordPress/Classes/Utility/Migrations/32-33/BlogToBlog32to33.swift b/WordPress/Classes/Utility/Migrations/32-33/BlogToBlog32to33.swift index cecd4ba7ccde..35e5b98d19c1 100644 --- a/WordPress/Classes/Utility/Migrations/32-33/BlogToBlog32to33.swift +++ b/WordPress/Classes/Utility/Migrations/32-33/BlogToBlog32to33.swift @@ -1,3 +1,4 @@ +import SFHFKeychainUtils import UIKit class BlogToBlog32to33: NSEntityMigrationPolicy { diff --git a/WordPress/Classes/ViewRelated/Aztec/Extensions/FormatBarItemProviders.swift b/WordPress/Classes/ViewRelated/Aztec/Extensions/FormatBarItemProviders.swift index 064cd2bff840..1a92f1b928af 100644 --- a/WordPress/Classes/ViewRelated/Aztec/Extensions/FormatBarItemProviders.swift +++ b/WordPress/Classes/ViewRelated/Aztec/Extensions/FormatBarItemProviders.swift @@ -1,6 +1,7 @@ import Foundation import Gridicons import Aztec +import UIKit protocol FormatBarItemProvider { var iconImage: UIImage { get } diff --git a/WordPress/Classes/ViewRelated/Aztec/Extensions/TextList+WordPress.swift b/WordPress/Classes/ViewRelated/Aztec/Extensions/TextList+WordPress.swift index 645951d19dee..5b273cba93c4 100644 --- a/WordPress/Classes/ViewRelated/Aztec/Extensions/TextList+WordPress.swift +++ b/WordPress/Classes/ViewRelated/Aztec/Extensions/TextList+WordPress.swift @@ -1,5 +1,6 @@ import Foundation import Aztec +import UIKit // MARK: - TextList.Style // diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift index e7f06e08bf67..25043aa3cad8 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift @@ -1,5 +1,6 @@ import UIKit import SafariServices +import WordPressData import WordPressShared import WordPressFlux diff --git a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptCoordinator.swift b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptCoordinator.swift index e9e6ee74f50d..217e93f218ea 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptCoordinator.swift @@ -1,4 +1,5 @@ import UIKit +import WordPressData /// Helps manage the flow related to Blogging Prompts. /// diff --git a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptTableViewCell.swift b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptTableViewCell.swift index 663831890eb6..a8d6b5159985 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptTableViewCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptTableViewCell.swift @@ -1,3 +1,5 @@ +import WordPressData + class BloggingPromptTableViewCell: UITableViewCell, NibReusable { // MARK: - Properties diff --git a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptsViewController.swift b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptsViewController.swift index ece125b36186..db518db2178c 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptsViewController.swift @@ -1,4 +1,5 @@ import UIKit +import WordPressData class BloggingPromptsViewController: UIViewController, NoResultsViewHost { diff --git a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/RootViewCoordinator+BloggingPrompt.swift b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/RootViewCoordinator+BloggingPrompt.swift index ba0292e40518..d550bbfc5fd3 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/RootViewCoordinator+BloggingPrompt.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/RootViewCoordinator+BloggingPrompt.swift @@ -1,3 +1,4 @@ +import WordPressData /// Encapsulates logic related to Blogging Prompts in RootViewCoordinator. /// diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/AddSiteController.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/AddSiteController.swift index 0772d442d9fd..e7d2f041f6a4 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/AddSiteController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/AddSiteController.swift @@ -1,6 +1,7 @@ import UIKit import SwiftUI import WordPressAuthenticator +import WordPressData /// Manages the site creation flows. struct AddSiteController { @@ -80,7 +81,7 @@ struct AddSiteMenuViewModel { } } - init(context: ContextManager = .shared, onSelection: @escaping (Selection) -> Void) { + init(context: CoreDataStackSwift = ContextManager.shared, onSelection: @escaping (Selection) -> Void) { let defaultAccount = try? WPAccount.lookupDefaultWordPressComAccount(in: context.mainContext) let canAddSelfHostedSite = AppConfiguration.showAddSelfHostedSiteButton diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift index 8ed9d257aa22..540d6d30aa19 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift @@ -1,5 +1,6 @@ import CoreData import SwiftUI +import WordPressData import WordPressShared final class BlogListViewModel: NSObject, ObservableObject { @@ -15,7 +16,7 @@ final class BlogListViewModel: NSObject, ObservableObject { private let configuration: BlogListConfiguration private var rawSites: [Blog] = [] private let fetchedResultsController: NSFetchedResultsController - private let contextManager: ContextManager + private let contextManager: CoreDataStackSwift private let blogService: BlogService private let eventTracker: EventTracker private let recentSitesService: RecentSitesService @@ -24,7 +25,7 @@ final class BlogListViewModel: NSObject, ObservableObject { var onAddSiteTapped: (AddSiteMenuViewModel.Selection) -> Void = { _ in } init(configuration: BlogListConfiguration = .defaultConfig, - contextManager: ContextManager = ContextManager.shared, + contextManager: CoreDataStackSwift = ContextManager.shared, recentSitesService: RecentSitesService = RecentSitesService(), eventTracker: EventTracker = DefaultEventTracker()) { self.configuration = configuration diff --git a/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopoverViewModel.swift b/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopoverViewModel.swift index c7b15044b923..e7b45f98db08 100644 --- a/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopoverViewModel.swift +++ b/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopoverViewModel.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import WordPressData import WordPressUI class CompliancePopoverViewModel: ObservableObject { @@ -13,12 +14,12 @@ class CompliancePopoverViewModel: ObservableObject { private let analyticsTracker: PrivacySettingsAnalyticsTracking private let defaults: UserDefaults - private let contextManager: ContextManager + private let contextManager: CoreDataStackSwift // MARK: - Init init(defaults: UserDefaults, - contextManager: ContextManager, + contextManager: CoreDataStackSwift, analyticsTracker: PrivacySettingsAnalyticsTracking = PrivacySettingsAnalyticsTracker()) { self.defaults = defaults self.analyticsTracker = analyticsTracker diff --git a/WordPress/Classes/ViewRelated/Feature Introduction/Blogging Prompts/BloggingPromptsIntroductionPresenter.swift b/WordPress/Classes/ViewRelated/Feature Introduction/Blogging Prompts/BloggingPromptsIntroductionPresenter.swift index cdc4a4e17ce8..033abd321c6d 100644 --- a/WordPress/Classes/ViewRelated/Feature Introduction/Blogging Prompts/BloggingPromptsIntroductionPresenter.swift +++ b/WordPress/Classes/ViewRelated/Feature Introduction/Blogging Prompts/BloggingPromptsIntroductionPresenter.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import WordPressData /// Presents the BloggingPromptsFeatureIntroduction with actionable buttons /// and directs the flow according to which action button is tapped. diff --git a/WordPress/Classes/ViewRelated/NUX/Helpers/WordPressAuthenticationManager.swift b/WordPress/Classes/ViewRelated/NUX/Helpers/WordPressAuthenticationManager.swift index 8790d85414b7..122567801162 100644 --- a/WordPress/Classes/ViewRelated/NUX/Helpers/WordPressAuthenticationManager.swift +++ b/WordPress/Classes/ViewRelated/NUX/Helpers/WordPressAuthenticationManager.swift @@ -1,4 +1,5 @@ import Foundation +import SFHFKeychainUtils import WordPressAuthenticator import Gridicons import UIKit diff --git a/WordPress/Classes/ViewRelated/Notifications/FormattableContent/NotificationTextContent.swift b/WordPress/Classes/ViewRelated/Notifications/FormattableContent/NotificationTextContent.swift index 43661fe3e678..3a130038ece0 100644 --- a/WordPress/Classes/ViewRelated/Notifications/FormattableContent/NotificationTextContent.swift +++ b/WordPress/Classes/ViewRelated/Notifications/FormattableContent/NotificationTextContent.swift @@ -1,5 +1,5 @@ - import Foundation +import UIKit extension FormattableContentKind { static let image = FormattableContentKind("image") diff --git a/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift b/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift index 59807817f4d2..3f60785fcec4 100644 --- a/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/EditPostViewController.swift @@ -1,4 +1,5 @@ import UIKit +import WordPressData class EditPostViewController: UIViewController { diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/BloggingPromptsHeaderView.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/BloggingPromptsHeaderView.swift index 3b8858a80a65..f6c2246ffee8 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/BloggingPromptsHeaderView.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/BloggingPromptsHeaderView.swift @@ -1,4 +1,5 @@ import UIKit +import WordPressData class BloggingPromptsHeaderView: UIView, NibLoadable { @IBOutlet private weak var containerStackView: UIStackView! diff --git a/WordPress/Classes/ViewRelated/System/Floating Create Button/CreateButtonCoordinator.swift b/WordPress/Classes/ViewRelated/System/Floating Create Button/CreateButtonCoordinator.swift index aea4b03d2c22..db61a7dfcc12 100644 --- a/WordPress/Classes/ViewRelated/System/Floating Create Button/CreateButtonCoordinator.swift +++ b/WordPress/Classes/ViewRelated/System/Floating Create Button/CreateButtonCoordinator.swift @@ -1,4 +1,5 @@ import Gridicons +import WordPressData import WordPressFlux import WordPressUI diff --git a/WordPress/JetpackStatsWidgets/Remote service/StatsWidgetsService.swift b/WordPress/JetpackStatsWidgets/Remote service/StatsWidgetsService.swift index 7f4f1bef5b53..70f42c254808 100644 --- a/WordPress/JetpackStatsWidgets/Remote service/StatsWidgetsService.swift +++ b/WordPress/JetpackStatsWidgets/Remote service/StatsWidgetsService.swift @@ -1,3 +1,4 @@ +import SFHFKeychainUtils import WordPressKit import JetpackStatsWidgetsCore import WordPressShared diff --git a/WordPress/JetpackStatsWidgets/Supporting Files/JetpackStatsWidgets-Bridging-Header.h b/WordPress/JetpackStatsWidgets/Supporting Files/JetpackStatsWidgets-Bridging-Header.h index ff4dac2f06fc..4bda21fed193 100644 --- a/WordPress/JetpackStatsWidgets/Supporting Files/JetpackStatsWidgets-Bridging-Header.h +++ b/WordPress/JetpackStatsWidgets/Supporting Files/JetpackStatsWidgets-Bridging-Header.h @@ -1,2 +1 @@ -#import "SFHFKeychainUtils.h" #import "Constants.h" diff --git a/WordPress/Shared/SharePost.swift b/WordPress/Shared/SharePost.swift index b5721ca3b707..17084297ccff 100644 --- a/WordPress/Shared/SharePost.swift +++ b/WordPress/Shared/SharePost.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import MobileCoreServices /// A simple shared model to represent a Site diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 16ed3070d1b2..3262be4d13d1 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -1019,7 +1019,6 @@ 93CD939319099BE70049096E /* authtoken.json in Resources */ = {isa = PBXBuildFile; fileRef = 93CD939219099BE70049096E /* authtoken.json */; }; 93D86B981C691E71003D8E3E /* LocalCoreDataServiceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 59A9AB391B4C3ECD00A433DC /* LocalCoreDataServiceTests.m */; }; 93E5285619A77BAC003A1A9C /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93E5283B19A7741A003A1A9C /* NotificationCenter.framework */; }; - 93EF094C19ED533500C89770 /* ContextManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E9050319E6F242005513C9 /* ContextManagerTests.swift */; }; 93F2E53E1E9E5A010050D489 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E131CB5116CACA6B004B0314 /* CoreText.framework */; }; 93F2E5401E9E5A180050D489 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 93F2E53F1E9E5A180050D489 /* libsqlite3.tbd */; }; 93F2E5421E9E5A350050D489 /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93F2E5411E9E5A350050D489 /* QuickLook.framework */; }; @@ -1059,7 +1058,6 @@ B5AEEC7C1ACACFDA008BF2A4 /* notifications-new-follower.json in Resources */ = {isa = PBXBuildFile; fileRef = B5AEEC771ACACFDA008BF2A4 /* notifications-new-follower.json */; }; B5AEEC7D1ACACFDA008BF2A4 /* notifications-replied-comment.json in Resources */ = {isa = PBXBuildFile; fileRef = B5AEEC781ACACFDA008BF2A4 /* notifications-replied-comment.json */; }; B5DA8A5F20ADAA1D00D5BDE1 /* plugin-directory-jetpack.json in Resources */ = {isa = PBXBuildFile; fileRef = B5DA8A5E20ADAA1C00D5BDE1 /* plugin-directory-jetpack.json */; }; - B5ECA6CD1DBAAD510062D7E0 /* CoreDataHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5ECA6CC1DBAAD510062D7E0 /* CoreDataHelperTests.swift */; }; B5EFB1C91B333C5A007608A3 /* NotificationSettingsServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EFB1C81B333C5A007608A3 /* NotificationSettingsServiceTests.swift */; }; B5EFB1D11B33630C007608A3 /* notifications-settings.json in Resources */ = {isa = PBXBuildFile; fileRef = B5EFB1D01B33630C007608A3 /* notifications-settings.json */; }; B5FA22831C99F6180016CA7C /* Tracks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FA22821C99F6180016CA7C /* Tracks.swift */; }; @@ -2787,7 +2785,6 @@ 93D86B941C63EC31003D8E3E /* en-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-CA"; path = "en-CA.lproj/Localizable.strings"; sourceTree = ""; }; 93E5283B19A7741A003A1A9C /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; 93E9050219E6F240005513C9 /* WordPressTest-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WordPressTest-Bridging-Header.h"; sourceTree = ""; }; - 93E9050319E6F242005513C9 /* ContextManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextManagerTests.swift; sourceTree = ""; }; 93F2E53F1E9E5A180050D489 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; 93F2E5411E9E5A350050D489 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; }; 93F2E5431E9E5A570050D489 /* CoreSpotlight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreSpotlight.framework; path = System/Library/Frameworks/CoreSpotlight.framework; sourceTree = SDKROOT; }; @@ -2841,7 +2838,6 @@ B5AEEC771ACACFDA008BF2A4 /* notifications-new-follower.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "notifications-new-follower.json"; sourceTree = ""; }; B5AEEC781ACACFDA008BF2A4 /* notifications-replied-comment.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "notifications-replied-comment.json"; sourceTree = ""; }; B5DA8A5E20ADAA1C00D5BDE1 /* plugin-directory-jetpack.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "plugin-directory-jetpack.json"; sourceTree = ""; }; - B5ECA6CC1DBAAD510062D7E0 /* CoreDataHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataHelperTests.swift; sourceTree = ""; }; B5EFB1C81B333C5A007608A3 /* NotificationSettingsServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationSettingsServiceTests.swift; sourceTree = ""; }; B5EFB1D01B33630C007608A3 /* notifications-settings.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "notifications-settings.json"; sourceTree = ""; }; B5FA22821C99F6180016CA7C /* Tracks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Tracks.swift; path = WordPressShareExtension/Tracks.swift; sourceTree = SOURCE_ROOT; wrapsLines = 0; }; @@ -3297,9 +3293,6 @@ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 24CE68AE2CD3375300C7B37D /* Exceptions for "Classes" folder in "WordPress" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - additionalCompilerFlagsByRelativePath = { - Utility/SFHFKeychainUtils.m = "-fno-objc-arc"; - }; membershipExceptions = ( "Utility/Migrations/20-21/SafeReaderTopicToReaderTopic.m", "ViewRelated/Me/All Domains/Coordinators/AllDomainsAddDomainCoordinator.swift", @@ -3314,9 +3307,6 @@ }; 24CE68AF2CD3375300C7B37D /* Exceptions for "Classes" folder in "WordPressShareExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - additionalCompilerFlagsByRelativePath = { - Utility/SFHFKeychainUtils.m = "-fno-objc-arc"; - }; membershipExceptions = ( "Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift", "Extensions/Colors and Styles/WPStyleGuide+Gridicon.swift", @@ -3337,7 +3327,6 @@ Utility/BuildInformation/FeatureFlagRolloutStore.swift, Utility/KeychainUtils.swift, Utility/Logging/CocoaLumberjack.swift, - Utility/SFHFKeychainUtils.m, ViewRelated/Aztec/Extensions/FormatBarItemProviders.swift, "ViewRelated/Aztec/Extensions/Header+WordPress.swift", "ViewRelated/Aztec/Extensions/TextList+WordPress.swift", @@ -3351,9 +3340,6 @@ }; 24CE68B02CD3375300C7B37D /* Exceptions for "Classes" folder in "WordPressDraftActionExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - additionalCompilerFlagsByRelativePath = { - Utility/SFHFKeychainUtils.m = "-fno-objc-arc"; - }; membershipExceptions = ( "Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift", "Extensions/Colors and Styles/WPStyleGuide+Gridicon.swift", @@ -3374,7 +3360,6 @@ Utility/BuildInformation/FeatureFlagRolloutStore.swift, Utility/KeychainUtils.swift, Utility/Logging/CocoaLumberjack.swift, - Utility/SFHFKeychainUtils.m, ViewRelated/Aztec/Extensions/FormatBarItemProviders.swift, "ViewRelated/Aztec/Extensions/Header+WordPress.swift", "ViewRelated/Aztec/Extensions/TextList+WordPress.swift", @@ -3388,9 +3373,6 @@ }; 24CE68B22CD3375300C7B37D /* Exceptions for "Classes" folder in "WordPressNotificationServiceExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - additionalCompilerFlagsByRelativePath = { - Utility/SFHFKeychainUtils.m = "-fno-objc-arc"; - }; membershipExceptions = ( "Extensions/String+Helpers.swift", "Extensions/UIImage+Extensions.swift", @@ -3416,7 +3398,6 @@ Utility/FormattableContent/FormattableRangesFactory.swift, Utility/FormattableContent/FormattableTextContent.swift, Utility/KeychainUtils.swift, - Utility/SFHFKeychainUtils.m, ViewRelated/Notifications/FormattableContent/FormattableCommentContent.swift, ViewRelated/Notifications/FormattableContent/FormattableUserContent.swift, ViewRelated/Notifications/FormattableContent/Notifiable.swift, @@ -3446,9 +3427,6 @@ }; 24CE68B52CD3375300C7B37D /* Exceptions for "Classes" folder in "Jetpack" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - additionalCompilerFlagsByRelativePath = { - Utility/SFHFKeychainUtils.m = "-fno-objc-arc"; - }; membershipExceptions = ( "Utility/App Configuration/AppConfiguration.swift", "Utility/App Configuration/AppConstants.swift", @@ -3460,9 +3438,6 @@ }; 24CE68B62CD3375300C7B37D /* Exceptions for "Classes" folder in "JetpackShareExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - additionalCompilerFlagsByRelativePath = { - Utility/SFHFKeychainUtils.m = "-fno-objc-arc"; - }; membershipExceptions = ( "Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift", "Extensions/Colors and Styles/WPStyleGuide+Gridicon.swift", @@ -3481,7 +3456,6 @@ Utility/BuildInformation/FeatureFlagRolloutStore.swift, Utility/KeychainUtils.swift, Utility/Logging/CocoaLumberjack.swift, - Utility/SFHFKeychainUtils.m, ViewRelated/Aztec/Extensions/FormatBarItemProviders.swift, "ViewRelated/Aztec/Extensions/Header+WordPress.swift", "ViewRelated/Aztec/Extensions/TextList+WordPress.swift", @@ -3495,9 +3469,6 @@ }; 24CE68B72CD3375300C7B37D /* Exceptions for "Classes" folder in "JetpackDraftActionExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - additionalCompilerFlagsByRelativePath = { - Utility/SFHFKeychainUtils.m = "-fno-objc-arc"; - }; membershipExceptions = ( "Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift", "Extensions/Colors and Styles/WPStyleGuide+Gridicon.swift", @@ -3516,7 +3487,6 @@ Utility/BuildInformation/FeatureFlagRolloutStore.swift, Utility/KeychainUtils.swift, Utility/Logging/CocoaLumberjack.swift, - Utility/SFHFKeychainUtils.m, ViewRelated/Aztec/Extensions/FormatBarItemProviders.swift, "ViewRelated/Aztec/Extensions/Header+WordPress.swift", "ViewRelated/Aztec/Extensions/TextList+WordPress.swift", @@ -3530,9 +3500,6 @@ }; 24CE68B82CD3375300C7B37D /* Exceptions for "Classes" folder in "JetpackNotificationServiceExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - additionalCompilerFlagsByRelativePath = { - Utility/SFHFKeychainUtils.m = "-fno-objc-arc"; - }; membershipExceptions = ( "Extensions/String+Helpers.swift", "Extensions/UIImage+Extensions.swift", @@ -3556,7 +3523,6 @@ Utility/FormattableContent/FormattableRangesFactory.swift, Utility/FormattableContent/FormattableTextContent.swift, Utility/KeychainUtils.swift, - Utility/SFHFKeychainUtils.m, ViewRelated/Notifications/FormattableContent/FormattableCommentContent.swift, ViewRelated/Notifications/FormattableContent/FormattableUserContent.swift, ViewRelated/Notifications/FormattableContent/Notifiable.swift, @@ -3571,9 +3537,6 @@ }; 24CE68B92CD3375300C7B37D /* Exceptions for "Classes" folder in "JetpackStatsWidgets" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - additionalCompilerFlagsByRelativePath = { - Utility/SFHFKeychainUtils.m = "-fno-objc-arc"; - }; membershipExceptions = ( Services/KeyValueDatabase.swift, System/Constants/Constants.m, @@ -3586,7 +3549,6 @@ Utility/BuildInformation/FeatureFlagRolloutStore.swift, Utility/KeychainUtils.swift, Utility/Logging/CocoaLumberjack.swift, - Utility/SFHFKeychainUtils.m, "ViewRelated/Stats/Extensions/Double+Stats.swift", "ViewRelated/Stats/Extensions/NumberFormatter+Stats.swift", ); @@ -3594,13 +3556,9 @@ }; 24CE68BA2CD3375300C7B37D /* Exceptions for "Classes" folder in "JetpackIntents" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - additionalCompilerFlagsByRelativePath = { - Utility/SFHFKeychainUtils.m = "-fno-objc-arc"; - }; membershipExceptions = ( System/Constants/Constants.m, Utility/Logging/CocoaLumberjack.swift, - Utility/SFHFKeychainUtils.m, ); target = 0107E13828FE9DB200DE87DB /* JetpackIntents */; }; @@ -6062,8 +6020,6 @@ B5AEEC731ACACF3B008BF2A4 /* Core Data */ = { isa = PBXGroup; children = ( - 93E9050319E6F242005513C9 /* ContextManagerTests.swift */, - B5ECA6CC1DBAAD510062D7E0 /* CoreDataHelperTests.swift */, 4A2C73E32A943DEA00ACE79E /* TaggedManagedObjectIDTests.swift */, 931D26FF19EDAE8600114F17 /* CoreDataMigrationTests.m */, FE320CC4294705990046899B /* ReaderPostBackupTests.swift */, @@ -9677,7 +9633,6 @@ 2481B1E8260D4EAC00AE59DB /* WPAccount+LookupTests.swift in Sources */, E10F3DA11E5C2CE0008FAADA /* PostListFilterTests.swift in Sources */, DCF892D0282FA42A00BB71E1 /* SiteStatsImmuTableRowsTests.swift in Sources */, - 93EF094C19ED533500C89770 /* ContextManagerTests.swift in Sources */, F17A2A2023BFBD84001E96AC /* UIView+ExistingConstraints.swift in Sources */, B5552D821CD1061F00B26DF6 /* StringExtensionsTests.swift in Sources */, 3F759FBA2A2DA93B0039A845 /* WPAccount+Fixture.swift in Sources */, @@ -9730,7 +9685,6 @@ 73178C3321BEE94700E37C9A /* SiteAssemblyServiceTests.swift in Sources */, D88A6492208D7A0A008AE9BC /* MockStockPhotosService.swift in Sources */, F5D0A65223CCD3B600B20D27 /* PreviewWebKitViewControllerTests.swift in Sources */, - B5ECA6CD1DBAAD510062D7E0 /* CoreDataHelperTests.swift in Sources */, 931D270019EDAE8600114F17 /* CoreDataMigrationTests.m in Sources */, 80EF9284280CFEB60064A971 /* DashboardPostsSyncManagerTests.swift in Sources */, 7EC9FE0B22C627DB00C5A888 /* PostEditorAnalyticsSessionTests.swift in Sources */, diff --git a/WordPress/WordPressDraftActionExtension/WordPressDraftActionExtension-Bridging-Header.h b/WordPress/WordPressDraftActionExtension/WordPressDraftActionExtension-Bridging-Header.h index 670e97f7e3e3..285c18bfd1ae 100644 --- a/WordPress/WordPressDraftActionExtension/WordPressDraftActionExtension-Bridging-Header.h +++ b/WordPress/WordPressDraftActionExtension/WordPressDraftActionExtension-Bridging-Header.h @@ -2,5 +2,4 @@ #import "Constants.h" // Shared Helpers -#import "SFHFKeychainUtils.h" #import "TextBundleWrapper.h" diff --git a/WordPress/WordPressNotificationServiceExtension/Sources/NotificationService.swift b/WordPress/WordPressNotificationServiceExtension/Sources/NotificationService.swift index 43bbdda47c9b..3b563afdecc4 100644 --- a/WordPress/WordPressNotificationServiceExtension/Sources/NotificationService.swift +++ b/WordPress/WordPressNotificationServiceExtension/Sources/NotificationService.swift @@ -1,3 +1,4 @@ +import SFHFKeychainUtils import UserNotifications import WordPressKit diff --git a/WordPress/WordPressNotificationServiceExtension/Sources/WordPressNotificationServiceExtension-Bridging-Header.h b/WordPress/WordPressNotificationServiceExtension/Sources/WordPressNotificationServiceExtension-Bridging-Header.h index 4a84db88f642..308d6e64ad55 100644 --- a/WordPress/WordPressNotificationServiceExtension/Sources/WordPressNotificationServiceExtension-Bridging-Header.h +++ b/WordPress/WordPressNotificationServiceExtension/Sources/WordPressNotificationServiceExtension-Bridging-Header.h @@ -1,5 +1,2 @@ // Main App Helpers #import "Constants.h" - -// Shared Helpers -#import "SFHFKeychainUtils.h" diff --git a/WordPress/WordPressShareExtension/UINavigationController+Extensions.swift b/WordPress/WordPressShareExtension/UINavigationController+Extensions.swift index ad35d7dd410b..6abd384fa21f 100644 --- a/WordPress/WordPressShareExtension/UINavigationController+Extensions.swift +++ b/WordPress/WordPressShareExtension/UINavigationController+Extensions.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit /// Encapsulates UINavigationController Helper Methods. /// diff --git a/WordPress/WordPressShareExtension/WordPressShare-Bridging-Header.h b/WordPress/WordPressShareExtension/WordPressShare-Bridging-Header.h index 670e97f7e3e3..285c18bfd1ae 100644 --- a/WordPress/WordPressShareExtension/WordPressShare-Bridging-Header.h +++ b/WordPress/WordPressShareExtension/WordPressShare-Bridging-Header.h @@ -2,5 +2,4 @@ #import "Constants.h" // Shared Helpers -#import "SFHFKeychainUtils.h" #import "TextBundleWrapper.h" diff --git a/WordPress/WordPressTest/DataMigratorTests.swift b/WordPress/WordPressTest/DataMigratorTests.swift index c834c515b674..916119e2f36b 100644 --- a/WordPress/WordPressTest/DataMigratorTests.swift +++ b/WordPress/WordPressTest/DataMigratorTests.swift @@ -209,27 +209,6 @@ class DataMigratorTests: XCTestCase { } } -// MARK: - CoreDataStackMock - -private final class CoreDataStackMock: CoreDataStack { - var mainContext: NSManagedObjectContext - - init(mainContext: NSManagedObjectContext) { - self.mainContext = mainContext - } - - func newDerivedContext() -> NSManagedObjectContext { - return mainContext - } - - func saveContextAndWait(_ context: NSManagedObjectContext) {} - func save(_ context: NSManagedObjectContext) {} - func save(_ context: NSManagedObjectContext, completion completionBlock: (() -> Void)?, on queue: DispatchQueue) {} - - func performAndSave(_ aBlock: @escaping (NSManagedObjectContext) -> Void) {} - func performAndSave(_ aBlock: @escaping (NSManagedObjectContext) -> Void, completion: (() -> Void)?, on queue: DispatchQueue) {} -} - // MARK: - KeychainUtilsMock private final class KeychainUtilsMock: KeychainUtils { diff --git a/WordPress/WordPressTest/KeychainTest.m b/WordPress/WordPressTest/KeychainTest.m index e85a544eed2b..76129e8bd137 100644 --- a/WordPress/WordPressTest/KeychainTest.m +++ b/WordPress/WordPressTest/KeychainTest.m @@ -1,6 +1,7 @@ +// TODO: Port to yet to create SFHFKeychainUtils test package #import #import -#import "SFHFKeychainUtils.h" +@import SFHFKeychainUtils; @interface KeychainTest : XCTestCase { diff --git a/WordPress/WordPressTest/KeychainUtilsTests.swift b/WordPress/WordPressTest/KeychainUtilsTests.swift index 35c62c8da4de..4254cedef338 100644 --- a/WordPress/WordPressTest/KeychainUtilsTests.swift +++ b/WordPress/WordPressTest/KeychainUtilsTests.swift @@ -1,3 +1,5 @@ +// TODO: Port to yet to create SFHFKeychainUtils test package +import SFHFKeychainUtils import XCTest @testable import WordPress diff --git a/WordPress/WordPressTest/SharedDataIssueSolverTests.swift b/WordPress/WordPressTest/SharedDataIssueSolverTests.swift index c857ac660c9d..fb45820d88e1 100644 --- a/WordPress/WordPressTest/SharedDataIssueSolverTests.swift +++ b/WordPress/WordPressTest/SharedDataIssueSolverTests.swift @@ -126,13 +126,18 @@ private extension SharedDataIssueSolverTests { // MARK: - CoreDataStackMock -private final class CoreDataStackMock: CoreDataStack { - var mainContext: NSManagedObjectContext +final class CoreDataStackMock: CoreDataStack { + private(set) var mainContext: NSManagedObjectContext + + private static var shared: CoreDataStack? init(mainContext: NSManagedObjectContext) { self.mainContext = mainContext + CoreDataStackMock.shared = self } + static func sharedInstance() -> any CoreDataStack { CoreDataStackMock.shared! } + func newDerivedContext() -> NSManagedObjectContext { return mainContext }