├── .buildkite ├── build-and-test.sh ├── create-xcframeworks.sh ├── pipeline.yml └── publish-pod.sh ├── .bundle └── config ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── run-danger.yml ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── .swiftlint.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dangerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Package.swift ├── Podfile ├── Podfile.lock ├── README.md ├── Sources ├── APIInterface │ ├── FilePart.m │ ├── WordPressComRESTAPIVersionedPathBuilder.m │ └── include │ │ ├── FilePart.h │ │ ├── WordPressComRESTAPIInterfacing.h │ │ ├── WordPressComRESTAPIVersion.h │ │ ├── WordPressComRESTAPIVersionedPathBuilder.h │ │ └── WordPressComRestApiErrorDomain.h ├── BasicBlogAPIObjc │ ├── ServiceRemoteWordPressComREST.h │ └── ServiceRemoteWordPressComREST.m ├── CoreAPI │ ├── AppTransportSecuritySettings.swift │ ├── Date+WordPressCom.swift │ ├── DateFormatter+WordPressCom.swift │ ├── Either.swift │ ├── HTTPAuthenticationAlertController.swift │ ├── HTTPClient.swift │ ├── HTTPRequestBuilder.swift │ ├── MultipartForm.swift │ ├── NSDate+WordPressCom.swift │ ├── NonceRetrieval.swift │ ├── Result+Callback.swift │ ├── SocialLogin2FANonceInfo.swift │ ├── StringEncoding+IANA.swift │ ├── WebauthChallengeInfo.swift │ ├── WordPressAPIError+NSErrorBridge.swift │ ├── WordPressAPIError.swift │ ├── WordPressComOAuthClient.swift │ ├── WordPressComRestApi.swift │ ├── WordPressOrgRestApi.swift │ ├── WordPressOrgXMLRPCApi.swift │ ├── WordPressOrgXMLRPCValidator.swift │ └── WordPressRSDParser.swift ├── WordPressKit │ ├── Info.plist │ ├── Logging │ │ ├── WPKitLogging.h │ │ ├── WPKitLogging.m │ │ └── WPKitLogging.swift │ ├── Models │ │ ├── AccountSettings.swift │ │ ├── Activity.swift │ │ ├── Assistant │ │ │ └── JetpackAssistantFeatureDetails.swift │ │ ├── Atomic │ │ │ └── AtomicLogs.swift │ │ ├── AutomatedTransferStatus.swift │ │ ├── Blaze │ │ │ ├── BlazeCampaign.swift │ │ │ └── BlazeCampaignsSearchResponse.swift │ │ ├── DomainContactInformation.swift │ │ ├── EditorSettings.swift │ │ ├── Extensions │ │ │ ├── Date+endOfDay.swift │ │ │ ├── Decodable+Dictionary.swift │ │ │ ├── Enum+UnknownCaseRepresentable.swift │ │ │ ├── NSAttributedString+extensions.swift │ │ │ └── NSMutableParagraphStyle+extensions.swift │ │ ├── FeatureFlag.swift │ │ ├── Jetpack Scan │ │ │ ├── JetpackCredentials.swift │ │ │ ├── JetpackScan.swift │ │ │ ├── JetpackScanHistory.swift │ │ │ ├── JetpackScanThreat.swift │ │ │ └── JetpackThreatFixStatus.swift │ │ ├── JetpackBackup.swift │ │ ├── JetpackRestoreTypes.swift │ │ ├── KeyringConnection.swift │ │ ├── KeyringConnectionExternalUser.swift │ │ ├── Plugins │ │ │ ├── PluginDirectoryEntry.swift │ │ │ ├── PluginDirectoryFeedPage.swift │ │ │ ├── PluginState.swift │ │ │ ├── SitePlugin.swift │ │ │ └── SitePluginCapabilities.swift │ │ ├── ReaderFeed.swift │ │ ├── RemoteBlockEditorSettings.swift │ │ ├── RemoteBlog.swift │ │ ├── RemoteBlogJetpackModulesSettings.swift │ │ ├── RemoteBlogJetpackMonitorSettings.swift │ │ ├── RemoteBlogJetpackSettings.swift │ │ ├── RemoteBlogOptionsHelper.swift │ │ ├── RemoteBlogSettings.swift │ │ ├── RemoteBloggingPrompt.swift │ │ ├── RemoteBloggingPromptsSettings.swift │ │ ├── RemoteComment.h │ │ ├── RemoteComment.m │ │ ├── RemoteCommentV2.swift │ │ ├── RemoteDomain.swift │ │ ├── RemoteGravatarProfile.swift │ │ ├── RemoteHomepageType.swift │ │ ├── RemoteInviteLink.swift │ │ ├── RemoteMedia.h │ │ ├── RemoteMedia.m │ │ ├── RemoteMenu.swift │ │ ├── RemoteMenuItem.swift │ │ ├── RemoteMenuLocation.swift │ │ ├── RemoteNotification.swift │ │ ├── RemoteNotificationSettings.swift │ │ ├── RemotePageLayouts.swift │ │ ├── RemotePerson.swift │ │ ├── RemotePlan_ApiVersion1_3.swift │ │ ├── RemotePost.h │ │ ├── RemotePost.m │ │ ├── RemotePostAutosave.swift │ │ ├── RemotePostCategory.h │ │ ├── RemotePostCategory.m │ │ ├── RemotePostParameters.swift │ │ ├── RemotePostTag.h │ │ ├── RemotePostTag.m │ │ ├── RemotePostType.h │ │ ├── RemotePostType.m │ │ ├── RemoteProfile.swift │ │ ├── RemotePublicizeConnection.swift │ │ ├── RemotePublicizeInfo.swift │ │ ├── RemotePublicizeService.swift │ │ ├── RemoteReaderCard.swift │ │ ├── RemoteReaderCrossPostMeta.swift │ │ ├── RemoteReaderInterest.swift │ │ ├── RemoteReaderPost.h │ │ ├── RemoteReaderPost.m │ │ ├── RemoteReaderPost.swift │ │ ├── RemoteReaderSimplePost.swift │ │ ├── RemoteReaderSite.swift │ │ ├── RemoteReaderSiteInfo.swift │ │ ├── RemoteReaderSiteInfoSubscription.swift │ │ ├── RemoteReaderTopic.swift │ │ ├── RemoteShareAppContent.swift │ │ ├── RemoteSharingButton.swift │ │ ├── RemoteSiteDesign.swift │ │ ├── RemoteSourcePostAttribution.h │ │ ├── RemoteSourcePostAttribution.m │ │ ├── RemoteTaxonomyPaging.h │ │ ├── RemoteTaxonomyPaging.m │ │ ├── RemoteTheme.h │ │ ├── RemoteTheme.m │ │ ├── RemoteUser+Likes.swift │ │ ├── RemoteUser.h │ │ ├── RemoteUser.m │ │ ├── RemoteVideoPressVideo.swift │ │ ├── RemoteWpcomPlan.swift │ │ ├── Revisions │ │ │ ├── RemoteDiff.swift │ │ │ └── RemoteRevision.swift │ │ ├── SessionDetails.swift │ │ ├── Stats │ │ │ ├── Emails │ │ │ │ └── StatsEmailsSummaryData.swift │ │ │ ├── Insights │ │ │ │ ├── StatsAllAnnualInsight.swift │ │ │ │ ├── StatsAllTimesInsight.swift │ │ │ │ ├── StatsAnnualAndMostPopularTimeInsight.swift │ │ │ │ ├── StatsCommentsInsight.swift │ │ │ │ ├── StatsDotComFollowersInsight.swift │ │ │ │ ├── StatsEmailFollowersInsight.swift │ │ │ │ ├── StatsLastPostInsight.swift │ │ │ │ ├── StatsPostingStreakInsight.swift │ │ │ │ ├── StatsPublicizeInsight.swift │ │ │ │ ├── StatsTagsAndCategoriesInsight.swift │ │ │ │ └── StatsTodayInsight.swift │ │ │ ├── StatsPostDetails.swift │ │ │ ├── StatsSubscribersSummaryData.swift │ │ │ └── Time Interval │ │ │ │ ├── StatsFileDownloadsTimeIntervalData.swift │ │ │ │ ├── StatsPublishedPostsTimeIntervalData.swift │ │ │ │ ├── StatsSearchTermTimeIntervalData.swift │ │ │ │ ├── StatsSummaryTimeIntervalData.swift │ │ │ │ ├── StatsTopAuthorsTimeIntervalData.swift │ │ │ │ ├── StatsTopClicksTimeIntervalData.swift │ │ │ │ ├── StatsTopCountryTimeIntervalData.swift │ │ │ │ ├── StatsTopPostsTimeIntervalData.swift │ │ │ │ ├── StatsTopReferrersTimeIntervalData.swift │ │ │ │ ├── StatsTopVideosTimeIntervalData.swift │ │ │ │ └── StatsTotalsSummaryData.swift │ │ ├── WPCountry.swift │ │ ├── WPState.swift │ │ └── WPTimeZone.swift │ ├── Private │ │ └── WPKit-Swift.h │ ├── Services │ │ ├── AccountServiceRemote.h │ │ ├── AccountServiceRemoteREST+SocialService.swift │ │ ├── AccountServiceRemoteREST.h │ │ ├── AccountServiceRemoteREST.m │ │ ├── AccountSettingsRemote.swift │ │ ├── ActivityServiceRemote.swift │ │ ├── ActivityServiceRemote_ApiVersion1_0.swift │ │ ├── AnnouncementServiceRemote.swift │ │ ├── AtomicAuthenticationServiceRemote.swift │ │ ├── AtomicSiteServiceRemote.swift │ │ ├── AutomatedTransferService.swift │ │ ├── BlazeServiceRemote.swift │ │ ├── BlockEditorSettingsServiceRemote.swift │ │ ├── BlogJetpackSettingsServiceRemote.swift │ │ ├── BlogServiceRemote.h │ │ ├── BlogServiceRemoteREST.h │ │ ├── BlogServiceRemoteREST.m │ │ ├── BlogServiceRemoteXMLRPC.h │ │ ├── BlogServiceRemoteXMLRPC.m │ │ ├── BloggingPromptsServiceRemote.swift │ │ ├── CommentServiceRemote.h │ │ ├── CommentServiceRemoteREST+ApiV2.swift │ │ ├── CommentServiceRemoteREST.h │ │ ├── CommentServiceRemoteREST.m │ │ ├── CommentServiceRemoteXMLRPC.h │ │ ├── CommentServiceRemoteXMLRPC.m │ │ ├── DashboardServiceRemote.swift │ │ ├── Domains │ │ │ ├── DomainsServiceRemote+AllDomains.swift │ │ │ └── DomainsServiceRemote.swift │ │ ├── EditorServiceRemote.swift │ │ ├── FeatureFlagRemote.swift │ │ ├── GravatarServiceRemote.swift │ │ ├── HomepageSettingsServiceRemote.swift │ │ ├── IPLocationRemote.swift │ │ ├── JSONDecoderExtension.swift │ │ ├── JetpackAIServiceRemote.swift │ │ ├── JetpackBackupServiceRemote.swift │ │ ├── JetpackCapabilitiesServiceRemote.swift │ │ ├── JetpackProxyServiceRemote.swift │ │ ├── JetpackScanServiceRemote.swift │ │ ├── JetpackServiceRemote.swift │ │ ├── JetpackSocialServiceRemote.swift │ │ ├── MediaServiceRemote.h │ │ ├── MediaServiceRemoteREST.h │ │ ├── MediaServiceRemoteREST.m │ │ ├── MediaServiceRemoteXMLRPC.h │ │ ├── MediaServiceRemoteXMLRPC.m │ │ ├── MenusServiceRemote.h │ │ ├── MenusServiceRemote.m │ │ ├── NotificationSettingsServiceRemote.swift │ │ ├── NotificationSyncServiceRemote.swift │ │ ├── PageLayoutServiceRemote.swift │ │ ├── PeopleServiceRemote.swift │ │ ├── Plans │ │ │ ├── PlanServiceRemote.swift │ │ │ └── PlanServiceRemote_ApiVersion1_3.swift │ │ ├── Plugin Management │ │ │ ├── JetpackPluginManagementClient.swift │ │ │ ├── PluginManagementClient.swift │ │ │ └── SelfHostedPluginManagementClient.swift │ │ ├── PluginDirectoryServiceRemote.swift │ │ ├── PluginServiceRemote.swift │ │ ├── PostServiceRemote.h │ │ ├── PostServiceRemoteExtended.swift │ │ ├── PostServiceRemoteOptions.h │ │ ├── PostServiceRemoteREST+Extended.swift │ │ ├── PostServiceRemoteREST+Revisions.swift │ │ ├── PostServiceRemoteREST.h │ │ ├── PostServiceRemoteREST.m │ │ ├── PostServiceRemoteXMLRPC+Extended.swift │ │ ├── PostServiceRemoteXMLRPC.h │ │ ├── PostServiceRemoteXMLRPC.m │ │ ├── ProductServiceRemote.swift │ │ ├── PushAuthenticationServiceRemote.swift │ │ ├── QR Login │ │ │ ├── QRLoginServiceRemote.swift │ │ │ └── QRLoginValidationResponse.swift │ │ ├── ReaderPostServiceRemote+Cards.swift │ │ ├── ReaderPostServiceRemote+RelatedPosts.swift │ │ ├── ReaderPostServiceRemote+Subscriptions.swift │ │ ├── ReaderPostServiceRemote+V2.swift │ │ ├── ReaderPostServiceRemote.h │ │ ├── ReaderPostServiceRemote.m │ │ ├── ReaderServiceDeliveryFrequency.swift │ │ ├── ReaderSiteSearchServiceRemote.swift │ │ ├── ReaderSiteServiceRemote.h │ │ ├── ReaderSiteServiceRemote.m │ │ ├── ReaderTopicServiceError.swift │ │ ├── ReaderTopicServiceRemote+Interests.swift │ │ ├── ReaderTopicServiceRemote+Subscription.swift │ │ ├── ReaderTopicServiceRemote.h │ │ ├── ReaderTopicServiceRemote.m │ │ ├── RemoteConfigRemote.swift │ │ ├── ServiceRemoteWordPressXMLRPC.h │ │ ├── ServiceRemoteWordPressXMLRPC.m │ │ ├── ServiceRequest.swift │ │ ├── ShareAppContentServiceRemote.swift │ │ ├── SharingServiceRemote.swift │ │ ├── SiteDesignServiceRemote.swift │ │ ├── SiteManagementServiceRemote.swift │ │ ├── SiteServiceRemoteWordPressComREST.h │ │ ├── SiteServiceRemoteWordPressComREST.m │ │ ├── StatsServiceRemoteV2.swift │ │ ├── SubscribersServiceRemote.swift │ │ ├── TaxonomyServiceRemote.h │ │ ├── TaxonomyServiceRemoteREST.h │ │ ├── TaxonomyServiceRemoteREST.m │ │ ├── TaxonomyServiceRemoteXMLRPC.h │ │ ├── TaxonomyServiceRemoteXMLRPC.m │ │ ├── ThemeServiceRemote.h │ │ ├── ThemeServiceRemote.m │ │ ├── TimeZoneServiceRemote.swift │ │ ├── TransactionsServiceRemote.swift │ │ ├── UsersServiceRemoteXMLRPC.swift │ │ ├── WordPressComServiceRemote+SiteCreation.swift │ │ ├── WordPressComServiceRemote+SiteSegments.swift │ │ ├── WordPressComServiceRemote+SiteVerticals.swift │ │ ├── WordPressComServiceRemote+SiteVerticalsPrompt.swift │ │ ├── WordPressComServiceRemote.h │ │ └── WordPressComServiceRemote.m │ ├── Utility │ │ ├── ChecksumUtil.swift │ │ ├── HTTPProtocolHelpers.swift │ │ ├── NSCharacterSet+URLEncode.swift │ │ ├── NSMutableDictionary+Helpers.h │ │ ├── NSMutableDictionary+Helpers.m │ │ ├── NSString+MD5.h │ │ ├── NSString+MD5.m │ │ ├── ObjectValidation.swift │ │ ├── StringCodingKey.swift │ │ ├── UIDevice+Extensions.swift │ │ └── ZendeskMetadata.swift │ └── WordPressKit.h └── WordPressShared │ ├── Dictionary+Helpers.swift │ ├── DisplayableImageHelper.h │ ├── DisplayableImageHelper.m │ ├── NSBundle+VersionNumberHelper.h │ ├── NSBundle+VersionNumberHelper.m │ ├── NSDate+Helpers.swift │ ├── NSMutableData+Helpers.swift │ ├── NSString+Helpers.h │ ├── NSString+Helpers.m │ ├── NSString+Summary.swift │ ├── NSString+XMLExtensions.h │ ├── NSString+XMLExtensions.m │ ├── Secret.swift │ ├── String+Helpers.swift │ ├── WPKitDateUtils.h │ ├── WPKitDateUtils.m │ ├── WPMapFilterReduce.h │ ├── WPMapFilterReduce.m │ └── WordPressComLanguageDatabase.swift ├── Tests ├── CoreAPITests │ ├── AppTransportSecuritySettingsTests.swift │ ├── Bundle+SPMSupport.swift │ ├── FakeInfoDictionaryObjectProvider.swift │ ├── HTTPRequestBuilderTests.swift │ ├── MultipartFormTests.swift │ ├── NonceRetrievalTests.swift │ ├── RSDParserTests.swift │ ├── Stubs │ │ ├── HTML │ │ │ ├── xmlrpc-response-invalid.html │ │ │ └── xmlrpc-response-mobile-plugin-redirect.html │ │ ├── JSON │ │ │ ├── WordPressComAuthenticateWithIDToken2FANeededSuccess.json │ │ │ ├── WordPressComAuthenticateWithIDTokenBearerTokenSuccess.json │ │ │ ├── WordPressComAuthenticateWithIDTokenExistingUserNeedsConnection.json │ │ │ ├── WordPressComOAuthAuthenticateSignature.json │ │ │ ├── WordPressComOAuthNeeds2FAFail.json │ │ │ ├── WordPressComOAuthNeedsWebauthnMFA.json │ │ │ ├── WordPressComOAuthRequestChallenge.json │ │ │ ├── WordPressComOAuthSuccess.json │ │ │ ├── WordPressComOAuthWrongPasswordFail.json │ │ │ ├── WordPressComRestApiFailInvalidInput.json │ │ │ ├── WordPressComRestApiFailInvalidJSON.json │ │ │ ├── WordPressComRestApiFailReauthenticationRequired.json │ │ │ ├── WordPressComRestApiFailRequestInvalidToken.json │ │ │ ├── WordPressComRestApiFailThrottled.json │ │ │ ├── WordPressComRestApiFailUnauthorized.json │ │ │ ├── WordPressComRestApiMedia.json │ │ │ ├── WordPressComRestApiMultipleErrors.json │ │ │ ├── WordPressComSocial2FACodeSuccess.json │ │ │ ├── me-settings-success.json │ │ │ ├── wp-forbidden.json │ │ │ ├── wp-pages.json │ │ │ └── wp-reusable-blocks.json │ │ └── XML │ │ │ ├── xmlrpc-bad-username-password-error.xml │ │ │ ├── xmlrpc-response-getpost.xml │ │ │ └── xmlrpc-response-list-methods.xml │ ├── URLRequest+HTTPBodyText.swift │ ├── WordPressComOAuthClientTests.swift │ ├── WordPressComRestApiTests+Error.swift │ ├── WordPressComRestApiTests+Locale.swift │ ├── WordPressComRestApiTests.swift │ ├── WordPressOrgAPITests.swift │ ├── WordPressOrgRestApiTests.swift │ ├── WordPressOrgXMLRPCApiTests.swift │ └── WordPressOrgXMLRPCValidatorTests.swift └── WordPressKitTests │ ├── Info.plist │ ├── Mock Data │ ├── BlockEditorSettings │ │ ├── get_wp_v2_themes_twentytwentyone-no-colors.json │ │ ├── get_wp_v2_themes_twentytwentyone.json │ │ ├── wp-block-editor-v1-settings-success-NotThemeJSON.json │ │ └── wp-block-editor-v1-settings-success-ThemeJSON.json │ ├── Domains │ │ └── get-all-domains-response.json │ ├── activity-groups-bad-json-failure.json │ ├── activity-groups-success.json │ ├── activity-log-auth-failure.json │ ├── activity-log-bad-json-failure.json │ ├── activity-log-success-1.json │ ├── activity-log-success-2.json │ ├── activity-log-success-3.json │ ├── activity-restore-success.json │ ├── activity-rewind-status-restore-failure.json │ ├── activity-rewind-status-restore-finished.json │ ├── activity-rewind-status-restore-in-progress.json │ ├── activity-rewind-status-restore-queued.json │ ├── activity-rewind-status-success.json │ ├── atomic-get-auth-cookie-success.json │ ├── auth-send-login-email-invalid-client-failure.json │ ├── auth-send-login-email-invalid-secret-failure.json │ ├── auth-send-login-email-no-user-failure.json │ ├── auth-send-login-email-success.json │ ├── auth-send-verification-email-already-verified-failure.json │ ├── auth-send-verification-email-success.json │ ├── backup-get-backup-status-complete-without-download-id-success.json │ ├── blaze-campaigns-search.json │ ├── blogging-prompts-settings-fetch-success.json │ ├── blogging-prompts-settings-update-empty-response.json │ ├── blogging-prompts-settings-update-with-response.json │ ├── blogging-prompts-success.json │ ├── comment-likes-success.json │ ├── comments-v2-edit-context-success.json │ ├── comments-v2-view-context-success.json │ ├── common-starter-site-designs-empty-designs.json │ ├── common-starter-site-designs-malformed.json │ ├── common-starter-site-designs-success.json │ ├── dashboard-200-with-drafts-and-scheduled-posts.json │ ├── dashboard-400-invalid-card.json │ ├── domain-contact-information-response-success.json │ ├── domain-service-all-domain-types.json │ ├── domain-service-bad-json.json │ ├── domain-service-empty.json │ ├── domain-service-invalid-query.json │ ├── empty-array.json │ ├── empty.json │ ├── get-multiple-themes-v1.2.json │ ├── get-purchased-themes-v1.1.json │ ├── get-single-theme-v1.1.json │ ├── is-available-email-failure.json │ ├── is-available-email-success.json │ ├── is-available-username-failure.json │ ├── is-available-username-success.json │ ├── is-passwordless-account-no-account-found.json │ ├── is-passwordless-account-success.json │ ├── jetpack-capabilities-107159616-success.json │ ├── jetpack-capabilities-34197361-success.json │ ├── jetpack-capabilities-malformed.json │ ├── jetpack-scan-enqueue-failure.json │ ├── jetpack-scan-enqueue-success.json │ ├── jetpack-scan-idle-success-no-threats.json │ ├── jetpack-scan-idle-success-threats.json │ ├── jetpack-scan-in-progress.json │ ├── jetpack-scan-unavailable.json │ ├── jetpack-service-check-site-failure-data.json │ ├── jetpack-service-check-site-success-no-jetpack.json │ ├── jetpack-service-check-site-success.json │ ├── jetpack-service-error-activation-failure.json │ ├── jetpack-service-error-activation-install.json │ ├── jetpack-service-error-activation-response.json │ ├── jetpack-service-error-forbidden.json │ ├── jetpack-service-error-install-failure.json │ ├── jetpack-service-error-install-response.json │ ├── jetpack-service-error-invalid-credentials.json │ ├── jetpack-service-error-login-failure.json │ ├── jetpack-service-error-site-is-jetpack.json │ ├── jetpack-service-error-unknown.json │ ├── jetpack-service-failure.json │ ├── jetpack-service-success.json │ ├── jetpack-social-403.json │ ├── jetpack-social-no-publicize.json │ ├── jetpack-social-with-publicize.json │ ├── me-auth-failure.json │ ├── me-bad-json-failure.json │ ├── me-settings-close-account-failure.json │ ├── me-settings-close-account-success.json │ ├── me-sites-auth-failure.json │ ├── me-sites-bad-json-failure.json │ ├── me-sites-empty-success.json │ ├── me-sites-success.json │ ├── me-sites-visibility-bad-json-failure.json │ ├── me-sites-visibility-failure.json │ ├── me-sites-visibility-success.json │ ├── me-success.json │ ├── notifications-last-seen.json │ ├── notifications-load-all.json │ ├── notifications-load-hash.json │ ├── notifications-mark-as-read.json │ ├── page-layout-blog-layouts-malformed.json │ ├── page-layout-blog-layouts-success.json │ ├── people-send-invitation-failure.json │ ├── people-send-invitation-success.json │ ├── people-validate-invitation-failure.json │ ├── people-validate-invitation-success.json │ ├── plans-me-sites-success.json │ ├── plans-mobile-success.json │ ├── plugin-directory-jetpack-beta.json │ ├── plugin-directory-jetpack.json │ ├── plugin-directory-new.json │ ├── plugin-directory-popular.json │ ├── plugin-directory-rename-xml-rpc.json │ ├── plugin-install-already-installed.json │ ├── plugin-install-generic-error.json │ ├── plugin-install-succeeds.json │ ├── plugin-modify-malformed-response.json │ ├── plugin-service-remote-auth-failure.json │ ├── plugin-service-remote-featured-malformed.json │ ├── plugin-service-remote-featured-plugins-invalid.json │ ├── plugin-service-remote-featured.json │ ├── plugin-state-contact-form-7.json │ ├── plugin-state-jetpack.json │ ├── plugin-update-gutenberg-needs-update.json │ ├── plugin-update-jetpack-already-updated.json │ ├── plugin-update-response-malformed.json │ ├── post-autosave-mapping-success.json │ ├── post-likes-failure.json │ ├── post-likes-success.json │ ├── post-revisions-failure.json │ ├── post-revisions-mapping-success.json │ ├── post-revisions-success.json │ ├── qrlogin-authenticate-200.json │ ├── qrlogin-authenticate-failed-400.json │ ├── qrlogin-validate-200.json │ ├── qrlogin-validate-400.json │ ├── qrlogin-validate-expired-401.json │ ├── reader-cards-success.json │ ├── reader-following-mine.json │ ├── reader-interests-success.json │ ├── reader-post-comments-subscribe-failure.json │ ├── reader-post-comments-subscribe-success.json │ ├── reader-post-comments-subscription-status-success.json │ ├── reader-post-comments-unsubscribe-success.json │ ├── reader-post-comments-update-notification-success.json │ ├── reader-post-related-posts-success.json │ ├── reader-posts-success.json │ ├── reader-site-search-blog-id-fallback.json │ ├── reader-site-search-failure.json │ ├── reader-site-search-no-blog-or-feed-id.json │ ├── reader-site-search-success-hasmore.json │ ├── reader-site-search-success-no-data.json │ ├── reader-site-search-success-no-icon.json │ ├── reader-site-search-success.json │ ├── remote-notification.json │ ├── rest-site-settings.json │ ├── self-hosted-plugins-get.json │ ├── self-hosted-plugins-install.json │ ├── share-app-content-success.json │ ├── site-active-purchases-auth-failure.json │ ├── site-active-purchases-bad-json-failure.json │ ├── site-active-purchases-empty-response.json │ ├── site-active-purchases-none-active-success.json │ ├── site-active-purchases-two-active-success.json │ ├── site-comment-success.json │ ├── site-comments-success.json │ ├── site-creation-success.json │ ├── site-delete-auth-failure.json │ ├── site-delete-bad-json-failure.json │ ├── site-delete-missing-status-failure.json │ ├── site-delete-success.json │ ├── site-delete-unexpected-json-failure.json │ ├── site-email-followers-get-auth-failure.json │ ├── site-email-followers-get-failure.json │ ├── site-email-followers-get-success-more-pages.json │ ├── site-email-followers-get-success.json │ ├── site-export-auth-failure.json │ ├── site-export-bad-json-failure.json │ ├── site-export-failure.json │ ├── site-export-missing-status-failure.json │ ├── site-export-success.json │ ├── site-followers-delete-auth-failure.json │ ├── site-followers-delete-bad-json-failure.json │ ├── site-followers-delete-failure.json │ ├── site-followers-delete-success.json │ ├── site-plans-bad-json-failure.json │ ├── site-plans-v3-bad-json-failure.json │ ├── site-plans-v3-empty-failure.json │ ├── site-plans-v3-success.json │ ├── site-plugins-error.json │ ├── site-plugins-malformed.json │ ├── site-plugins-success.json │ ├── site-quick-start-failure.json │ ├── site-quick-start-success.json │ ├── site-roles-auth-failure.json │ ├── site-roles-bad-json-failure.json │ ├── site-roles-success.json │ ├── site-segments-multiple.json │ ├── site-segments-single.json │ ├── site-subscriber-get-details-response-invalid-country.json │ ├── site-subscriber-get-details-response.json │ ├── site-subscriber-stats-response.json │ ├── site-subscribers-response.json │ ├── site-users-delete-auth-failure.json │ ├── site-users-delete-bad-json-failure.json │ ├── site-users-delete-not-member-failure.json │ ├── site-users-delete-site-owner-failure.json │ ├── site-users-delete-success.json │ ├── site-users-update-role-bad-json-failure.json │ ├── site-users-update-role-success.json │ ├── site-users-update-role-unknown-site-failure.json │ ├── site-users-update-role-unknown-user-failure.json │ ├── site-verticals-empty.json │ ├── site-verticals-multiple.json │ ├── site-verticals-prompt.json │ ├── site-verticals-single.json │ ├── site-viewers-delete-auth-failure.json │ ├── site-viewers-delete-bad-json.json │ ├── site-viewers-delete-failure.json │ ├── site-viewers-delete-success.json │ ├── site-zendesk-metadata-success.json │ ├── sites-external-services.json │ ├── sites-invites-links-disable-empty.json │ ├── sites-invites-links-disable.json │ ├── sites-invites-links-generate.json │ ├── sites-invites.json │ ├── sites-site-active-features.json │ ├── sites-site-no-active-features.json │ ├── stats-clicks-data.json │ ├── stats-countries-data.json │ ├── stats-emails-summary.json │ ├── stats-file-downloads.json │ ├── stats-post-details.json │ ├── stats-posts-data.json │ ├── stats-published-posts.json │ ├── stats-referrer-data.json │ ├── stats-referrer-mark-as-spam.json │ ├── stats-search-term-result.json │ ├── stats-streak-result.json │ ├── stats-subscribers.json │ ├── stats-summary.json │ ├── stats-top-authors.json │ ├── stats-videos-data.json │ ├── stats-visits-day.json │ ├── stats-visits-month-unit-week.json │ ├── stats-visits-month.json │ ├── stats-visits-week.json │ ├── supported-countries-success.json │ ├── supported-states-empty.json │ ├── supported-states-success.json │ ├── timezones.json │ ├── validate-domain-contact-information-response-fail.json │ ├── validate-domain-contact-information-response-success.json │ ├── videopress-private-video.json │ ├── videopress-public-video.json │ ├── videopress-site-default-video.json │ ├── videopress-token.json │ ├── wp-admin-post-new.html │ ├── xmlrpc-malformed-request-xml-error.xml │ ├── xmlrpc-metaweblog-editpost-bad-xml-failure.xml │ ├── xmlrpc-metaweblog-editpost-change-format-failure.xml │ ├── xmlrpc-metaweblog-editpost-change-type-failure.xml │ ├── xmlrpc-metaweblog-editpost-success.xml │ ├── xmlrpc-metaweblog-newpost-bad-xml-failure.xml │ ├── xmlrpc-metaweblog-newpost-invalid-posttype-failure.xml │ ├── xmlrpc-metaweblog-newpost-success.xml │ ├── xmlrpc-response-getprofile.xml │ ├── xmlrpc-response-valid-but-unexpected-dictionary.xml │ ├── xmlrpc-site-comment-success.xml │ ├── xmlrpc-site-comments-success.xml │ ├── xmlrpc-wp-getpost-bad-xml-failure.xml │ ├── xmlrpc-wp-getpost-invalid-id-failure.xml │ └── xmlrpc-wp-getpost-success.xml │ ├── Tests │ ├── AccountServiceRemoteRESTTests.swift │ ├── AccountSettingsRemoteTests.swift │ ├── ActivityServiceRemoteTests.swift │ ├── ActivityTests.swift │ ├── AllDomainsResultDomainTests.swift │ ├── AnnouncementServiceRemoteTests.swift │ ├── AtomicAuthenticationServiceRemoteTests.swift │ ├── BlazeServiceRemoteTests.swift │ ├── BlockEditorSettingsServiceRemoteTests.swift │ ├── BlogServiceRemote+ActiveFeaturesTests.swift │ ├── BlogServiceRemoteRESTTests.m │ ├── BloggingPromptsServiceRemoteTests.swift │ ├── CommentServiceRemoteREST+APIv2Tests.swift │ ├── CommentServiceRemoteRESTLikesTests.swift │ ├── CommentServiceRemoteRESTTests.swift │ ├── CommentServiceRemoteXMLRPCTests.swift │ ├── DashboardServiceRemoteTests.swift │ ├── Date+WordPressComTests.swift │ ├── DateFormatter+WordPressComTests.swift │ ├── DomainsServiceRemoteRESTTests.swift │ ├── DynamicMockProvider.swift │ ├── EditorServiceRemoteTests.swift │ ├── Extensions │ │ └── Decodable+DictionaryTests.swift │ ├── IPLocationRemoteTests.swift │ ├── JSONLoader.swift │ ├── JetpackBackupServiceRemoteTests.swift │ ├── JetpackCapabilitiesServiceRemoteTests.swift │ ├── JetpackProxyServiceRemoteTests.swift │ ├── JetpackServiceRemoteTests.swift │ ├── LoadMediaLibraryTests.swift │ ├── MediaLibraryTestSupport.swift │ ├── MediaServiceRemoteRESTTests.swift │ ├── MenusServiceRemoteTests.m │ ├── MockPluginDirectoryEntryProvider.swift │ ├── MockPluginStateProvider.swift │ ├── MockServiceRequest.swift │ ├── MockWordPressComRestApi.swift │ ├── Models │ │ ├── RemotePersonTests.swift │ │ ├── RemoteVideoPressVideoTests.swift │ │ └── Stats │ │ │ └── V2 │ │ │ ├── Emails │ │ │ └── StatsEmailsSummaryDataTests.swift │ │ │ ├── Insights │ │ │ ├── MockData │ │ │ │ ├── stats-insight-comments.json │ │ │ │ ├── stats-insight-followers.json │ │ │ │ ├── stats-insight-last-post.json │ │ │ │ ├── stats-insight-publicize.json │ │ │ │ ├── stats-insight-streak.json │ │ │ │ ├── stats-insight-summary.json │ │ │ │ ├── stats-insight-tag-and-category.json │ │ │ │ ├── stats-insight.json │ │ │ │ └── stats.json │ │ │ ├── StatsDotComFollowersInsightTests.swift │ │ │ └── StatsInsightDecodingTests.swift │ │ │ └── StatsSubscribersSummaryDataTests.swift │ ├── NSDate+WordPressComTests.swift │ ├── NotificationSyncServiceRemoteTests.swift │ ├── PageLayoutServiceRemoteTests.swift │ ├── PeopleServiceRemoteTests.swift │ ├── PlanServiceRemoteTests.swift │ ├── PluginDirectoryTests.swift │ ├── PluginServiceRemoteTests.swift │ ├── PluginStateTests.swift │ ├── PostServiceRemoteRESTAutosaveTests.swift │ ├── PostServiceRemoteRESTLikesTests.swift │ ├── PostServiceRemoteRESTRevisionsTest.swift │ ├── PostServiceRemoteRESTTests.m │ ├── PostServiceRemoteXMLRPCTests.swift │ ├── PushAuthenticationServiceRemoteTests.swift │ ├── QRLoginServiceRemoteTests.swift │ ├── RESTTestable.swift │ ├── ReaderPostServiceRemote+CardsTests.swift │ ├── ReaderPostServiceRemote+FetchEndpointTests.swift │ ├── ReaderPostServiceRemote+RelatedPostsTests.swift │ ├── ReaderPostServiceRemote+SubscriptionTests.swift │ ├── ReaderPostServiceRemoteTests.m │ ├── ReaderSiteSearchServiceRemoteTests.swift │ ├── ReaderSiteServiceRemoteTests.swift │ ├── ReaderTopicServiceRemote+InterestsTests.swift │ ├── ReaderTopicServiceRemoteTest+Subscriptions.swift │ ├── ReaderTopicServiceRemoteTests.swift │ ├── RemoteNotificationTests.swift │ ├── RemoteReaderPostTests+V2.swift │ ├── RemoteReaderPostTests.m │ ├── RemoteReaderSiteInfoSubscriptionTests.swift │ ├── RemoteTestCase.swift │ ├── Scan │ │ └── JetpackScanServiceRemoteTests.swift │ ├── SelfHostedPluginManagementClientTests.swift │ ├── ServiceRequestTest.swift │ ├── ShareAppContentServiceRemoteTests.swift │ ├── SharingServiceRemoteTests.swift │ ├── SiteCreationRequestEncodingTests.swift │ ├── SiteCreationResponseDecodingTests.swift │ ├── SiteCreationSegmentsTests.swift │ ├── SiteDesignServiceRemoteTests.swift │ ├── SiteManagementServiceRemoteTests.swift │ ├── SitePluginTests.swift │ ├── SiteSegmentsResponseDecodingTests.swift │ ├── SiteVerticalsPromptResponseDecodingTests.swift │ ├── SiteVerticalsRequestEncodingTests.swift │ ├── SiteVerticalsResponseDecodingTests.swift │ ├── Social │ │ └── JetpackSocialServiceRemoteTests.swift │ ├── StatsAnnualAndMostPopularTimeInsightDecodingTests.swift │ ├── StatsRemoteV2Tests.swift │ ├── SubscribersServiceRemoteTests.swift │ ├── TaxonomyServiceRemoteRESTTests.m │ ├── TestCollector+Constants.swift │ ├── ThemeServiceRemoteTests.m │ ├── TimeZoneServiceRemoteTests.swift │ ├── TransactionsServiceRemoteTests.swift │ ├── UsersServiceRemoteXMLRPCTests.swift │ ├── Utilities │ │ ├── ChecksumUtilTests.swift │ │ ├── FeatureFlagRemoteTests.swift │ │ ├── FeatureFlagSerializationTest.swift │ │ ├── HTTPBodyEncodingTests.swift │ │ ├── HTTPHeaderValueParserTests.swift │ │ ├── LoggingTests.m │ │ ├── LoggingTests.swift │ │ ├── RemoteConfigRemoteTests.swift │ │ ├── URLSessionHelperTests.swift │ │ └── WordPressAPIErrorTests.swift │ ├── WordPressComServiceRemoteRestTests.swift │ ├── WordPressComServiceRemoteTests+SiteCreation.swift │ ├── WordPressComServiceRemoteTests+SiteVerticals.swift │ ├── WordPressComServiceRemoteTests+SiteVerticalsPrompt.swift │ └── XMLRPCTestable.swift │ ├── UnitTests.xctestplan │ └── WordPressKitTests-Bridging-Header.h ├── WordPressKit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ ├── WordPressKit.xcscheme │ └── WordPressKitTests.xcscheme ├── WordPressKit.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── WordPressKitTests ├── backup-get-backup-status-complete-success.json ├── backup-get-backup-status-in-progress-success.json ├── backup-prepare-backup-success.json ├── me-settings-auth-failure.json ├── me-settings-bad-json-failure.json ├── me-settings-change-aboutme-success.json ├── me-settings-change-display-name-bad-json-failure.json ├── me-settings-change-display-name-success.json ├── me-settings-change-email-success.json ├── me-settings-change-firstname-success.json ├── me-settings-change-invalid-input-failure.json ├── me-settings-change-lastname-success.json ├── me-settings-change-primary-site-success.json ├── me-settings-change-web-address-success.json └── me-settings-revert-email-success.json └── fastlane └── Fastfile /.buildkite/build-and-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -u 2 | 3 | set +e 4 | build_and_test_pod 5 | TESTS_EXIT_STATUS=$? 6 | set -e 7 | 8 | if [[ $TESTS_EXIT_STATUS -ne 0 ]]; then 9 | # Keep the (otherwise collapsed) current "Testing" section open in Buildkite logs on error. See https://buildkite.com/docs/pipelines/managing-log-output#collapsing-output 10 | echo "^^^ +++" 11 | echo "Tests failed!" 12 | fi 13 | 14 | echo "--- 📦 Zipping test results" 15 | cd fastlane/test_output/ && zip -rq WordPressKit.xcresult.zip WordPressKit.xcresult && cd - 16 | 17 | echo "--- 🚦 Report Tests Status" 18 | if [[ $TESTS_EXIT_STATUS -eq 0 ]]; then 19 | echo "Tests seems to have passed (exit code 0). All good 👍" 20 | else 21 | echo "The tests have failed." 22 | echo "For more details about the failed tests, check the Buildkite annotation, the logs under the '🧪 Building and Running Tests' section and the \`.xcresult\` and test reports in Buildkite artifacts." 23 | fi 24 | annotate_test_failures "fastlane/test_output/report.junit" 25 | 26 | exit $TESTS_EXIT_STATUS 27 | -------------------------------------------------------------------------------- /.buildkite/create-xcframeworks.sh: -------------------------------------------------------------------------------- 1 | ROOT="./.build/xcframeworks" 2 | 3 | rm -rf $ROOT 4 | 5 | for SDK in iphoneos iphonesimulator 6 | do 7 | xcodebuild archive \ 8 | -workspace WordPressKit.xcworkspace \ 9 | -scheme WordPressKit \ 10 | -archivePath "$ROOT/WordPressKit-$SDK.xcarchive" \ 11 | -sdk $SDK \ 12 | SKIP_INSTALL=NO \ 13 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 14 | DEBUG_INFORMATION_FORMAT=DWARF 15 | done 16 | 17 | xcodebuild -create-xcframework \ 18 | -framework "$ROOT/WordPressKit-iphoneos.xcarchive/Products/Library/Frameworks/WordPressKit.framework" \ 19 | -framework "$ROOT/WordPressKit-iphonesimulator.xcarchive/Products/Library/Frameworks/WordPressKit.framework" \ 20 | -output "$ROOT/WordPressKit.xcframework" 21 | 22 | cd $ROOT 23 | zip -r -X WordPressKit.zip *.xcframework 24 | rm -rf *.xcframework 25 | 26 | swift package compute-checksum WordPressKit.zip 27 | cd - 28 | -------------------------------------------------------------------------------- /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json 2 | --- 3 | 4 | agents: 5 | queue: mac 6 | 7 | env: 8 | IMAGE_ID: xcode-15.3-v3 9 | 10 | # Nodes with values to reuse in the pipeline. 11 | common_params: 12 | plugins: &common_plugins 13 | - automattic/a8c-ci-toolkit#3.5.1 14 | 15 | # This is the default pipeline – it will build and test the app 16 | steps: 17 | ################# 18 | # Build and Test 19 | ################# 20 | - label: "🧪 Build and Test" 21 | key: "test" 22 | command: .buildkite/build-and-test.sh 23 | artifact_paths: 24 | - fastlane/test_output/*.xcresult.zip 25 | - fastlane/test_output/report.html 26 | - fastlane/test_output/report.junit 27 | - .build/derived-data/Logs/**/*.xcactivitylog 28 | plugins: *common_plugins 29 | 30 | ################# 31 | # Linters 32 | ################# 33 | - label: ":swift: SwiftLint" 34 | command: swiftlint 35 | env: 36 | SWIFTLINT_OPTION_STRICT: true 37 | notify: 38 | - github_commit_status: 39 | context: SwiftLint 40 | agents: 41 | queue: linter 42 | -------------------------------------------------------------------------------- /.buildkite/publish-pod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | PODSPEC_PATH="WordPressKit.podspec" 4 | SPECS_REPO="git@github.com:wordpress-mobile/cocoapods-specs.git" 5 | SLACK_WEBHOOK=$PODS_SLACK_WEBHOOK 6 | 7 | echo "--- :rubygems: Setting up Gems" 8 | install_gems 9 | 10 | echo "--- :cocoapods: Publishing Pod to CocoaPods CDN" 11 | publish_pod --patch-cocoapods $PODSPEC_PATH 12 | 13 | echo "--- :cocoapods: Publishing Pod to WP Specs Repo" 14 | publish_private_pod --patch-cocoapods $PODSPEC_PATH $SPECS_REPO "$SPEC_REPO_PUBLIC_DEPLOY_KEY" 15 | 16 | echo "--- :slack: Notifying Slack" 17 | slack_notify_pod_published $PODSPEC_PATH "$SLACK_WEBHOOK" 18 | -------------------------------------------------------------------------------- /.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: "vendor/bundle" 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected behavior 2 | 3 | ℹ Please replace this with a clear and concise description of what you expected to happen, or what the problem is. 4 | 5 | ### Actual behavior 6 | 7 | ℹ Please replace this with a clear and concise description of what happened instead, or what you want to happen. 8 | 9 | ### Steps to reproduce the behavior 10 | 11 | ℹ Please replace this with a clear and concise description of what you did, step by step. 12 | 13 | ##### WordPressKit Environment 14 | - WordPressKit Version [e.g. 22] 15 | - OS: [e.g. iOS 12.1.3 (16D40)] 16 | - Device: [e.g. iPhone XS] 17 | - Xcode Version: [e.g. Xcode 10.1 (10B61)] 18 | 19 | ##### Additional Context 20 | ℹ Please link to or upload any information that sheds additional light on the issue. This might include a Charles session, demo project, or the like. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | Fixes # 4 | 5 | ℹ Please replace the above with a link to the issue this pull request addresses, as well as a summary of the implementation details. 6 | 7 | ### Testing Details 8 | 9 | ℹ Please replace this with a clear and concise description of the steps required to validate this pull request. 10 | 11 | --- 12 | 13 | - [ ] Please check here if your pull request includes additional test coverage. 14 | - [ ] I have considered if this change warrants release notes and have added them to the appropriate section in the `CHANGELOG.md` if necessary. 15 | -------------------------------------------------------------------------------- /.github/workflows/run-danger.yml: -------------------------------------------------------------------------------- 1 | name: ☢️ Danger 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, ready_for_review, synchronize] 6 | 7 | jobs: 8 | dangermattic: 9 | # runs on draft PRs only for opened / synchronize events 10 | uses: Automattic/dangermattic/.github/workflows/reusable-run-danger.yml@v1.0.0 11 | secrets: 12 | github-token: ${{ secrets.DANGERMATTIC_GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Opt in to new cops by default 2 | AllCops: 3 | NewCops: enable 4 | 5 | # Override the maximum line length 6 | Layout/LineLength: 7 | Max: 160 8 | 9 | # Allow the Podspec filename to match the project 10 | Naming/FileName: 11 | Exclude: 12 | - 'WordPressKit.podspec' 13 | 14 | Metrics/BlockLength: 15 | Exclude: 16 | - 'fastlane/Fastfile' 17 | - 'WordPressKit.podspec' 18 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.2 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | swiftlint_version: 0.54.0 2 | 3 | parent_config: https://raw.githubusercontent.com/Automattic/swiftlint-config/0f8ab6388bd8d15a04391825ab125f80cfb90704/.swiftlint.yml 4 | remote_timeout: 10.0 5 | 6 | excluded: 7 | .build # `swift build` etc. output folder 8 | 9 | opt_in_rules: 10 | - overridden_super_call 11 | - discarded_notification_center_observer 12 | - weak_delegate 13 | - vertical_whitespace 14 | - duplicate_imports 15 | 16 | overridden_super_call: 17 | severity: error 18 | 19 | discarded_notification_center_observer: 20 | severity: error 21 | 22 | weak_delegate: 23 | severity: error 24 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | github.dismiss_out_of_range_messages 4 | 5 | # `files: []` forces rubocop to scan all files, not just the ones modified in the PR 6 | rubocop.lint(files: [], force_exclusion: true, inline_comment: true, fail_on_inline_comment: true, include_cop_names: true) 7 | 8 | manifest_pr_checker.check_all_manifest_lock_updated 9 | 10 | podfile_checker.check_podfile_does_not_have_branch_references 11 | 12 | pr_size_checker.check_diff_size( 13 | max_size: 300, 14 | type: :insertions 15 | ) 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'cocoapods', '~> 1.10' 6 | gem 'cocoapods-check', '~> 1.1' 7 | gem 'danger-dangermattic', '~> 1.0' 8 | gem 'fastlane', '~> 2.189' 9 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.10 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "WordPressKit", 7 | platforms: [.iOS(.v15)], 8 | products: [ 9 | .library(name: "WordPressKit", targets: ["WordPressKit"]), 10 | ], 11 | targets: [ 12 | .binaryTarget( 13 | name: "WordPressKit", 14 | url: "https://github.com/user-attachments/files/20895757/WordPressKit.zip", 15 | checksum: "b08eaf182f0399303aadccb1a6dad6cad294a9c8d123d920889b15950c85e08f" 16 | ), 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://cdn.cocoapods.org/' 4 | 5 | inhibit_all_warnings! 6 | use_frameworks! 7 | 8 | APP_IOS_DEPLOYMENT_TARGET = Gem::Version.new('13.0') 9 | 10 | platform :ios, APP_IOS_DEPLOYMENT_TARGET 11 | workspace './WordPressKit.xcworkspace' 12 | 13 | def swiftlint_version 14 | require 'yaml' 15 | 16 | YAML.load_file('.swiftlint.yml')['swiftlint_version'] 17 | end 18 | 19 | abstract_target 'Tools' do 20 | pod 'SwiftLint', swiftlint_version 21 | end 22 | 23 | # Let Pods targets inherit deployment target from the app 24 | post_install do |installer| 25 | installer.pods_project.targets.each do |target| 26 | target.build_configurations.each do |configuration| 27 | ios_deployment_key = 'IPHONEOS_DEPLOYMENT_TARGET' 28 | pod_ios_deployment_target = Gem::Version.new(configuration.build_settings[ios_deployment_key]) 29 | configuration.build_settings.delete(ios_deployment_key) if pod_ios_deployment_target <= APP_IOS_DEPLOYMENT_TARGET 30 | end 31 | end 32 | 33 | yellow_marker = "\033[33m" 34 | reset_marker = "\033[0m" 35 | puts "#{yellow_marker}The abstract target warning below is expected. Feel free to ignore 36 | it.#{reset_marker}" 37 | end 38 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - SwiftLint (0.54.0) 3 | 4 | DEPENDENCIES: 5 | - SwiftLint (= 0.54.0) 6 | 7 | SPEC REPOS: 8 | trunk: 9 | - SwiftLint 10 | 11 | SPEC CHECKSUMS: 12 | SwiftLint: c1de071d9d08c8aba837545f6254315bc900e211 13 | 14 | PODFILE CHECKSUM: c0da9313733b88a1d938ba6a329dd46b895c7dea 15 | 16 | COCOAPODS: 1.15.2 17 | -------------------------------------------------------------------------------- /Sources/APIInterface/FilePart.m: -------------------------------------------------------------------------------- 1 | #import "FilePart.h" 2 | 3 | @implementation FilePart 4 | 5 | - (instancetype)initWithParameterName:(NSString *)parameterName 6 | url:(NSURL *)url 7 | fileName:(NSString *)fileName 8 | mimeType:(NSString *)mimeType 9 | { 10 | self = [super init]; 11 | self.parameterName = parameterName; 12 | self.url = url; 13 | self.fileName = fileName; 14 | self.mimeType = mimeType; 15 | return self; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Sources/APIInterface/include/FilePart.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /// Represents the infomartion needed to encode a file on a multipart form request. 4 | @interface FilePart: NSObject 5 | 6 | @property (strong, nonatomic) NSString * _Nonnull parameterName; 7 | @property (strong, nonatomic) NSURL * _Nonnull url; 8 | @property (strong, nonatomic) NSString * _Nonnull fileName; 9 | @property (strong, nonatomic) NSString * _Nonnull mimeType; 10 | 11 | - (instancetype _Nonnull)initWithParameterName:(NSString * _Nonnull)parameterName 12 | url:(NSURL * _Nonnull)url 13 | fileName:(NSString * _Nonnull)fileName 14 | mimeType:(NSString * _Nonnull)mimeType; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Sources/APIInterface/include/WordPressComRESTAPIVersion.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef NS_ENUM(NSInteger, WordPressComRESTAPIVersion) { 4 | WordPressComRESTAPIVersion_1_0 = 1000, 5 | WordPressComRESTAPIVersion_1_1 = 1001, 6 | WordPressComRESTAPIVersion_1_2 = 1002, 7 | WordPressComRESTAPIVersion_1_3 = 1003, 8 | WordPressComRESTAPIVersion_2_0 = 2000 9 | }; 10 | -------------------------------------------------------------------------------- /Sources/APIInterface/include/WordPressComRESTAPIVersionedPathBuilder.h: -------------------------------------------------------------------------------- 1 | #import 2 | #if SWIFT_PACKAGE 3 | #import "WordPressComRESTAPIVersion.h" 4 | #else 5 | #import 6 | #endif 7 | 8 | @interface WordPressComRESTAPIVersionedPathBuilder: NSObject 9 | 10 | + (NSString *)pathForEndpoint:(NSString *)endpoint 11 | withVersion:(WordPressComRESTAPIVersion)apiVersion 12 | NS_SWIFT_NAME(path(forEndpoint:withVersion:)); 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Sources/APIInterface/include/WordPressComRestApiErrorDomain.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /// Error domain of `NSError` instances that are converted from `WordPressComRestApiEndpointError` 4 | /// and `WordPressAPIError` instances. 5 | /// 6 | /// This matches the compiler generated value and is used to ensure consistent error domain across error types and SPM or Framework build modes. 7 | /// 8 | /// See `extension WordPressComRestApiEndpointError: CustomNSError` in CoreAPI package for context. 9 | static NSString *const _Nonnull WordPressComRestApiErrorDomain = @"WordPressKit.WordPressComRestApiError"; 10 | -------------------------------------------------------------------------------- /Sources/BasicBlogAPIObjc/ServiceRemoteWordPressComREST.m: -------------------------------------------------------------------------------- 1 | #import "ServiceRemoteWordPressComREST.h" 2 | #import "WPKit-Swift.h" 3 | 4 | @implementation ServiceRemoteWordPressComREST 5 | 6 | - (instancetype)initWithWordPressComRestApi:(WordPressComRestApi *)wordPressComRestApi { 7 | 8 | NSParameterAssert([wordPressComRestApi isKindOfClass:[WordPressComRestApi class]]); 9 | 10 | self = [super init]; 11 | if (self) { 12 | _wordPressComRestApi = wordPressComRestApi; 13 | _wordPressComRESTAPI = wordPressComRestApi; 14 | } 15 | return self; 16 | } 17 | 18 | #pragma mark - Request URL construction 19 | 20 | - (NSString *)pathForEndpoint:(NSString *)resourceUrl 21 | withVersion:(WordPressComRESTAPIVersion)apiVersion 22 | { 23 | NSParameterAssert([resourceUrl isKindOfClass:[NSString class]]); 24 | 25 | return [WordPressComRESTAPIVersionedPathBuilder pathForEndpoint:resourceUrl 26 | withVersion:apiVersion]; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Sources/CoreAPI/Date+WordPressCom.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Date { 4 | 5 | /// Parses a date string 6 | /// 7 | /// Dates in the format specified in http://www.w3.org/TR/NOTE-datetime should be OK. 8 | /// The kind of dates returned by the REST API should match that format, even if the doc promises ISO 8601. 9 | /// 10 | /// Parsing the full ISO 8601, or even RFC 3339 is more complex than this, and makes no sense right now. 11 | /// 12 | /// - SeeAlso: [WordPress.com REST API docs](https://developer.wordpress.com/docs/api/) 13 | /// - Warning: This method doesn't support fractional seconds or dates with leap seconds (23:59:60 turns into 23:59:00) 14 | static func with(wordPressComJSONString jsonString: String) -> Date? { 15 | DateFormatter.wordPressCom.date(from: jsonString) 16 | } 17 | 18 | var wordPressComJSONString: String { 19 | DateFormatter.wordPressCom.string(from: self) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/CoreAPI/DateFormatter+WordPressCom.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension DateFormatter { 4 | 5 | /// A `DateFormatter` configured to manage dates compatible with the WordPress.com API. 6 | /// 7 | /// - SeeAlso: [https://developer.wordpress.com/docs/api/](https://developer.wordpress.com/docs/api/) 8 | static let wordPressCom: DateFormatter = { 9 | let formatter = DateFormatter() 10 | formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ" 11 | formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) as TimeZone 12 | formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale 13 | return formatter 14 | }() 15 | } 16 | -------------------------------------------------------------------------------- /Sources/CoreAPI/Either.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Either { 4 | case left(L) 5 | case right(R) 6 | 7 | func map(left: (L) -> T, right: (R) -> T) -> T { 8 | switch self { 9 | case let .left(value): 10 | return left(value) 11 | case let .right(value): 12 | return right(value) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/CoreAPI/Result+Callback.swift: -------------------------------------------------------------------------------- 1 | public extension Swift.Result { 2 | 3 | // Notice there are no explicit unit tests for this utility because it is implicitly tested via the consuming code's tests. 4 | func execute(onSuccess: (Success) -> Void, onFailure: (Failure) -> Void) { 5 | switch self { 6 | case .success(let value): onSuccess(value) 7 | case .failure(let error): onFailure(error) 8 | } 9 | } 10 | 11 | func execute(_ completion: (Self) -> Void) { 12 | completion(self) 13 | } 14 | 15 | func eraseToError() -> Result { 16 | mapError { $0 } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/CoreAPI/WebauthChallengeInfo.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Type that represents the Webauthn challenge info return by Wordpress.com 4 | /// 5 | @objc public class WebauthnChallengeInfo: NSObject { 6 | /// Challenge to be signed. 7 | /// 8 | @objc public var challenge = "" 9 | 10 | /// The website this request is for 11 | /// 12 | @objc public var rpID = "" 13 | 14 | /// Nonce required by Wordpress.com to verify the signed challenge 15 | /// 16 | @objc public var twoStepNonce = "" 17 | 18 | /// Allowed credential IDs. 19 | /// 20 | @objc public var allowedCredentialIDs: [String] = [] 21 | 22 | init(challenge: String, rpID: String, twoStepNonce: String, allowedCredentialIDs: [String]) { 23 | self.challenge = challenge 24 | self.rpID = rpID 25 | self.twoStepNonce = twoStepNonce 26 | self.allowedCredentialIDs = allowedCredentialIDs 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Logging/WPKitLogging.swift: -------------------------------------------------------------------------------- 1 | func WPKitLogError(_ format: String, _ arguments: CVarArg...) { 2 | withVaList(arguments) { WPKitLogvError(format, $0) } 3 | } 4 | 5 | func WPKitLogWarning(_ format: String, _ arguments: CVarArg...) { 6 | withVaList(arguments) { WPKitLogvWarning(format, $0) } 7 | } 8 | 9 | func WPKitLogInfo(_ format: String, _ arguments: CVarArg...) { 10 | withVaList(arguments) { WPKitLogvInfo(format, $0) } 11 | } 12 | 13 | func WPKitLogDebug(_ format: String, _ arguments: CVarArg...) { 14 | withVaList(arguments) { WPKitLogvDebug(format, $0) } 15 | } 16 | 17 | func WPKitLogVerbose(_ format: String, _ arguments: CVarArg...) { 18 | withVaList(arguments) { WPKitLogvVerbose(format, $0) } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/AutomatedTransferStatus.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A helper object encapsulating a status of Automated Transfer operation. 4 | public struct AutomatedTransferStatus { 5 | public enum State: String, RawRepresentable { 6 | case active 7 | case backfilling 8 | case complete 9 | case error 10 | case notFound = "not found" 11 | case unknownStatus = "unknown_status" 12 | case uploading 13 | case pending 14 | } 15 | 16 | public let status: State 17 | public let step: Int? 18 | public let totalSteps: Int? 19 | 20 | init?(status statusString: String) { 21 | guard let status = State(rawValue: statusString) else { 22 | return nil 23 | } 24 | 25 | self.status = status 26 | self.step = nil 27 | self.totalSteps = nil 28 | } 29 | 30 | init?(status statusString: String, step: Int, totalSteps: Int) { 31 | guard let status = State(rawValue: statusString) else { 32 | return nil 33 | } 34 | 35 | self.status = status 36 | self.step = step 37 | self.totalSteps = totalSteps 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/Blaze/BlazeCampaignsSearchResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class BlazeCampaignsSearchResponse: Decodable { 4 | public let campaigns: [BlazeCampaign]? 5 | public let totalItems: Int? 6 | public let totalPages: Int? 7 | public let page: Int? 8 | 9 | public init(totalItems: Int?, campaigns: [BlazeCampaign]?, totalPages: Int?, page: Int?) { 10 | self.totalItems = totalItems 11 | self.campaigns = campaigns 12 | self.totalPages = totalPages 13 | self.page = page 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/Extensions/Date+endOfDay.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Date { 4 | /// Returns a Date representing the last second of the given day 5 | /// 6 | func endOfDay() -> Date? { 7 | Calendar.current.date(byAdding: .second, value: 86399, to: self) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/Extensions/Enum+UnknownCaseRepresentable.swift: -------------------------------------------------------------------------------- 1 | /// Allows automatic defaulting to `unknown` for any Enum that conforms to `UnknownCaseRepresentable` 2 | /// Credits: https://www.latenightswift.com/2019/02/04/unknown-enum-cases/ 3 | protocol UnknownCaseRepresentable: RawRepresentable, CaseIterable where RawValue: Equatable { 4 | static var unknownCase: Self { get } 5 | } 6 | 7 | extension UnknownCaseRepresentable { 8 | public init(rawValue: RawValue) { 9 | let value = Self.allCases.first(where: { $0.rawValue == rawValue }) 10 | self = value ?? Self.unknownCase 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/Extensions/NSAttributedString+extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension NSAttributedString { 4 | /// This helper method returns a new NSAttributedString instance, with all of the the leading / trailing newLines 5 | /// characters removed. 6 | /// 7 | @objc public func trimNewlines() -> NSAttributedString { 8 | guard let trimmed = mutableCopy() as? NSMutableAttributedString else { 9 | return self 10 | } 11 | 12 | let characterSet = CharacterSet.newlines 13 | 14 | // Trim: Leading 15 | var range = (trimmed.string as NSString).rangeOfCharacter(from: characterSet) 16 | 17 | while range.length != 0 && range.location == 0 { 18 | trimmed.replaceCharacters(in: range, with: String()) 19 | range = (trimmed.string as NSString).rangeOfCharacter(from: characterSet) 20 | } 21 | 22 | // Trim Trailing 23 | range = (trimmed.string as NSString).rangeOfCharacter(from: characterSet, options: .backwards) 24 | 25 | while range.length != 0 && NSMaxRange(range) == trimmed.length { 26 | trimmed.replaceCharacters(in: range, with: String()) 27 | range = (trimmed.string as NSString).rangeOfCharacter(from: characterSet, options: .backwards) 28 | } 29 | 30 | return trimmed 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/Extensions/NSMutableParagraphStyle+extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension NSMutableParagraphStyle { 4 | @objc convenience public init(minLineHeight: CGFloat, lineBreakMode: NSLineBreakMode, alignment: NSTextAlignment) { 5 | self.init() 6 | self.minimumLineHeight = minLineHeight 7 | self.lineBreakMode = lineBreakMode 8 | self.alignment = alignment 9 | } 10 | 11 | @objc convenience public init(minLineHeight: CGFloat, maxLineHeight: CGFloat, lineBreakMode: NSLineBreakMode, alignment: NSTextAlignment) { 12 | self.init(minLineHeight: minLineHeight, lineBreakMode: lineBreakMode, alignment: alignment) 13 | self.maximumLineHeight = maxLineHeight 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/Jetpack Scan/JetpackCredentials.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A limited representation of the users Jetpack credentials 4 | public struct JetpackScanCredentials: Decodable { 5 | public let host: String? 6 | public let port: Int? 7 | public let user: String? 8 | public let path: String? 9 | public let type: String 10 | public let role: String 11 | public let stillValid: Bool 12 | } 13 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/JetpackBackup.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct JetpackBackup: Decodable { 4 | 5 | // Common 6 | public let backupPoint: Date 7 | public let downloadID: Int 8 | public let rewindID: String 9 | public let startedAt: Date 10 | 11 | // Prepare backup 12 | public let progress: Int? 13 | 14 | // Get backup status 15 | public let downloadCount: Int? 16 | public let url: String? 17 | public let validUntil: Date? 18 | 19 | private enum CodingKeys: String, CodingKey { 20 | case backupPoint 21 | case downloadID = "downloadId" 22 | case rewindID = "rewindId" 23 | case startedAt 24 | case progress 25 | case downloadCount 26 | case url 27 | case validUntil 28 | } 29 | 30 | public init(backupPoint: Date, downloadID: Int, rewindID: String, startedAt: Date, progress: Int?, downloadCount: Int?, url: String?, validUntil: Date?) { 31 | self.backupPoint = backupPoint 32 | self.downloadID = downloadID 33 | self.rewindID = rewindID 34 | self.startedAt = startedAt 35 | self.progress = progress 36 | self.downloadCount = downloadCount 37 | self.url = url 38 | self.validUntil = validUntil 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/JetpackRestoreTypes.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct JetpackRestoreTypes { 4 | public var themes: Bool 5 | public var plugins: Bool 6 | public var uploads: Bool 7 | public var sqls: Bool 8 | public var roots: Bool 9 | public var contents: Bool 10 | 11 | public init(themes: Bool = true, 12 | plugins: Bool = true, 13 | uploads: Bool = true, 14 | sqls: Bool = true, 15 | roots: Bool = true, 16 | contents: Bool = true) { 17 | self.themes = themes 18 | self.plugins = plugins 19 | self.uploads = uploads 20 | self.sqls = sqls 21 | self.roots = roots 22 | self.contents = contents 23 | } 24 | 25 | func toDictionary() -> [String: AnyObject] { 26 | return [ 27 | "themes": themes as AnyObject, 28 | "plugins": plugins as AnyObject, 29 | "uploads": uploads as AnyObject, 30 | "sqls": sqls as AnyObject, 31 | "roots": roots as AnyObject, 32 | "contents": contents as AnyObject 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/KeyringConnection.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// KeyringConnection represents a keyring connected to a particular external service. 4 | /// We only rarely need keyring data and we don't really need to persist it. For these 5 | /// reasons KeyringConnection is treated like a model, even though it is not an NSManagedObject, 6 | /// but also treated like it is a Remote Object. 7 | /// 8 | open class KeyringConnection: NSObject { 9 | @objc open var additionalExternalUsers = [KeyringConnectionExternalUser]() 10 | @objc open var dateIssued = Date() 11 | @objc open var dateExpires: Date? 12 | @objc open var externalID = "" // Some services uses strings for their IDs 13 | @objc open var externalName = "" 14 | @objc open var externalDisplay = "" 15 | @objc open var externalProfilePicture = "" 16 | @objc open var label = "" 17 | @objc open var keyringID: NSNumber = 0 18 | @objc open var refreshURL = "" 19 | @objc open var service = "" 20 | @objc open var status = "" 21 | @objc open var type = "" 22 | @objc open var userID: NSNumber = 0 23 | } 24 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/KeyringConnectionExternalUser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// KeyringConnectionExternalUser represents an additional user account on the 4 | /// external service that could be used other than the default account. 5 | /// 6 | open class KeyringConnectionExternalUser: NSObject { 7 | @objc open var externalID = "" 8 | @objc open var externalName = "" 9 | @objc open var externalCategory = "" 10 | @objc open var externalProfilePicture = "" 11 | } 12 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/Plugins/SitePlugin.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct SitePlugins: Codable { 4 | public var plugins: [PluginState] 5 | public var capabilities: SitePluginCapabilities 6 | 7 | } 8 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/Plugins/SitePluginCapabilities.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct SitePluginCapabilities: Equatable, Codable { 4 | public let modify: Bool 5 | public let autoupdate: Bool 6 | 7 | public static func ==(lhs: SitePluginCapabilities, rhs: SitePluginCapabilities) -> Bool { 8 | return lhs.modify == rhs.modify 9 | && lhs.autoupdate == rhs.autoupdate 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteBlogJetpackModulesSettings.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This struct encapsulates the *remote* Jetpack modules settings available for a Blog entity 4 | /// 5 | public struct RemoteBlogJetpackModulesSettings { 6 | /// Indicates whether the Jetpack site serves images from our server. 7 | /// 8 | public let serveImagesFromOurServers: Bool 9 | 10 | public init(serveImagesFromOurServers: Bool) { 11 | self.serveImagesFromOurServers = serveImagesFromOurServers 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteBlogJetpackMonitorSettings.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This struct encapsulates the *remote* Jetpack monitor settings available for a Blog entity 4 | /// 5 | public struct RemoteBlogJetpackMonitorSettings { 6 | 7 | /// Indicates whether the Jetpack site's monitor notifications should be sent by email 8 | /// 9 | public let monitorEmailNotifications: Bool 10 | 11 | /// Indicates whether the Jetpack site's monitor notifications should be sent by push notifications 12 | /// 13 | public let monitorPushNotifications: Bool 14 | 15 | public init(monitorEmailNotifications: Bool, 16 | monitorPushNotifications: Bool) { 17 | self.monitorEmailNotifications = monitorEmailNotifications 18 | self.monitorPushNotifications = monitorPushNotifications 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteComment.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RemoteComment : NSObject 4 | @property (nonatomic, strong) NSNumber *commentID; 5 | @property (nonatomic, strong) NSNumber *authorID; 6 | @property (nonatomic, strong) NSString *author; 7 | @property (nonatomic, strong) NSString *authorEmail; 8 | @property (nonatomic, strong) NSString *authorUrl; 9 | @property (nonatomic, strong) NSString *authorAvatarURL; 10 | @property (nonatomic, strong) NSString *authorIP; 11 | @property (nonatomic, strong) NSString *content; 12 | @property (nonatomic, strong) NSString *rawContent; 13 | @property (nonatomic, strong) NSDate *date; 14 | @property (nonatomic, strong) NSString *link; 15 | @property (nonatomic, strong) NSNumber *parentID; 16 | @property (nonatomic, strong) NSNumber *postID; 17 | @property (nonatomic, strong) NSString *postTitle; 18 | @property (nonatomic, strong) NSString *status; 19 | @property (nonatomic, strong) NSString *type; 20 | @property (nonatomic) BOOL isLiked; 21 | @property (nonatomic, strong) NSNumber *likeCount; 22 | @property (nonatomic) BOOL canModerate; 23 | @end 24 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteComment.m: -------------------------------------------------------------------------------- 1 | #import "RemoteComment.h" 2 | 3 | @implementation RemoteComment 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteHomepageType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The type of homepage used by a site: blog posts (.posts), or static pages (.page). 4 | public enum RemoteHomepageType { 5 | case page 6 | case posts 7 | 8 | /// True if the site uses a page for its front page, rather than blog posts 9 | internal var isPageOnFront: Bool { 10 | return self == .page 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteInviteLink.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct RemoteInviteLink { 4 | public let inviteKey: String 5 | public let role: String 6 | public let isPending: Bool 7 | public let inviteDate: Date 8 | public let groupInvite: Bool 9 | public let expiry: Int64 10 | public let link: String 11 | 12 | init(dict: [String: Any]) { 13 | var date = Date() 14 | if let inviteDate = dict["invite_date"] as? String, 15 | let formattedDate = ISO8601DateFormatter().date(from: inviteDate) { 16 | date = formattedDate 17 | } 18 | inviteKey = dict["invite_key"] as? String ?? "" 19 | role = dict["role"] as? String ?? "" 20 | isPending = dict["is_pending"] as? Bool ?? false 21 | inviteDate = date 22 | groupInvite = dict["is_group_invite"] as? Bool ?? false 23 | expiry = dict["expiry"] as? Int64 ?? 0 24 | link = dict["link"] as? String ?? "" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteMedia.m: -------------------------------------------------------------------------------- 1 | #import "RemoteMedia.h" 2 | #import 3 | 4 | @implementation RemoteMedia 5 | 6 | - (NSString *)debugDescription { 7 | NSDictionary *properties = [self debugProperties]; 8 | return [NSString stringWithFormat:@"<%@: %p> (%@)", NSStringFromClass([self class]), self, properties]; 9 | } 10 | 11 | - (NSDictionary *)debugProperties { 12 | unsigned int propertyCount; 13 | objc_property_t *properties = class_copyPropertyList([RemoteMedia class], &propertyCount); 14 | NSMutableDictionary *debugProperties = [NSMutableDictionary dictionaryWithCapacity:propertyCount]; 15 | for (int i = 0; i < propertyCount; i++) 16 | { 17 | // Add property name to array 18 | objc_property_t property = properties[i]; 19 | const char *propertyName = property_getName(property); 20 | id value = [self valueForKey:@(propertyName)]; 21 | if (value == nil) { 22 | value = [NSNull null]; 23 | } 24 | [debugProperties setObject:value forKey:@(propertyName)]; 25 | } 26 | free(properties); 27 | return [NSDictionary dictionaryWithDictionary:debugProperties]; 28 | } 29 | 30 | @end -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteMenu.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objcMembers public class RemoteMenu: NSObject { 4 | 5 | public var menuID: NSNumber? 6 | public var details: String? 7 | public var name: String? 8 | public var items: [RemoteMenuItem]? 9 | public var locationNames: [String]? 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteMenuItem.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objcMembers public class RemoteMenuItem: NSObject { 4 | 5 | public var itemID: NSNumber? 6 | public var contentID: NSNumber? 7 | public var details: String? 8 | public var linkTarget: String? 9 | public var linkTitle: String? 10 | public var name: String? 11 | public var type: String? 12 | public var typeFamily: String? 13 | public var typeLabel: String? 14 | public var urlStr: String? 15 | public var classes: [String]? 16 | public var children: [RemoteMenuItem]? 17 | public weak var parentItem: RemoteMenuItem? 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteMenuLocation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objcMembers public class RemoteMenuLocation: NSObject { 4 | 5 | public var name: String? 6 | public var defaultState: String? 7 | public var details: String? 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemotePostAutosave.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Encapsulates the autosave attributes of a post. 4 | @objc 5 | @objcMembers 6 | public class RemotePostAutosave: NSObject { 7 | public var title: String? 8 | public var excerpt: String? 9 | public var content: String? 10 | public var modifiedDate: Date? 11 | public var identifier: NSNumber? 12 | public var authorID: String? 13 | public var postID: NSNumber? 14 | public var previewURL: String? 15 | } 16 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemotePostCategory.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RemotePostCategory : NSObject 4 | @property (nonatomic, strong) NSNumber *categoryID; 5 | @property (nonatomic, strong) NSString *name; 6 | @property (nonatomic, strong) NSNumber *parentID; 7 | @end 8 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemotePostCategory.m: -------------------------------------------------------------------------------- 1 | #import "RemotePostCategory.h" 2 | 3 | @implementation RemotePostCategory 4 | 5 | - (NSString *)debugDescription { 6 | NSDictionary *properties = @{ 7 | @"ID": self.categoryID, 8 | @"name": self.name, 9 | @"parent": self.parentID, 10 | }; 11 | return [NSString stringWithFormat:@"<%@: %p> (%@)", NSStringFromClass([self class]), self, properties]; 12 | } 13 | 14 | - (NSString *)description { 15 | return [NSString stringWithFormat:@"<%@: %p> %@[%@]", NSStringFromClass([self class]), self, self.name, self.categoryID]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemotePostTag.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RemotePostTag : NSObject 4 | 5 | @property (nonatomic, strong) NSNumber *tagID; 6 | @property (nonatomic, strong) NSString *name; 7 | @property (nonatomic, strong) NSString *slug; 8 | @property (nonatomic, strong) NSString *tagDescription; 9 | @property (nonatomic, strong) NSNumber *postCount; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemotePostTag.m: -------------------------------------------------------------------------------- 1 | #import "RemotePostTag.h" 2 | 3 | @implementation RemotePostTag 4 | 5 | - (NSString *)debugDescription 6 | { 7 | NSDictionary *properties = @{ 8 | @"ID": self.tagID, 9 | @"name": self.name 10 | }; 11 | return [NSString stringWithFormat:@"<%@: %p> (%@)", NSStringFromClass([self class]), self, properties]; 12 | } 13 | 14 | - (NSString *)description 15 | { 16 | return [NSString stringWithFormat:@"<%@: %p> %@[%@]", NSStringFromClass([self class]), self, self.name, self.tagID]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemotePostType.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RemotePostType : NSObject 4 | 5 | @property (nonatomic, strong) NSNumber *apiQueryable; 6 | @property (nonatomic, strong) NSString *name; 7 | @property (nonatomic, strong) NSString *label; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemotePostType.m: -------------------------------------------------------------------------------- 1 | #import "RemotePostType.h" 2 | 3 | @implementation RemotePostType 4 | 5 | - (NSString *)debugDescription 6 | { 7 | NSDictionary *properties = @{ 8 | @"name": self.name, 9 | @"label": self.label, 10 | @"apiQueryable": self.apiQueryable 11 | }; 12 | return [NSString stringWithFormat:@"<%@: %p> (%@)", NSStringFromClass([self class]), self, properties]; 13 | } 14 | 15 | - (NSString *)description 16 | { 17 | return [NSString stringWithFormat:@"<%@: %p> %@[%@], apiQueryable=%@", NSStringFromClass([self class]), self, self.name, self.label, self.apiQueryable]; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteProfile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public class RemoteProfile { 4 | public let bio: String 5 | public let displayName: String 6 | public let email: String 7 | public let firstName: String 8 | public let lastName: String 9 | public let nicename: String 10 | public let nickname: String 11 | public let url: String 12 | public let userID: Int 13 | public let username: String 14 | 15 | public init(dictionary: NSDictionary) { 16 | bio = dictionary.string(forKey: "bio") ?? "" 17 | displayName = dictionary.string(forKey: "display_name") ?? "" 18 | email = dictionary.string(forKey: "email") ?? "" 19 | firstName = dictionary.string(forKey: "first_name") ?? "" 20 | lastName = dictionary.string(forKey: "last_name") ?? "" 21 | nicename = dictionary.string(forKey: "nicename") ?? "" 22 | nickname = dictionary.string(forKey: "nickname") ?? "" 23 | url = dictionary.string(forKey: "url") ?? "" 24 | userID = dictionary.number(forKey: "user_id")?.intValue ?? 0 25 | username = dictionary.string(forKey: "username") ?? "" 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemotePublicizeConnection.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc open class RemotePublicizeConnection: NSObject { 4 | @objc open var connectionID: NSNumber = 0 5 | @objc open var dateIssued = Date() 6 | @objc open var dateExpires: Date? 7 | @objc open var externalID = "" 8 | @objc open var externalName = "" 9 | @objc open var externalDisplay = "" 10 | @objc open var externalProfilePicture = "" 11 | @objc open var externalProfileURL = "" 12 | @objc open var externalFollowerCount: NSNumber = 0 13 | @objc open var keyringConnectionID: NSNumber = 0 14 | @objc open var keyringConnectionUserID: NSNumber = 0 15 | @objc open var label = "" 16 | @objc open var refreshURL = "" 17 | @objc open var service = "" 18 | @objc open var shared = false 19 | @objc open var status = "" 20 | @objc open var siteID: NSNumber = 0 21 | @objc open var userID: NSNumber = 0 22 | } 23 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemotePublicizeInfo.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct RemotePublicizeInfo: Decodable { 4 | public let shareLimit: Int 5 | public let toBePublicizedCount: Int 6 | public let sharedPostsCount: Int 7 | public let sharesRemaining: Int 8 | 9 | private enum CodingKeys: CodingKey { 10 | case shareLimit 11 | case toBePublicizedCount 12 | case sharedPostsCount 13 | case sharesRemaining 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemotePublicizeService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc open class RemotePublicizeService: NSObject { 4 | @objc open var connectURL = "" 5 | @objc open var detail = "" 6 | @objc open var externalUsersOnly = false 7 | @objc open var icon = "" 8 | @objc open var jetpackSupport = false 9 | @objc open var jetpackModuleRequired = "" 10 | @objc open var label = "" 11 | @objc open var multipleExternalUserIDSupport = false 12 | @objc open var order: NSNumber = 0 13 | @objc open var serviceID = "" 14 | @objc open var type = "" 15 | @objc open var status = "" 16 | } 17 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteReaderCrossPostMeta.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class RemoteReaderCrossPostMeta: NSObject { 4 | @objc open var postID: NSNumber = 0 5 | @objc open var siteID: NSNumber = 0 6 | @objc open var siteURL = "" 7 | @objc open var postURL = "" 8 | @objc open var commentURL = "" 9 | } 10 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteReaderInterest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ReaderInterestEnvelope: Decodable { 4 | var interests: [RemoteReaderInterest] 5 | } 6 | 7 | public struct RemoteReaderInterest: Decodable { 8 | public var title: String 9 | public var slug: String 10 | 11 | private enum CodingKeys: String, CodingKey { 12 | case title 13 | case slug = "slug" 14 | } 15 | 16 | public init(from decoder: Decoder) throws { 17 | let container = try decoder.container(keyedBy: CodingKeys.self) 18 | title = try container.decode(String.self, forKey: .title) 19 | slug = try container.decode(String.self, forKey: .slug) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteReaderPost.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ReaderPostsEnvelope: Decodable { 4 | var posts: [RemoteReaderPost] 5 | var nextPageHandle: String? 6 | 7 | private enum CodingKeys: String, CodingKey { 8 | case posts 9 | case nextPageHandle = "next_page_handle" 10 | } 11 | 12 | public init(from decoder: Decoder) throws { 13 | let container = try decoder.container(keyedBy: CodingKeys.self) 14 | let postDictionary = try container.decode([String: Any].self, forKey: .posts) 15 | posts = [RemoteReaderPost(dictionary: postDictionary)] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteReaderSite.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objcMembers public class RemoteReaderSite: NSObject { 4 | 5 | public var recordID: NSNumber! 6 | public var siteID: NSNumber! 7 | public var feedID: NSNumber! 8 | public var name: String! 9 | public var path: String! // URL 10 | public var icon: String! // Sites only 11 | public var isSubscribed: Bool = false 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteReaderSiteInfoSubscription.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Mapping keys 4 | private struct CodingKeys { 5 | static let sendPost = "send_posts" 6 | static let sendComments = "send_comments" 7 | static let postDeliveryFrequency = "post_delivery_frequency" 8 | } 9 | 10 | /// Site Info Post Subscription model 11 | @objc public class RemoteReaderSiteInfoSubscriptionPost: NSObject { 12 | @objc public var sendPosts: Bool 13 | 14 | @objc required public init(dictionary: [String: Any]) { 15 | self.sendPosts = (dictionary[CodingKeys.sendPost] as? Bool) ?? false 16 | super.init() 17 | } 18 | } 19 | 20 | /// Site Info Email Subscription model 21 | @objc public class RemoteReaderSiteInfoSubscriptionEmail: RemoteReaderSiteInfoSubscriptionPost { 22 | @objc public var sendComments: Bool 23 | @objc public var postDeliveryFrequency: String 24 | 25 | @objc required public init(dictionary: [String: Any]) { 26 | sendComments = (dictionary[CodingKeys.sendComments] as? Bool) ?? false 27 | postDeliveryFrequency = (dictionary[CodingKeys.postDeliveryFrequency] as? String) ?? "" 28 | super.init(dictionary: dictionary) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteShareAppContent.swift: -------------------------------------------------------------------------------- 1 | /// Defines the information structure used for recommending the app to others. 2 | /// 3 | public struct RemoteShareAppContent: Codable { 4 | /// A text content to share. 5 | public let message: String 6 | 7 | /// A URL string that directs the recipient to a page describing steps to get the app. 8 | public let link: String 9 | 10 | /// Convenience method that returns `link` as URL. 11 | public func linkURL() -> URL? { 12 | URL(string: link) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteSharingButton.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc open class RemoteSharingButton: NSObject { 4 | @objc open var buttonID = "" 5 | @objc open var name = "" 6 | @objc open var shortname = "" 7 | @objc open var custom = false 8 | @objc open var enabled = false 9 | @objc open var visibility: String? 10 | @objc open var order: NSNumber = 0 11 | } 12 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteSourcePostAttribution.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RemoteSourcePostAttribution : NSObject 4 | 5 | @property (nonatomic, strong) NSString *permalink; 6 | @property (nonatomic, strong) NSString *authorName; 7 | @property (nonatomic, strong) NSString *authorURL; 8 | @property (nonatomic, strong) NSString *blogName; 9 | @property (nonatomic, strong) NSString *blogURL; 10 | @property (nonatomic, strong) NSString *avatarURL; 11 | @property (nonatomic, strong) NSNumber *blogID; 12 | @property (nonatomic, strong) NSNumber *postID; 13 | @property (nonatomic, strong) NSNumber *likeCount; 14 | @property (nonatomic, strong) NSNumber *commentCount; 15 | @property (nonatomic, strong) NSArray *taxonomies; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteSourcePostAttribution.m: -------------------------------------------------------------------------------- 1 | #import "RemoteSourcePostAttribution.h" 2 | 3 | @implementation RemoteSourcePostAttribution 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteTaxonomyPaging.m: -------------------------------------------------------------------------------- 1 | #import "RemoteTaxonomyPaging.h" 2 | 3 | @implementation RemoteTaxonomyPaging 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteTheme.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RemoteTheme : NSObject 4 | 5 | @property (nonatomic, assign) BOOL active; 6 | @property (nonatomic, strong) NSString *type; 7 | @property (nonatomic, strong) NSString *author; 8 | @property (nonatomic, strong) NSString *authorUrl; 9 | @property (nonatomic, strong) NSString *desc; 10 | @property (nonatomic, strong) NSString *demoUrl; 11 | @property (nonatomic, strong) NSString *themeUrl; 12 | @property (nonatomic, strong) NSString *downloadUrl; 13 | @property (nonatomic, strong) NSDate *launchDate; 14 | @property (nonatomic, strong) NSString *name; 15 | @property (nonatomic, assign) NSInteger order; 16 | @property (nonatomic, strong) NSNumber *popularityRank; 17 | @property (nonatomic, strong) NSString *previewUrl; 18 | @property (nonatomic, strong) NSString *price; 19 | @property (nonatomic, strong) NSNumber *purchased; 20 | @property (nonatomic, strong) NSString *screenshotUrl; 21 | @property (nonatomic, strong) NSString *stylesheet; 22 | @property (nonatomic, strong) NSString *themeId; 23 | @property (nonatomic, strong) NSNumber *trendingRank; 24 | @property (nonatomic, strong) NSString *version; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteTheme.m: -------------------------------------------------------------------------------- 1 | #import "RemoteTheme.h" 2 | 3 | @implementation RemoteTheme 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteUser.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RemoteUser : NSObject 4 | 5 | @property (nonatomic, strong) NSNumber *userID; 6 | @property (nonatomic, strong) NSString *username; 7 | @property (nonatomic, strong) NSString *email; 8 | @property (nonatomic, strong) NSString *displayName; 9 | @property (nonatomic, strong) NSNumber *primaryBlogID; 10 | @property (nonatomic, strong) NSString *avatarURL; 11 | @property (nonatomic, strong) NSDate *dateCreated; 12 | @property (nonatomic, assign) BOOL emailVerified; 13 | @property (nonatomic, strong) NSNumber *linkedUserID; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/RemoteUser.m: -------------------------------------------------------------------------------- 1 | #import "RemoteUser.h" 2 | 3 | @implementation RemoteUser 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/Revisions/RemoteRevision.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Revision model 4 | /// 5 | public struct RemoteRevision: Codable { 6 | /// Revision id 7 | public var id: Int 8 | 9 | /// Optional post content 10 | public var postContent: String? 11 | 12 | /// Optional post excerpt 13 | public var postExcerpt: String? 14 | 15 | /// Optional post title 16 | public var postTitle: String? 17 | 18 | /// Optional post date 19 | public var postDateGmt: String? 20 | 21 | /// Optional post modified date 22 | public var postModifiedGmt: String? 23 | 24 | /// Optional post author id 25 | public var postAuthorId: String? 26 | 27 | /// Optional revision diff 28 | public var diff: RemoteDiff? 29 | 30 | /// Mapping keys 31 | private enum CodingKeys: String, CodingKey { 32 | case id = "id" 33 | case postContent = "post_content" 34 | case postExcerpt = "post_excerpt" 35 | case postTitle = "post_title" 36 | case postDateGmt = "post_date_gmt" 37 | case postModifiedGmt = "post_modified_gmt" 38 | case postAuthorId = "post_author" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/SessionDetails.swift: -------------------------------------------------------------------------------- 1 | public struct SessionDetails { 2 | let deviceId: String 3 | let platform: String 4 | let buildNumber: String 5 | let marketingVersion: String 6 | let identifier: String 7 | let osVersion: String 8 | } 9 | 10 | extension SessionDetails: Encodable { 11 | 12 | enum CodingKeys: String, CodingKey { 13 | case deviceId = "device_id" 14 | case platform = "platform" 15 | case buildNumber = "build_number" 16 | case marketingVersion = "marketing_version" 17 | case identifier = "identifier" 18 | case osVersion = "os_version" 19 | } 20 | 21 | init(deviceId: String, bundle: Bundle = .main) { 22 | self.deviceId = deviceId 23 | self.platform = "ios" 24 | self.buildNumber = bundle.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown" 25 | self.marketingVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" 26 | self.identifier = bundle.bundleIdentifier ?? "Unknown" 27 | self.osVersion = UIDevice.current.systemVersion 28 | } 29 | 30 | func dictionaryRepresentation() throws -> [String: AnyObject]? { 31 | let encoder = JSONEncoder() 32 | let data = try encoder.encode(self) 33 | return try JSONSerialization.jsonObject(with: data) as? [String: AnyObject] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/Stats/Insights/StatsEmailFollowersInsight.swift: -------------------------------------------------------------------------------- 1 | public struct StatsEmailFollowersInsight: Codable { 2 | public let emailFollowersCount: Int 3 | public let topEmailFollowers: [StatsFollower] 4 | 5 | public init(emailFollowersCount: Int, 6 | topEmailFollowers: [StatsFollower]) { 7 | self.emailFollowersCount = emailFollowersCount 8 | self.topEmailFollowers = topEmailFollowers 9 | } 10 | 11 | private enum CodingKeys: String, CodingKey { 12 | case emailFollowersCount = "total_email" 13 | case topEmailFollowers = "subscribers" 14 | } 15 | } 16 | 17 | extension StatsEmailFollowersInsight: StatsInsightData { 18 | 19 | // MARK: - StatsInsightData Conformance 20 | public static func queryProperties(with maxCount: Int) -> [String: String] { 21 | return ["type": "email", 22 | "max": String(maxCount)] 23 | } 24 | 25 | public static var pathComponent: String { 26 | return "stats/followers" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/WPCountry.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc public class WPCountry: NSObject, Codable { 4 | public var code: String? 5 | public var name: String? 6 | } 7 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Models/WPState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc public class WPState: NSObject, Codable { 4 | public var code: String? 5 | public var name: String? 6 | } 7 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Private/WPKit-Swift.h: -------------------------------------------------------------------------------- 1 | // Import this header instead of 2 | // This allows the pod to be built as a static or dynamic framework 3 | // See https://github.com/CocoaPods/CocoaPods/issues/7594 4 | #if __has_include("WordPressKit-Swift.h") 5 | #import "WordPressKit-Swift.h" 6 | #else 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/BlazeServiceRemote.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class BlazeServiceRemote: ServiceRemoteWordPressComREST { 4 | 5 | // MARK: - Campaigns 6 | 7 | /// Searches the campaigns for the site with the given ID. The campaigns are returned ordered by the post date. 8 | /// 9 | /// - parameters: 10 | /// - siteId: The site ID. 11 | /// - page: The response page. By default, returns the first page. 12 | open func searchCampaigns(forSiteId siteId: Int, page: Int = 1, callback: @escaping (Result) -> Void) { 13 | let endpoint = "sites/\(siteId)/wordads/dsp/api/v1/search/campaigns/site/\(siteId)" 14 | let path = self.path(forEndpoint: endpoint, withVersion: ._2_0) 15 | Task { @MainActor in 16 | let result = await self.wordPressComRestApi 17 | .perform( 18 | .get, 19 | URLString: path, 20 | parameters: ["page": page] as [String: AnyObject], 21 | jsonDecoder: JSONDecoder.apiDecoder, 22 | type: BlazeCampaignsSearchResponse.self 23 | ) 24 | .map { $0.body } 25 | .eraseToError() 26 | callback(result) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/BlogServiceRemoteXMLRPC.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | typedef void (^OptionsHandler)(NSDictionary *options); 6 | 7 | @interface BlogServiceRemoteXMLRPC : ServiceRemoteWordPressXMLRPC 8 | 9 | /** 10 | * @brief Synchronizes a blog's options. 11 | * 12 | * @note Available in XML-RPC only. 13 | * 14 | * @param success The block that will be executed on success. Can be nil. 15 | * @param failure The block that will be executed on failure. Can be nil. 16 | */ 17 | - (void)syncBlogOptionsWithSuccess:(OptionsHandler)success 18 | failure:(void (^)(NSError *error))failure; 19 | 20 | /** 21 | * @brief Update a blog's options. 22 | * 23 | * @note Available in XML-RPC only. 24 | * 25 | * @param success The block that will be executed on success. Can be nil. 26 | * @param failure The block that will be executed on failure. Can be nil. 27 | */ 28 | - (void)updateBlogOptionsWith:(NSDictionary *)remoteBlogOptions 29 | success:(SuccessHandler)success 30 | failure:(void (^)(NSError *error))failure; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/CommentServiceRemoteXMLRPC.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface CommentServiceRemoteXMLRPC : ServiceRemoteWordPressXMLRPC 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/MediaServiceRemoteXMLRPC.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface MediaServiceRemoteXMLRPC : ServiceRemoteWordPressXMLRPC 6 | @end 7 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/Plugin Management/PluginManagementClient.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol PluginManagementClient { 4 | func getPlugins(success: @escaping (SitePlugins) -> Void, failure: @escaping (Error) -> Void) 5 | func updatePlugin(pluginID: String, success: @escaping (PluginState) -> Void, failure: @escaping (Error) -> Void) 6 | func activatePlugin(pluginID: String, success: @escaping () -> Void, failure: @escaping (Error) -> Void) 7 | func deactivatePlugin(pluginID: String, success: @escaping () -> Void, failure: @escaping (Error) -> Void) 8 | func enableAutoupdates(pluginID: String, success: @escaping () -> Void, failure: @escaping (Error) -> Void) 9 | func disableAutoupdates(pluginID: String, success: @escaping () -> Void, failure: @escaping (Error) -> Void) 10 | func activateAndEnableAutoupdates(pluginID: String, success: @escaping () -> Void, failure: @escaping (Error) -> Void) 11 | func install(pluginSlug: String, success: @escaping (PluginState) -> Void, failure: @escaping (Error) -> Void) 12 | func remove(pluginID: String, success: @escaping () -> Void, failure: @escaping (Error) -> Void) 13 | } 14 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/PostServiceRemoteExtended.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol PostServiceRemoteExtended: PostServiceRemote { 4 | /// Returns a post with the given ID. 5 | /// 6 | /// - throws: ``PostServiceRemoteError`` or oher underlying errors 7 | /// (see ``WordPressAPIError``) 8 | func post(withID postID: Int) async throws -> RemotePost 9 | 10 | /// Creates a new post with the given parameters. 11 | func createPost(with parameters: RemotePostCreateParameters) async throws -> RemotePost 12 | 13 | /// Performs a partial update to the existing post. 14 | /// 15 | /// - throws: ``PostServiceRemoteError`` or oher underlying errors 16 | /// (see ``WordPressAPIError``) 17 | func patchPost(withID postID: Int, parameters: RemotePostUpdateParameters) async throws -> RemotePost 18 | 19 | /// Permanently deletes a post with the given ID. 20 | /// 21 | /// - throws: ``PostServiceRemoteError`` or oher underlying errors 22 | /// (see ``WordPressAPIError``) 23 | func deletePost(withID postID: Int) async throws 24 | } 25 | 26 | @frozen public enum PostServiceRemoteError: Error { 27 | /// 409 (Conflict) 28 | case conflict 29 | /// 404 (Not Found) 30 | case notFound 31 | } 32 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/PostServiceRemoteXMLRPC.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface PostServiceRemoteXMLRPC : ServiceRemoteWordPressXMLRPC 6 | 7 | + (RemotePost *)remotePostFromXMLRPCDictionary:(NSDictionary *)xmlrpcDictionary; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/QR Login/QRLoginValidationResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct QRLoginValidationResponse: Decodable { 4 | /// The name of the browser that the user has requested the login from 5 | /// IE: Chrome, Firefox 6 | /// This may be null if the browser could not be determined 7 | public var browser: String? 8 | 9 | /// The City, State the user has requested the login from 10 | /// IE: Columbus, Ohio 11 | public var location: String 12 | } 13 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/ReaderServiceDeliveryFrequency.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Delivery frequency values for email notifications 4 | /// 5 | /// - daily: daily frequency 6 | /// - instantly: instantly frequency 7 | /// - weekly: weekly frequency 8 | @frozen public enum ReaderServiceDeliveryFrequency: String { 9 | case daily 10 | case instantly 11 | case weekly 12 | } 13 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/ReaderTopicServiceError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ReaderTopicServiceError: Error { 4 | case invalidId 5 | case topicNotfound(id: Int) 6 | case remoteResponse(message: String?, url: String) 7 | 8 | public var description: String { 9 | switch self { 10 | case .invalidId: 11 | return "Invalid id: an id must be valid or not nil" 12 | 13 | case .topicNotfound(let id): 14 | let localizedString = NSLocalizedString("Topic not found for id:", 15 | comment: "Used when a Reader Topic is not found for a specific id") 16 | return localizedString + " \(id)" 17 | 18 | case .remoteResponse(let message, let url): 19 | let localizedString = NSLocalizedString("An error occurred while processing your request: ", 20 | comment: "Used when a remote response doesn't have a specific message for a specific request") 21 | return message ?? localizedString + " \(url)" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/RemoteConfigRemote.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class RemoteConfigRemote: ServiceRemoteWordPressComREST { 4 | 5 | public typealias RemoteConfigDictionary = [String: Any] 6 | public typealias RemoteConfigResponseCallback = (Result) -> Void 7 | 8 | public enum RemoteConfigRemoteError: Error { 9 | case InvalidDataError 10 | } 11 | 12 | open func getRemoteConfig(callback: @escaping RemoteConfigResponseCallback) { 13 | 14 | let endpoint = "mobile/remote-config" 15 | let path = self.path(forEndpoint: endpoint, withVersion: ._2_0) 16 | 17 | wordPressComRESTAPI.get(path, 18 | parameters: nil, 19 | success: { response, _ in 20 | if let remoteConfigDictionary = response as? [String: Any] { 21 | callback(.success(remoteConfigDictionary)) 22 | } else { 23 | callback(.failure(RemoteConfigRemoteError.InvalidDataError)) 24 | } 25 | 26 | }, failure: { error, response in 27 | WPKitLogError("Error retrieving remote config values") 28 | WPKitLogError("\(error)") 29 | 30 | if let response = response { 31 | WPKitLogDebug("Response Code: \(response.statusCode)") 32 | } 33 | 34 | callback(.failure(error)) 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/ServiceRemoteWordPressXMLRPC.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class WordPressOrgXMLRPCApi; 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | 7 | @interface ServiceRemoteWordPressXMLRPC : NSObject 8 | 9 | - (id)initWithApi:(WordPressOrgXMLRPCApi *)api username:(NSString *)username password:(NSString *)password; 10 | 11 | @property (nonatomic, readonly) WordPressOrgXMLRPCApi *api; 12 | 13 | - (NSArray *)defaultXMLRPCArguments; 14 | - (NSArray *)XMLRPCArgumentsWithExtra:(_Nullable id)extra; 15 | - (NSArray *)XMLRPCArgumentsWithExtraDefaults:(NSArray *)extraDefaults andExtra:(_Nullable id)extra; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/SiteServiceRemoteWordPressComREST.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | @interface SiteServiceRemoteWordPressComREST : ServiceRemoteWordPressComREST 7 | 8 | @property (nonatomic, readonly) NSNumber *siteID; 9 | 10 | - (instancetype)initWithWordPressComRestApi:(WordPressComRestApi *)api __unavailable; 11 | - (instancetype)initWithWordPressComRestApi:(WordPressComRestApi *)api siteID:(NSNumber *)siteID; 12 | 13 | @end 14 | 15 | NS_ASSUME_NONNULL_END 16 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/SiteServiceRemoteWordPressComREST.m: -------------------------------------------------------------------------------- 1 | #import "SiteServiceRemoteWordPressComREST.h" 2 | 3 | @interface SiteServiceRemoteWordPressComREST () 4 | @property (nonatomic, strong) NSNumber *siteID; 5 | @end 6 | 7 | @implementation SiteServiceRemoteWordPressComREST 8 | 9 | - (instancetype)initWithWordPressComRestApi:(WordPressComRestApi *)api siteID:(NSNumber *)siteID { 10 | self = [super initWithWordPressComRestApi:api]; 11 | if (self) { 12 | _siteID = siteID; 13 | } 14 | return self; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/TaxonomyServiceRemoteREST.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface TaxonomyServiceRemoteREST : SiteServiceRemoteWordPressComREST 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/TaxonomyServiceRemoteXMLRPC.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @class RemoteCategory; 6 | 7 | @interface TaxonomyServiceRemoteXMLRPC : ServiceRemoteWordPressXMLRPC 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Services/UsersServiceRemoteXMLRPC.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum UsersServiceRemoteError: Int, Error { 4 | case UnexpectedResponseData 5 | } 6 | 7 | /// UsersServiceRemoteXMLRPC handles Users related XML-RPC calls. 8 | /// https://codex.wordpress.org/XML-RPC_WordPress_API/Users 9 | /// 10 | public class UsersServiceRemoteXMLRPC: ServiceRemoteWordPressXMLRPC { 11 | 12 | /// Fetch the blog user's profile. 13 | /// 14 | public func fetchProfile(_ success: @escaping ((RemoteProfile) -> Void), failure: @escaping ((NSError?) -> Void)) { 15 | let params = defaultXMLRPCArguments() as [AnyObject] 16 | api.callMethod("wp.getProfile", parameters: params, success: { (responseObj, _) in 17 | guard let dict = responseObj as? NSDictionary else { 18 | assertionFailure("A dictionary was expected but the API returned something different.") 19 | failure(UsersServiceRemoteError.UnexpectedResponseData as NSError) 20 | return 21 | } 22 | let profile = RemoteProfile(dictionary: dict) 23 | success(profile) 24 | 25 | }, failure: { (error, _) in 26 | failure(error) 27 | }) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Utility/ChecksumUtil.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public class ChecksumUtil { 4 | 5 | /// Generates a checksum based on the encoded keys. 6 | static func checksum(from codable: T) -> String where T: Encodable { 7 | let encoder = JSONEncoder() 8 | encoder.outputFormatting = .sortedKeys 9 | let result: String 10 | do { 11 | let data = try encoder.encode(codable) 12 | result = String(data: data, encoding: .utf8) ?? "" 13 | } catch { 14 | result = "" 15 | } 16 | return result.md5() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Utility/NSCharacterSet+URLEncode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc 4 | public extension NSCharacterSet { 5 | /// The base character set `urlPathAllowed` allows single apostrophes. This encoding is a bit more 6 | /// restrictive and disallows some extra characters as per RFC 3986. 7 | /// 8 | @objc(URLPathRFC3986AllowedCharacterSet) 9 | static var urlPathRFC3986Allowed: CharacterSet { 10 | CharacterSet.urlPathAllowed.subtracting(CharacterSet(charactersIn: "!'()*")) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Utility/NSMutableDictionary+Helpers.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSMutableDictionary (Helpers) 4 | - (void)setValueIfNotNil:(id)value forKey:(NSString *)key; 5 | @end 6 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Utility/NSMutableDictionary+Helpers.m: -------------------------------------------------------------------------------- 1 | #import "NSMutableDictionary+Helpers.h" 2 | 3 | @implementation NSMutableDictionary (Helpers) 4 | - (void)setValueIfNotNil:(id)value forKey:(NSString *)key 5 | { 6 | if (value != nil) { 7 | self[key] = value; 8 | } 9 | } 10 | @end 11 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Utility/NSString+MD5.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSString (MD5) 4 | 5 | - (NSString *)md5; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Utility/NSString+MD5.m: -------------------------------------------------------------------------------- 1 | #import "NSString+MD5.h" 2 | #import 3 | 4 | 5 | @implementation NSString (MD5) 6 | 7 | - (NSString *)md5 8 | { 9 | const char *cStr = [self UTF8String]; 10 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 11 | 12 | #pragma clang diagnostic push 13 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" // because Apple considers MD5 insecure 14 | CC_MD5(cStr, (CC_LONG)strlen(cStr), result); 15 | #pragma clang diagnostic pop 16 | 17 | return [NSString stringWithFormat: 18 | @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 19 | result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], 20 | result[8], result[9], result[10], result[11], result[12], result[13], result[14], result[15] 21 | ]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Utility/ObjectValidation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc public extension NSObject { 4 | 5 | /// Validate if a class is a valid NSObject and if it's not nil 6 | /// 7 | /// - Returns: Bool value 8 | func wp_isValidObject() -> Bool { 9 | return !(self is NSNull) 10 | } 11 | } 12 | 13 | @objc public extension NSString { 14 | 15 | /// Validate if a class is a valid NSString and if it's not nil 16 | /// 17 | /// - Returns: Bool value 18 | func wp_isValidString() -> Bool { 19 | return wp_isValidObject() && self != "" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Utility/StringCodingKey.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct StringCodingKey: CodingKey, ExpressibleByStringLiteral { 4 | private let string: String 5 | private var int: Int? 6 | 7 | var stringValue: String { return string } 8 | 9 | init(string: String) { 10 | self.string = string 11 | } 12 | 13 | init?(stringValue: String) { 14 | self.string = stringValue 15 | } 16 | 17 | var intValue: Int? { return int } 18 | 19 | init?(intValue: Int) { 20 | self.string = String(describing: intValue) 21 | self.int = intValue 22 | } 23 | 24 | init(stringLiteral value: String) { 25 | self.string = value 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Utility/UIDevice+Extensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIDevice { 4 | var platform: String { 5 | var size = 0 6 | sysctlbyname("hw.machine", nil, &size, nil, 0) 7 | var machine = [CChar](repeating: 0, count: size) 8 | sysctlbyname("hw.machine", &machine, &size, nil, 0) 9 | return String(cString: machine) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/WordPressKit/Utility/ZendeskMetadata.swift: -------------------------------------------------------------------------------- 1 | public struct ZendeskSiteContainer: Decodable { 2 | public let sites: [ZendeskSite] 3 | } 4 | 5 | public struct ZendeskSite: Decodable { 6 | public let ID: Int 7 | public let zendeskMetadata: ZendeskMetadata 8 | 9 | private enum CodingKeys: String, CodingKey { 10 | case ID = "ID" 11 | case zendeskMetadata = "zendesk_site_meta" 12 | } 13 | } 14 | 15 | public struct ZendeskMetadata: Decodable { 16 | public let plan: String 17 | public let jetpackAddons: [String] 18 | 19 | private enum CodingKeys: String, CodingKey { 20 | case plan = "plan" 21 | case jetpackAddons = "addon" 22 | } 23 | 24 | public init(plan: String, jetpackAddons: [String]) { 25 | self.plan = plan 26 | self.jetpackAddons = jetpackAddons 27 | } 28 | } 29 | 30 | /// Errors generated by the metadata decoding process 31 | public enum PlanServiceRemoteError: Error { 32 | // thrown when no metadata were found 33 | case noMetadata 34 | } 35 | -------------------------------------------------------------------------------- /Sources/WordPressShared/Dictionary+Helpers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - Dictionary Helper Methods 4 | // 5 | extension Dictionary { 6 | /// This method attempts to convert a given value into a String, if it's not already the 7 | /// case. Initial implementation supports only NSNumber. This is meant for bulletproof parsing, 8 | /// in which a String value might be serialized, backend side, as a Number. 9 | /// 10 | /// - Parameter key: The key to retrieve. 11 | /// 12 | /// - Returns: Value as a String (when possible!) 13 | /// 14 | func valueAsString(forKey key: Key) -> String? { 15 | let value = self[key] 16 | switch value { 17 | case let string as String: 18 | return string 19 | case let number as NSNumber: 20 | return number.description 21 | default: 22 | return nil 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/WordPressShared/NSBundle+VersionNumberHelper.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSBundle (WPKitVersionNumberHelper) 4 | 5 | - (NSString *)wpkit_bundleVersion; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Sources/WordPressShared/NSBundle+VersionNumberHelper.m: -------------------------------------------------------------------------------- 1 | #import "NSBundle+VersionNumberHelper.h" 2 | 3 | @implementation NSBundle (WPKitVersionNumberHelper) 4 | 5 | - (NSString *)wpkit_bundleVersion 6 | { 7 | NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; 8 | return infoDictionary[(NSString *)kCFBundleVersionKey] ?: [NSString new]; 9 | } 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /Sources/WordPressShared/NSMutableData+Helpers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Encapsulates all of the NSMutableData Helper Methods. 4 | /// 5 | extension NSMutableData { 6 | 7 | /// Encodes a raw String into UTF8, and appends it to the current instance. 8 | /// 9 | /// - Parameter string: The raw String to be UTF8-Encoded, and appended 10 | /// 11 | @objc func appendString(_ string: String) { 12 | if let data = string.data(using: String.Encoding.utf8) { 13 | append(data) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/WordPressShared/NSString+Helpers.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSString (WPKitHelpers) 4 | 5 | - (NSString *)wpkit_stringByUrlEncoding; 6 | - (NSString *)wpkit_stringByStrippingHTML; 7 | - (NSString *)wpkit_stringByEllipsizingWithMaxLength:(NSInteger)lengthlimit preserveWords:(BOOL)preserveWords; 8 | - (bool)wpkit_isEmpty; 9 | 10 | @end 11 | 12 | @interface NSString (WPKitNumericValueHack) 13 | - (NSNumber *)wpkit_numericValue; 14 | @end 15 | 16 | @interface NSObject (WPKitNumericValueHack) 17 | - (NSNumber *)wpkit_numericValue; 18 | @end 19 | -------------------------------------------------------------------------------- /Sources/WordPressShared/NSString+XMLExtensions.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSString (WPKitXMLExtensions) 4 | 5 | + (NSString *)wpkit_encodeXMLCharactersIn : (NSString *)source; 6 | + (NSString *)wpkit_decodeXMLCharactersIn : (NSString *)source; 7 | - (NSString *)wpkit_stringByDecodingXMLCharacters; 8 | - (NSString *)wpkit_stringByEncodingXMLCharacters; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Sources/WordPressShared/Secret.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Wraps a value that contains sensitive information to prevent accidental logging 4 | /// 5 | /// Usage example 6 | /// 7 | /// ``` 8 | /// let password = Secret("my secret password") 9 | /// print(password) // Prints "--redacted--" 10 | /// print(password.secretValue) // Prints "my secret password" 11 | /// ``` 12 | /// 13 | public struct Secret { 14 | public let secretValue: T 15 | 16 | public init(_ secretValue: T) { 17 | self.secretValue = secretValue 18 | } 19 | } 20 | 21 | extension Secret: RawRepresentable { 22 | public typealias RawValue = T 23 | 24 | public init?(rawValue: Self.RawValue) { 25 | self.init(rawValue) 26 | } 27 | 28 | public var rawValue: T { 29 | return secretValue 30 | } 31 | } 32 | 33 | extension Secret: CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable { 34 | private static var redacted: String { 35 | return "--redacted--" 36 | } 37 | 38 | public var description: String { 39 | return Secret.redacted 40 | } 41 | 42 | public var debugDescription: String { 43 | return Secret.redacted 44 | } 45 | 46 | public var customMirror: Mirror { 47 | return Mirror(reflecting: Secret.redacted) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/WordPressShared/WPKitDateUtils.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface WPKitDateUtils : NSObject 4 | 5 | + (NSDate *)dateFromISOString:(NSString *)isoString; 6 | + (NSString *)isoStringFromDate:(NSDate *)date; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /Sources/WordPressShared/WPMapFilterReduce.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef id (^WPKitMapBlock)(id obj); 4 | typedef BOOL (^WPKitFilterBlock)(id obj); 5 | 6 | @interface NSArray (WPKitMapFilterReduce) 7 | 8 | /** 9 | Transforms values in an array 10 | 11 | The resulting array will include the results of calling mapBlock for each of 12 | the receiver array objects. If mapBlock returns nil that value will be missing 13 | from the resulting array. 14 | */ 15 | - (NSArray *)wpkit_map:(WPKitMapBlock)mapBlock; 16 | 17 | /** 18 | Filters an array to only include values that satisfy the filter block 19 | */ 20 | - (NSArray *)wpkit_filter:(WPKitFilterBlock)filterBlock; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Sources/WordPressShared/WPMapFilterReduce.m: -------------------------------------------------------------------------------- 1 | #import "WPMapFilterReduce.h" 2 | 3 | @implementation NSArray (WPKitMapFilterReduce) 4 | 5 | - (NSArray *)wpkit_map:(WPKitMapBlock)mapBlock 6 | { 7 | NSMutableArray *results = [NSMutableArray arrayWithCapacity:self.count]; 8 | for (id obj in self) { 9 | id objectToAdd = mapBlock(obj); 10 | if (objectToAdd) { 11 | [results addObject:objectToAdd]; 12 | } 13 | } 14 | return [NSArray arrayWithArray:results]; 15 | } 16 | 17 | - (NSArray *)wpkit_filter:(WPKitFilterBlock)filterBlock 18 | { 19 | NSMutableArray *results = [NSMutableArray arrayWithCapacity:self.count]; 20 | for (id obj in self) { 21 | if (filterBlock(obj)) { 22 | [results addObject:obj]; 23 | } 24 | } 25 | return [NSArray arrayWithArray:results]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Bundle+SPMSupport.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Bundle { 4 | /// Returns the `Bundle` for the target. 5 | /// 6 | /// If installed via CocoaPods, this will be `.bundle`, otherwise it will be the framework bundle. 7 | @objc public class var coreAPITestsBundle: Bundle { 8 | #if SWIFT_PACKAGE 9 | return Bundle.module 10 | #else 11 | let defaultBundle = Bundle(for: BundleFinder.self) 12 | 13 | guard let bundleURL = defaultBundle.resourceURL, 14 | let resourceBundle = Bundle(url: bundleURL.appendingPathComponent("CoreAPITests.bundle")) else { 15 | return defaultBundle 16 | } 17 | 18 | return resourceBundle 19 | #endif 20 | } 21 | } 22 | 23 | #if !SWIFT_PACKAGE 24 | private class BundleFinder: NSObject {} 25 | #endif 26 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/FakeInfoDictionaryObjectProvider.swift: -------------------------------------------------------------------------------- 1 | #if SWIFT_PACKAGE 2 | @testable import CoreAPI 3 | #else 4 | @testable import WordPressKit 5 | #endif 6 | 7 | class FakeInfoDictionaryObjectProvider: InfoDictionaryObjectProvider { 8 | private let appTransportSecurity: [String: Any]? 9 | 10 | init(appTransportSecurity: [String: Any]?) { 11 | self.appTransportSecurity = appTransportSecurity 12 | } 13 | 14 | func object(forInfoDictionaryKey key: String) -> Any? { 15 | if key == "NSAppTransportSecurity" { 16 | return appTransportSecurity 17 | } 18 | 19 | return nil 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/HTML/xmlrpc-response-invalid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | website 5 | 6 | 👋 7 | 8 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/HTML/xmlrpc-response-mobile-plugin-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | website 6 | 7 | 👋 8 | 9 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDToken2FANeededSuccess.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "two_step_nonce_authenticator": "two_step_nonce_authenticator", 4 | "two_step_nonce": "two_step_nonce", 5 | "two_step_nonce_sms": "two_step_nonce_sms", 6 | "two_step_nonce_backup": "two_step_nonce_backup", 7 | "two_step_nonce_webauthn": "two_step_nonce_webauthn", 8 | "two_step_notification_sent": "two_step_notification_sent", 9 | "two_step_supported_auth_types": "two_step_supported_auth_types", 10 | "phone_number": "phone_number", 11 | "user_id": 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDTokenBearerTokenSuccess.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "two_step_nonce_authenticator": "two_step_nonce_authenticator", 4 | "two_step_nonce": "two_step_nonce", 5 | "two_step_nonce_sms": "two_step_nonce_sms", 6 | "two_step_nonce_backup": "two_step_nonce_backup", 7 | "two_step_notification_sent": "two_step_notification_sent", 8 | "two_step_supported_auth_types": "two_step_supported_auth_types", 9 | "phone_number": "phone_number", 10 | "bearer_token": "bearer_token", 11 | "user_id": "user_id" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDTokenExistingUserNeedsConnection.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "errors": [{ 4 | "code":"user_exists", 5 | "message":"User exists but not connected" 6 | }], 7 | "two_step_nonce": "two_step_nonce", 8 | "email": "email", 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComOAuthAuthenticateSignature.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "data": { 4 | "bearer_token": "bearer_token", 5 | "token_links": [ 6 | "https:\/\/jetpack.com\/remote-login.php?wpcom_rem...", 7 | "https:\/\/fieldguide.automattic.com\/remote-logi...", 8 | "https:\/\/learn.a8c.com\/remote-login.php?wpcom_remote_login...", 9 | "https:\/\/a8c.tv\/remote-login.php?wpcom_remote_login=vali..." 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComOAuthNeeds2FAFail.json: -------------------------------------------------------------------------------- 1 | { 2 | "error_description": "Please enter the verification code generated by your Authenticator mobile application.", 3 | "error": "needs_2fa" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComOAuthNeedsWebauthnMFA.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "data": { 4 | "user_id": 1234, 5 | "two_step_nonce_webauthn": "two_step_nonce_webauthn", 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComOAuthRequestChallenge.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "data": { 4 | "challenge": "challenge", 5 | "rpId": "wordpress.com", 6 | "allowCredentials": [ 7 | { 8 | "type": "public-key", 9 | "id": "credential-id", 10 | "transports": [ 11 | "usb", 12 | "nfc", 13 | "ble", 14 | "hybrid", 15 | "internal" 16 | ] 17 | }, 18 | { 19 | "type": "public-key", 20 | "id": "credential-id-2", 21 | "transports": [ 22 | "usb", 23 | "nfc", 24 | "ble", 25 | "hybrid", 26 | "internal" 27 | ] 28 | } 29 | ], 30 | "timeout": 60000, 31 | "two_step_nonce": "two_step_nonce" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComOAuthSuccess.json: -------------------------------------------------------------------------------- 1 | { 2 | "blog_id": 0, 3 | "scope": "global", 4 | "blog_url": null, 5 | "token_type": "bearer", 6 | "access_token": "fakeToken" 7 | } 8 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComOAuthWrongPasswordFail.json: -------------------------------------------------------------------------------- 1 | { 2 | "error_description": "Incorrect username or password.", 3 | "error": "invalid_request" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailInvalidInput.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "No media provided in input.", 3 | "error": "invalid_input" 4 | } -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailInvalidJSON.json: -------------------------------------------------------------------------------- 1 | 2 | "message": "The OAuth2 token is invalid.", 3 | "error": "invalid_token" 4 | } -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailReauthenticationRequired.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "A fresh access token must be used to query information about the current user.", 3 | "error": "reauthorization_required" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailRequestInvalidToken.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "The OAuth2 token is invalid.", 3 | "error": "invalid_token" 4 | } -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailThrottled.json: -------------------------------------------------------------------------------- 1 | 2 | Limit reached 3 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailUnauthorized.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "User cannot upload media.", 3 | "error": "unauthorized" 4 | } -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComRestApiMultipleErrors.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors":[ 3 | { 4 | "message": "No media provided in input.", 5 | "error": "upload_error" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/WordPressComSocial2FACodeSuccess.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "two_step_nonce_authenticator": "two_step_nonce_authenticator", 4 | "two_step_nonce": "two_step_nonce", 5 | "two_step_nonce_sms": "two_step_nonce_sms", 6 | "two_step_nonce_backup": "two_step_nonce_backup", 7 | "two_step_notification_sent": "two_step_notification_sent", 8 | "two_step_supported_auth_types": "two_step_supported_auth_types", 9 | "phone_number": "phone_number", 10 | "bearer_token": "bearer_token", 11 | "user_id": "user_id" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/JSON/wp-forbidden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "rest_forbidden", 3 | "data": { 4 | "status": 401 5 | }, 6 | "message": "Sorry, you are not allowed to do that." 7 | } 8 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/Stubs/XML/xmlrpc-bad-username-password-error.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | faultCode 8 | 403 9 | 10 | 11 | faultString 12 | Incorrect username or password. 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/URLRequest+HTTPBodyText.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension URLRequest { 4 | var httpBodyText: String? { 5 | guard let data = (httpBody ?? httpBodyStream?.readToEnd() ) else { 6 | return nil 7 | } 8 | 9 | return String(data: data, encoding: .utf8) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/CoreAPITests/WordPressComRestApiTests+Error.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | #if SWIFT_PACKAGE 3 | import APIInterface 4 | @testable import CoreAPI 5 | #else 6 | @testable import WordPressKit 7 | #endif 8 | 9 | class WordPressComRestApiErrorTests: XCTestCase { 10 | 11 | func testNSErrorBridging() { 12 | for error in WordPressComRestApiErrorCode.allCases { 13 | let apiError = WordPressAPIError.endpointError(WordPressComRestApiEndpointError(code: error)) 14 | let newNSError = apiError as NSError 15 | 16 | XCTAssertEqual(newNSError.domain, "WordPressKit.WordPressComRestApiError") 17 | XCTAssertEqual(newNSError.code, error.rawValue) 18 | } 19 | } 20 | 21 | func testErrorDomain() { 22 | XCTAssertEqual(WordPressComRestApiErrorDomain, WordPressComRestApiEndpointError.errorDomain) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/activity-groups-bad-json-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "totalItems": 86, 3 | "took": 0.094605 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/activity-groups-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "post": { 4 | "name": "Posts and Pages", 5 | "count": 69 6 | }, 7 | "attachment": { 8 | "name": "Media", 9 | "count": 5 10 | }, 11 | "user": { 12 | "name": "People", 13 | "count": 2 14 | }, 15 | "rewind": { 16 | "name": "Backups and Restores", 17 | "count": 10 18 | }, 19 | }, 20 | "totalItems": 86, 21 | "took": 0.094605 22 | } 23 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/activity-log-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "authorization_required", 3 | "message": "An active access token must be used to query information about the current user." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/activity-restore-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "error": "", 4 | "restore_id": 22, 5 | "job_id": 1444315452 6 | } 7 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/activity-rewind-status-restore-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewind": { 3 | "status": "failure", 4 | "rewind_id": "100360", 5 | "started_at": "2018-01-25 15:07:02", 6 | "progress": 0, 7 | "reason": "it doesn't work" 8 | }, 9 | "state": "active", 10 | "last_updated": "2017-10-24T15:16:38.123+00:00" 11 | } 12 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/activity-rewind-status-restore-finished.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewind": { 3 | "status": "finished", 4 | "rewind_id": "100360", 5 | "started_at": "2018-01-25 15:07:02", 6 | "progress": 100 7 | }, 8 | "state": "active", 9 | "last_updated": "2017-10-24T15:16:38.123+00:00" 10 | } 11 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/activity-rewind-status-restore-in-progress.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewind": { 3 | "status": "running", 4 | "rewind_id": "100360", 5 | "started_at": "2018-01-25 15:07:02", 6 | "progress": 50 7 | }, 8 | "state": "active", 9 | "last_updated": "2017-10-24T15:16:38.123+00:00" 10 | } 11 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/activity-rewind-status-restore-queued.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewind": { 3 | "status": "queued", 4 | "rewind_id": "100360", 5 | "started_at": "2018-01-25 15:07:02", 6 | "progress": 0 7 | }, 8 | "state": "active", 9 | "last_updated": "2017-10-24T15:16:38.123+00:00" 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/activity-rewind-status-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "credentials": [ 3 | { 4 | "still_valid": true, 5 | "type": "auto", 6 | "role": "main" 7 | } 8 | ], 9 | "state": "active", 10 | "downloads": [], 11 | "last_updated": "2017-10-24T15:16:38.123+00:00" 12 | } 13 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/atomic-get-auth-cookie-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://someblog.wordpress.com", 3 | "cookies": [ 4 | { 5 | "expires": 1583364400, 6 | "path": "/", 7 | "domain": "someblog.wordpress.com", 8 | "name": "name", 9 | "value": "value" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/auth-send-login-email-invalid-client-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "invalid_client", 3 | "message": "Unknown client_id." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/auth-send-login-email-invalid-secret-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "invalid_secret", 3 | "message": "Invalid client_secret" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/auth-send-login-email-no-user-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "no_such_user", 3 | "message": "No user exists with that email." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/auth-send-login-email-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/auth-send-verification-email-already-verified-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "invalid_request", 3 | "message": "The email address associated with this account is already verified" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/auth-send-verification-email-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": 1 3 | } 4 | 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/backup-get-backup-status-complete-without-download-id-success.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "downloadId": 283844, 3 | "rewindId": "1608510088.971", 4 | "backupPoint": "2020-12-21T00:21:28+00:00", 5 | "startedAt": "2020-12-21T08:12:41+00:00", 6 | "downloadCount": 0, 7 | "validUntil": "2020-12-22T08:12:48+00:00", 8 | "url": "https://public-api.wordpress.com/wpcom/v2/sites/185351267/rewind/downloads/283844/data?token=DNAjse5rAVkbPfNjVQbwXpiBr2JQAAsNTUs6T83mD3C3MrYRqRJRsrqNw5HlgTNh" 9 | }] 10 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/blogging-prompts-settings-fetch-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "prompts_card_opted_in": true, 3 | "prompts_reminders_opted_in": true, 4 | "reminders_days": { 5 | "monday": false, 6 | "tuesday": true, 7 | "wednesday": false, 8 | "thursday": true, 9 | "friday": false, 10 | "saturday": true, 11 | "sunday": false 12 | }, 13 | "reminders_time": "14.30", 14 | "is_potential_blogging_site": true 15 | } 16 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/blogging-prompts-settings-update-empty-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "updated": [] 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/blogging-prompts-settings-update-with-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "updated": { 3 | "prompts_card_opted_in": false, 4 | "prompts_reminders_opted_in": true, 5 | "reminders_days": { 6 | "monday": true, 7 | "tuesday": false, 8 | "wednesday": true, 9 | "thursday": false, 10 | "friday": true, 11 | "saturday": false, 12 | "sunday": true 13 | }, 14 | "reminders_time": "12.59", 15 | "is_potential_blogging_site": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/common-starter-site-designs-empty-designs.json: -------------------------------------------------------------------------------- 1 | { 2 | "designs": [ 3 | ], 4 | "categories": [ 5 | { 6 | "slug": "business", 7 | "title": "Business", 8 | "description": "Business", 9 | "emoji": "💼" 10 | }, 11 | { 12 | "slug": "blog", 13 | "title": "Blog", 14 | "description": "Blog", 15 | "emoji": "📰" 16 | }, 17 | { 18 | "slug": "professional", 19 | "title": "Professional", 20 | "description": "Professional", 21 | "emoji": "💼" 22 | }, 23 | { 24 | "slug": "blank", 25 | "title": "Blank", 26 | "description": "Blank", 27 | "emoji": "📄" 28 | }, 29 | { 30 | "slug": "about", 31 | "title": "About", 32 | "description": "About", 33 | "emoji": "👋" 34 | }, 35 | { 36 | "slug": "highlights", 37 | "title": "Highlights", 38 | "description": "Highlights", 39 | "emoji": "👋" 40 | }, 41 | { 42 | "slug": "splash", 43 | "title": "Splash", 44 | "description": "Splash", 45 | "emoji": "🏖️" 46 | }, 47 | { 48 | "slug": "links", 49 | "title": "Links", 50 | "description": "Links", 51 | "emoji": "🔗" 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/dashboard-200-with-drafts-and-scheduled-posts.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "has_published": true, 4 | "draft": [{ 5 | "id": 3246, 6 | "title": "Whatever", 7 | "content": "", 8 | "featured_image": null, 9 | "date": "2022-01-13 00:30:56" 10 | }, { 11 | "id": 3120, 12 | "title": "Dfhhffg :)", 13 | "content": "", 14 | "featured_image": null, 15 | "date": "2021-03-05 21:04:44" 16 | }, { 17 | "id": 3109, 18 | "title": "Title!", 19 | "content": "", 20 | "featured_image": null, 21 | "date": "0000-00-00 00:00:00" 22 | }], 23 | "scheduled": [] 24 | }, 25 | "todays_stats": { 26 | "views": 0, 27 | "visitors": 0, 28 | "likes": 0, 29 | "comments": 0 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/dashboard-400-invalid-card.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "rest_invalid_param", 3 | "message": "Invalid parameter(s): cards", 4 | "data": { 5 | "status": 400, 6 | "params": { 7 | "cards": "Invalid request: Empty Params." 8 | }, 9 | "details": { 10 | "cards": { 11 | "code": "sites_dashboard_validate_cards", 12 | "message": "Invalid request: Empty Params.", 13 | "data": 400 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/domain-contact-information-response-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "address_1": null, 3 | "address_2": null, 4 | "city" : null, 5 | "country_code" : null, 6 | "email" : "pinar@yahoo.com", 7 | "fax" : null, 8 | "first_name" : "Pinar", 9 | "last_name" : null, 10 | "organization" : null, 11 | "phone": null, 12 | "postal_code" : "12345", 13 | "state" : null 14 | } 15 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/domain-service-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "domains": [ 3 | ] 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/domain-service-invalid-query.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "invalid_query", 3 | "message": "Domain searches only support the following characters (many accents are also allowed): A-Z, a-z, 0-9, -, ., space. You searched for the following unsupported characters: t" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/empty-array.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/empty.json: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/get-single-theme-v1.1.json: -------------------------------------------------------------------------------- 1 | {"id":"twentythirteen","screenshot":"https:\/\/i1.wp.com\/s0.wp.com\/wp-content\/themes\/pub\/twentythirteen\/screenshot.png","cost":{"currency":"USD","number":0,"display":""},"version":"1.0.1","download_url":"https:\/\/public-api.wordpress.com\/rest\/v1\/themes\/download\/twentythirteen.zip","trending_rank":44,"popularity_rank":6,"launch_date":"2013-04-24","name":"Twenty Thirteen","description":"The 2013 theme for WordPress takes us back to the blog, featuring a full range of post formats, each displayed beautifully in their own unique way. Design details abound, starting with a vibrant color scheme and matching header images, beautiful typography and icons, and a flexible layout that looks great on any device, big or small.","tags":["black","brown","orange","tan","white","yellow","light","one-column","two-columns","right-sidebar","fluid-layout","responsive-layout","custom-header","custom-menu","editor-style","featured-images","microformats","post-formats","rtl-language-support","sticky-post","translation-ready","accessibility-ready","infinite-scroll","blog","journal","lifestream","tumblelog","abstract","bright","clean","colorful","flamboyant","playful","vibrant"],"preview_url":"https:\/\/diegotest002.wordpress.com\/?theme=pub\/twentythirteen&hide_banners=true"} -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/is-available-email-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "taken", 3 | "message": "Choose a different email address. This one is not available.", 4 | "available": false 5 | } 6 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/is-available-email-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "available": true 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/is-available-username-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "taken", 3 | "message": "Sorry, that username already exists!", 4 | "status": "error" 5 | } 6 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/is-available-username-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "available": true 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/is-passwordless-account-no-account-found.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unknown_user", 3 | "message": "User does not exist." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/is-passwordless-account-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "passwordless": false, 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-capabilities-107159616-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": [ 3 | "backup", 4 | "backup-realtime", 5 | "scan", 6 | "antispam" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-capabilities-34197361-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": [] 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-capabilities-malformed.json: -------------------------------------------------------------------------------- 1 | { 2 | malformed 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-scan-enqueue-failure.json: -------------------------------------------------------------------------------- 1 | {"success":false} 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-scan-enqueue-success.json: -------------------------------------------------------------------------------- 1 | {"success":true} 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-scan-idle-success-no-threats.json: -------------------------------------------------------------------------------- 1 | { 2 | "credentials": [{ 3 | "host": "example.com", 4 | "port": 21, 5 | "user": "example", 6 | "path": "\/", 7 | "type": "ftp", 8 | "role": "main", 9 | "still_valid": true 10 | }], 11 | "has_cloud": false, 12 | "most_recent": { 13 | "duration": 24, 14 | "error": false, 15 | "is_initial": false, 16 | "progress": 100, 17 | "timestamp": "2020-11-17T00:19:45+00:00" 18 | }, 19 | "state": "idle", 20 | "threats": [] 21 | } 22 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-scan-in-progress.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "scanning", 3 | "threats": [], 4 | "has_cloud": false, 5 | "credentials": [{ 6 | "host": "example.com", 7 | "port": 21, 8 | "user": "example", 9 | "path": "\/", 10 | "type": "ftp", 11 | "role": "main", 12 | "still_valid": true 13 | }], 14 | "current": { 15 | "is_initial": true, 16 | "timestamp": "2020-11-13T01:03:34+00:00", 17 | "progress": 78 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-scan-unavailable.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "unavailable", 3 | "threats": [], 4 | "has_cloud": false, 5 | "credentials": [{ 6 | "host": "example.com", 7 | "port": 21, 8 | "user": "example", 9 | "path": "\/", 10 | "type": "ftp", 11 | "role": "main", 12 | "still_valid": true 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-check-site-failure-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "hasJetpack": "1" 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-check-site-success-no-jetpack.json: -------------------------------------------------------------------------------- 1 | { 2 | "hasJetpack": 0 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-check-site-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "hasJetpack": 1 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-error-activation-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "ACTIVATION_FAILURE", 3 | "message": "message" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-error-activation-install.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "ACTIVATION_ON_INSTALL_FAILURE", 3 | "message": "message" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-error-activation-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "ACTIVATION_RESPONSE_ERROR", 3 | "message": "message" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-error-forbidden.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "FORBIDDEN", 3 | "message": "message" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-error-install-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "INSTALL_FAILURE", 3 | "message": "message" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-error-install-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "INSTALL_RESPONSE_ERROR", 3 | "message": "message" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-error-invalid-credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "INVALID_CREDENTIALS", 3 | "message": "bad password" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-error-login-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "LOGIN_FAILURE", 3 | "message": "message" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-error-site-is-jetpack.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "SITE_IS_JETPACK", 3 | "message": "message" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-error-unknown.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "UNKNOWN_ERROR", 3 | "message": "message" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": false 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-service-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": true 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-social-403.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "rest_cannot_view", 3 | "message": "Required headers are missing from the request.", 4 | "data": { 5 | "status": 403 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-social-no-publicize.json: -------------------------------------------------------------------------------- 1 | { 2 | "is_share_limit_enabled": false, 3 | "is_enhanced_publishing_enabled": false, 4 | "is_social_image_generator_enabled": false 5 | } 6 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/jetpack-social-with-publicize.json: -------------------------------------------------------------------------------- 1 | { 2 | "is_share_limit_enabled": true, 3 | "to_be_publicized_count": 1, 4 | "share_limit": 30, 5 | "publicized_count": 15, 6 | "shared_posts_count": 15, 7 | "shares_remaining": 14, 8 | "is_enhanced_publishing_enabled": false, 9 | "is_social_image_generator_enabled": false 10 | } 11 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/me-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "authorization_required", 3 | "message": "An active access token must be used to query information about the current user." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/me-settings-close-account-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "active-subscriptions" 3 | "message": "This user account cannot be closed while it has active subscriptions." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/me-settings-close-account-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/me-sites-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "authorization_required", 3 | "message": "An active access token must be used to query information about the current user." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/me-sites-empty-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "sites": [] 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/me-sites-visibility-bad-json-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true 3 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/me-sites-visibility-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unauthorized", 3 | "message": "User cannot access this blog" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/me-sites-visibility-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/notifications-last-seen.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_seen_time": "1477564823", 3 | "success": true 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/notifications-load-hash.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_seen_time": "1477417665", 3 | "number": 10, 4 | "notes": [{ 5 | "id": 2674124016, 6 | "note_hash": 4007447833 7 | }, { 8 | "id": 2671944253, 9 | "note_hash": 11378929 10 | }, { 11 | "id": 2670620054, 12 | "note_hash": 1835726383 13 | }, { 14 | "id": 2670587785, 15 | "note_hash": 4054932170 16 | }, { 17 | "id": 2670553009, 18 | "note_hash": 2143753246 19 | }, { 20 | "id": 2670551808, 21 | "note_hash": 1864853241 22 | }, { 23 | "id": 2670540972, 24 | "note_hash": 3800278591 25 | }, { 26 | "id": 2670531512, 27 | "note_hash": 2873743572 28 | }, { 29 | "id": 2669742674, 30 | "note_hash": 2567279918 31 | }, { 32 | "id": 2666908332, 33 | "note_hash": 2934936606 34 | }] 35 | } 36 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/notifications-mark-as-read.json: -------------------------------------------------------------------------------- 1 | { 2 | "updated": [], 3 | "success": true 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/people-send-invitation-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors" : { 3 | "someInvalidUser" : { 4 | "code" : "invalid_input", 5 | "message" : "User not found" 6 | } 7 | }, 8 | "sent" : [ 9 | 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/people-send-invitation-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors" : { }, 3 | "sent" : ["jimthetester"] 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/people-validate-invitation-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors" : { 3 | "someInvalidUser" : { 4 | "code" : "invalid_input", 5 | "message" : "User not found" 6 | } 7 | }, 8 | "success" : [ 9 | 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/people-validate-invitation-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors" : { }, 3 | "success" : ["someValidUser"] 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plans-me-sites-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "sites": [ 3 | { 4 | "ID": 999999990, 5 | "plan": { 6 | "product_id": 2000, 7 | "product_slug": "jetpack_premium", 8 | "product_name_short": "Premium" 9 | } 10 | }, 11 | { 12 | "ID": 999999991, 13 | "plan": { 14 | "product_id": 1010, 15 | "product_slug": "blogger-bundle", 16 | "product_name_short": "Blogger" 17 | } 18 | }, 19 | { 20 | "ID": 999999992, 21 | "plan": { 22 | "product_id": 2002, 23 | "product_slug": "jetpack_free", 24 | "product_name_short": "Free" 25 | } 26 | }, 27 | { 28 | "ID": 999999993, 29 | "plan": { 30 | "product_id": 1008, 31 | "product_slug": "business-bundle", 32 | "product_name_short": "Business" 33 | } 34 | }, 35 | { 36 | "ID": 999999994, 37 | "plan": { 38 | "product_id": 1, 39 | "product_slug": "free_plan", 40 | "product_name_short": "Free" 41 | } 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-install-already-installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "plugin_already_installed", 3 | "message": "The plugin is already installed" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-install-generic-error.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "install_error", 3 | "message": "There was an error installing your plugin" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-install-succeeds.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "code-snippets", 3 | "active": false, 4 | "name": "code-snippets/code-snippets", 5 | "display_name": "Code Snippets", 6 | "plugin_url": "https://github.com/sheabunge/code-snippets", 7 | "version": "2.14.0", 8 | "description": "An easy, clean and simple way to run code snippets on your site. No need to edit to your theme's functions.php file again!", 9 | "author": "Code Snippets Pro", 10 | "author_url": "https://codesnippets.pro", 11 | "network": false, 12 | "autoupdate": false, 13 | "autoupdate_translation": false, 14 | "uninstallable": true 15 | } 16 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-modify-malformed-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" "code-snippets", 3 | 4 | } 5 | 6 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-service-remote-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unauthorized", 3 | "message": "That method is not allowed." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-service-remote-featured-malformed.json: -------------------------------------------------------------------------------- 1 | [ 2 | "name": "WooCommerce", 3 | ] 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-service-remote-featured-plugins-invalid.json: -------------------------------------------------------------------------------- 1 | 2 | [ 3 | { 4 | "name": "WooCommerce", 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-state-contact-form-7.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "contact-form-7", 3 | "active": true, 4 | "name": "contact-form-7/wp-contact-form-7", 5 | "display_name": "Contact Form 7", 6 | "plugin_url": "https://contactform7.com/", 7 | "version": "5.1.9", 8 | "description": "Just another contact form plugin. Simple but flexible.", 9 | "author": "Takayuki Miyoshi", 10 | "author_url": "https://ideasilo.wordpress.com/", 11 | "network": false, 12 | "autoupdate": true, 13 | "uninstallable": false 14 | } 15 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-state-jetpack.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "123", 3 | "slug": "jetpack", 4 | "active": true, 5 | "name": "jetpack/jetpack", 6 | "display_name": "Jetpack by WordPress.com", 7 | "plugin_url": "https://jetpack.com", 8 | "version": "8.6.1", 9 | "description": "Bring the power of the WordPress.com cloud to your self-hosted WordPress. Jetpack enables you to connect your blog to a WordPress.com account to use the powerful features normally only available to WordPress.com users.", 10 | "author": "Automattic", 11 | "author_url": "https://jetpack.com", 12 | "network": false, 13 | "autoupdate": false, 14 | "uninstallable": false, 15 | "updateState" : { 16 | "updated" : true 17 | }, 18 | "automanaged": true 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-update-gutenberg-needs-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "gutenberg", 3 | "active": true, 4 | "update": { 5 | "id": "w.org/plugins/gutenberg", 6 | "slug": "gutenberg", 7 | "plugin": "gutenberg/gutenberg.php", 8 | "new_version": "8.3.0", 9 | "url": "https://wordpress.org/plugins/gutenberg/", 10 | "package": "https://downloads.wordpress.org/plugin/gutenberg.zip", 11 | "tested": "5.4.2" 12 | }, 13 | "name": "gutenberg/gutenberg", 14 | "display_name": "Gutenberg", 15 | "plugin_url": "https://github.com/WordPress/gutenberg", 16 | "version": "7.2.1", 17 | "description": "Printing since 1440. This is the development plugin for the new block editor in core.", 18 | "author": "Gutenberg Team", 19 | "author_url": "", 20 | "network": false, 21 | "autoupdate": false, 22 | "autoupdate_translation": false, 23 | "uninstallable": false, 24 | "log": [ 25 | "Enabling Maintenance mode…", 26 | "Downloading update from https://downloads.wordpress.org/plugin/gutenberg.7.2.1.zip…", 27 | "Unpacking the update…", 28 | "Installing the latest version…", 29 | "Removing the old version of the plugin…", 30 | "Plugin updated successfully." 31 | ] 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-update-jetpack-already-updated.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "jetpack", 3 | "active": true, 4 | "name": "jetpack/jetpack", 5 | "display_name": "Jetpack by WordPress.com", 6 | "plugin_url": "https://jetpack.com", 7 | "version": "8.6.1", 8 | "description": "Bring the power of the WordPress.com cloud to your self-hosted WordPress. Jetpack enables you to connect your blog to a WordPress.com account to use the powerful features normally only available to WordPress.com users.", 9 | "author": "Automattic", 10 | "author_url": "https://jetpack.com", 11 | "network": false, 12 | "autoupdate": false, 13 | "autoupdate_translation": false, 14 | "uninstallable": true, 15 | "log": [ 16 | "No update needed" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/plugin-update-response-malformed.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug" "gutenberg", 3 | 4 | } 5 | 6 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/post-likes-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unauthorized", 3 | "message": "User cannot access this private blog." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/post-revisions-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error":"unknown_error", 3 | "message":"Unknown error" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/qrlogin-authenticate-200.json: -------------------------------------------------------------------------------- 1 | { 2 | "authenticated": true, 3 | "success": true 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/qrlogin-authenticate-failed-400.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "rest_invalid_param", 3 | "message": "Token does not match", 4 | "data": { 5 | "status": 400 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/qrlogin-validate-200.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": "Chrome", 3 | "location": "Mount Laurel, New Jersey", 4 | "success": true 5 | } 6 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/qrlogin-validate-400.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "rest_invalid_param", 3 | "message": "Token does not match", 4 | "data": { 5 | "status": 400 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/qrlogin-validate-expired-401.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "data_invalid", 3 | "message": "QR code data expired", 4 | "data": { 5 | "status": 401 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/reader-interests-success.json: -------------------------------------------------------------------------------- 1 | {"success":true,"interests":[ 2 | {"title":"One","slug":"one"}, 3 | {"title":"Two","slug":"two"}, 4 | {"title":"Three","slug":"three"}, 5 | {"title":"Four","slug":"four"}, 6 | {"title":"Five","slug":"five"} 7 | ]} 8 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/reader-post-comments-subscribe-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": false, 3 | "i_subscribe": false 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/reader-post-comments-subscribe-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "i_subscribe": true, 3 | "success": true 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/reader-post-comments-subscription-status-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "i_subscribe": true, 3 | "success": true 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/reader-post-comments-unsubscribe-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "i_subscribe": false, 3 | "success": true 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/reader-post-comments-update-notification-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "i_subscribe": true, 3 | "receives_notifications": true 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/reader-site-search-success-no-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "feeds": [ 3 | { 4 | "URL": "https://dailypost.wordpress.com", 5 | "subscribe_URL": "http://dailypost.wordpress.com", 6 | "blog_ID": "489937", 7 | "title": "The Daily Post", 8 | "railcar": { 9 | "railcar": "*dPbMa8*fdh#", 10 | "fetch_algo": "reader/manage/search:0", 11 | "fetch_position": 0, 12 | "rec_blog_id": 489937, 13 | "fetch_lang": "en", 14 | "fetch_query": "dailypost.wordpress.com" 15 | }, 16 | "meta": { 17 | "links": { 18 | "feed": "https://public-api.wordpress.com/rest/v1.1/read/feed/27030", 19 | "site": "https://public-api.wordpress.com/rest/v1.1/read/sites/489937" 20 | } 21 | }, 22 | "feed_ID": "27030", 23 | "subscribers_count": 37418087 24 | } 25 | ], 26 | "total": 1, 27 | "algorithm": "reader/manage/search:0", 28 | "next_page": "offset=10&algorithm=reader/manage/search:0" 29 | } 30 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/self-hosted-plugins-install.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "google-site-kit/google-site-kit", 3 | "status": "inactive", 4 | "name": "Site Kit by Google", 5 | "plugin_uri": "https://sitekit.withgoogle.com", 6 | "author": "Google", 7 | "author_uri": "https://opensource.google.com", 8 | "description": { 9 | "raw": "Site Kit is a one-stop solution for WordPress users to use everything Google has to offer to make them successful on the web.", 10 | "rendered": "Site Kit is a one-stop solution for WordPress users to use everything Google has to offer to make them successful on the web. By Google." 11 | }, 12 | "version": "1.119.0", 13 | "network_only": false, 14 | "requires_wp": "5.2", 15 | "requires_php": "5.6", 16 | "textdomain": "google-site-kit", 17 | "_links": { 18 | "self": [ 19 | { 20 | "href": "https://unlikely-student-crown.jurassic.ninja/wp-json/wp/v2/plugins/google-site-kit/google-site-kit" 21 | } 22 | ] 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/share-app-content-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordpress", 3 | "link": "https:\/\/example.blog\/app?campaign=wordpress", 4 | "message": "Example message for WordPress" 5 | } 6 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-active-purchases-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "authorization_required", 3 | "message": "An active access token must be used to query information about the current user." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-active-purchases-empty-response.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-comment-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": 1, 3 | "post": { 4 | "ID": 1, 5 | "title": "Post title", 6 | "type": "post", 7 | "link": "post URL" 8 | }, 9 | "author": { 10 | "ID": 12345, 11 | "login": "", 12 | "email": "author@email.com", 13 | "name": "Comment Author", 14 | "first_name": "", 15 | "last_name": "", 16 | "nice_name": "", 17 | "URL": "author URL", 18 | "avatar_URL": "avatar URL", 19 | "profile_URL": "profile URL", 20 | "ip_address": "000.0.00.000" 21 | }, 22 | "date": "2021-08-04T07:58:49+00:00", 23 | "URL": "comment URL", 24 | "short_URL": "short URL", 25 | "content": "I am comment content", 26 | "raw_content": "I am comment raw content", 27 | "status": "approved", 28 | "parent": false, 29 | "type": "comment", 30 | "like_count": 0, 31 | "i_like": false, 32 | "meta": { 33 | "links": { 34 | } 35 | }, 36 | "can_moderate": true, 37 | "i_replied": false 38 | } 39 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-comments-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "found": 1, 3 | "site_ID": 0, 4 | "comments": [ 5 | { 6 | "ID": 1, 7 | "post": { 8 | "ID": 1, 9 | "title": "Post title", 10 | "type": "post", 11 | "link": "post URL" 12 | }, 13 | "author": { 14 | "ID": 12345, 15 | "login": "", 16 | "email": "author@email.com", 17 | "name": "Comment Author", 18 | "first_name": "", 19 | "last_name": "", 20 | "nice_name": "", 21 | "URL": "author URL", 22 | "avatar_URL": "avatar URL", 23 | "profile_URL": "profile URL", 24 | "ip_address": "000.0.00.000" 25 | }, 26 | "date": "2021-08-04T07:58:49+00:00", 27 | "URL": "comment URL", 28 | "short_URL": "short URL", 29 | "content": "I am comment content", 30 | "raw_content": "I am comment raw content", 31 | "status": "approved", 32 | "parent": false, 33 | "type": "comment", 34 | "like_count": 0, 35 | "i_like": false, 36 | "meta": { 37 | "links": { 38 | } 39 | }, 40 | "can_moderate": true, 41 | "i_replied": false 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-creation-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "blog_details": { 4 | "url": "https://10711c.wordpress.com/", 5 | "blogid": "156355635", 6 | "blogname": "10711c", 7 | "xmlrpc": "https://10711c.wordpress.com/xmlrpc.php" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-delete-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unauthorized", 3 | "message": "API calls to this endpoint have been disabled." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-delete-unexpected-json-failure.json: -------------------------------------------------------------------------------- 1 | ["invalid", "response"] 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-email-followers-get-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unauthorized", 3 | "message": "user cannot view stats" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-email-followers-get-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "invalid_blog", 3 | "message": "This blog does not have the Stats module enabled" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-email-followers-get-success-more-pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "pages": 10, 4 | "total": 1, 5 | "total_email": 1, 6 | "total_wpcom": 1, 7 | "subscribers": [ 8 | { 9 | "avatar": "https://localhost/image", 10 | "label": "test@test.com", 11 | "ID": "123456", 12 | "url": null, 13 | "follow_data": null, 14 | "date_subscribed": "2022-01-01T00:00:00+00:00" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-email-followers-get-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "pages": 1, 4 | "total": 1, 5 | "total_email": 1, 6 | "total_wpcom": 1, 7 | "subscribers": [ 8 | { 9 | "avatar": "https://localhost/image", 10 | "label": "test@test.com", 11 | "ID": "123456", 12 | "url": null, 13 | "follow_data": null, 14 | "date_subscribed": "2022-01-01T00:00:00+00:00" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-export-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error":"unauthorized", 3 | "message":"User or Token does not have access to specified site." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-export-bad-json-failure.json: -------------------------------------------------------------------------------- 1 | {"id":0,"status":"running" 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-export-failure.json: -------------------------------------------------------------------------------- 1 | {"id":0,"status":"not-running"} 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-export-missing-status-failure.json: -------------------------------------------------------------------------------- 1 | {"id":0} 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-export-success.json: -------------------------------------------------------------------------------- 1 | {"id":0,"status":"running"} 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-followers-delete-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unauthorized", 3 | "message": "Current user does not have required capability to delete followers." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-followers-delete-bad-json-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "follower_id": "321", 3 | "deleted": true 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-followers-delete-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "not_following", 3 | "message": "That user is not found to be following this blog." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-followers-delete-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "follower_id": "321", 3 | "deleted": true 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-plans-v3-empty-failure.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-plugins-error.json: -------------------------------------------------------------------------------- 1 | {"error":"unauthorized","message":"This user is not authorized to activate_plugins on this blog."} 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-plugins-malformed.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "plugins": 4 | { 5 | ], 6 | "file_mod_capabilities": { 7 | "modify_files": true, 8 | "autoupdate_files": true 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-quick-start-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error":"unknown_error", 3 | "message":"Unknown error" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-quick-start-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-roles-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unauthorized", 3 | "message": "User cannot view roles for specified site" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-segments-single.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon_URL" : "https://s.wp.com/i/mobile_segmentation_icons/monochrome/ic_blogger.png", 3 | "icon_color" : "#0087be", 4 | "id" : 1, 5 | "mobile" : true, 6 | "segment_type_subtitle" : "Share and discuss ideas, updates, or creations.", 7 | "segment_type_title" : "Blog", 8 | "slug" : "blog" 9 | } 10 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-subscriber-get-details-response-invalid-country.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_id": 123, 3 | "subscription_id": 123, 4 | "email_address": "test@example.com", 5 | "date_subscribed": "2025-04-17T14:40:00+00:00", 6 | "is_email_subscriber": false, 7 | "subscription_status": "Subscribed", 8 | "avatar": "https://example.com/avatar", 9 | "display_name": "Alex", 10 | "url": "http://example.wordpress.com", 11 | "country": { 12 | "code": "", 13 | "name": false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-subscriber-stats-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "emails_sent": 1, 3 | "unique_opens": 2, 4 | "unique_clicks": 3, 5 | "blog_registration_date": "2024-12-04 16:00:32" 6 | } 7 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-subscribers-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "total": 1, 3 | "pages": 1, 4 | "page": 1, 5 | "per_page": 100, 6 | "subscribers": [ 7 | { 8 | "user_id": 1, 9 | "subscription_id": 2, 10 | "email_address": "user@example.com", 11 | "date_subscribed": "2025-02-28T19:36:36+00:00", 12 | "is_email_subscriber": false, 13 | "subscription_status": "Not subscribed", 14 | "avatar": "https://0.gravatar.com/avatar/example.jpg", 15 | "display_name": "Test", 16 | "url": "http://example.wordpress.com" 17 | } 18 | ], 19 | "is_owner_subscribed": true 20 | } 21 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-users-delete-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unauthorized", 3 | "message": "User cannot access this private blog." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-users-delete-bad-json-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true 3 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-users-delete-not-member-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "invalid_input", 3 | "message": "User is not a member of the specified site." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-users-delete-site-owner-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "forbidden", 3 | "message": "A site owner can not be removed through this endpoint." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-users-delete-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-users-update-role-bad-json-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": 1111, 3 | "login": "jimthetester", 4 | "email": "jimthetester@thetestemail.org", 5 | "name": "jimthetester", 6 | "first_name": "Jim", 7 | "last_name": "Tester", 8 | "nice_name": "jimthetester", 9 | "URL": "http:\/\/jimthetester.blog", 10 | "avatar_URL": "https:\/\/2.gravatar.com\/avatar\/5c78d333444a3c12345ed8ff0e567890?s=200&d=mm", 11 | "profile_URL": "http:\/\/en.gravatar.com\/jimthetester", 12 | "site_ID": 321, 13 | "roles": ["administrator"] 14 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-users-update-role-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": 1111, 3 | "login": "jimthetester", 4 | "email": "jimthetester@thetestemail.org", 5 | "name": "jimthetester", 6 | "first_name": "Jim", 7 | "last_name": "Tester", 8 | "nice_name": "jimthetester", 9 | "URL": "http:\/\/jimthetester.blog", 10 | "avatar_URL": "https:\/\/2.gravatar.com\/avatar\/5c78d333444a3c12345ed8ff0e567890?s=200&d=mm", 11 | "profile_URL": "http:\/\/en.gravatar.com\/jimthetester", 12 | "site_ID": 321, 13 | "roles": ["administrator"] 14 | } 15 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-users-update-role-unknown-site-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unauthorized", 3 | "message": "User cannot view users for specified site" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-users-update-role-unknown-user-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error":"unknown_user", 3 | "message":"Unknown user" 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-verticals-empty.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-verticals-multiple.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "vertical_id": "p25v48", 4 | "parent": "p25", 5 | "vertical_slug": "Landscaping", 6 | "vertical_name": "Landscaping", 7 | "preview": [ 8 | 9 | ], 10 | "is_user_input_vertical": false 11 | }, 12 | { 13 | "vertical_id": "p25v54", 14 | "parent": "p25", 15 | "vertical_slug": "Landscapers", 16 | "vertical_name": "Landscapers", 17 | "preview": [ 18 | 19 | ], 20 | "is_user_input_vertical": false 21 | }, 22 | { 23 | "vertical_id": "p25v32", 24 | "parent": "p25", 25 | "vertical_slug": "Landscape Designers", 26 | "vertical_name": "Landscape Designers", 27 | "preview": [ 28 | 29 | ], 30 | "is_user_input_vertical": false 31 | }, 32 | { 33 | "vertical_id": "p25v44", 34 | "parent": "p25", 35 | "vertical_slug": "Landscape Architect", 36 | "vertical_name": "Landscape Architect", 37 | "preview": [ 38 | 39 | ], 40 | "is_user_input_vertical": false 41 | }, 42 | { 43 | "vertical_id": "", 44 | "vertical_slug": "landscap", 45 | "vertical_name": "landscap", 46 | "preview": [ 47 | 48 | ], 49 | "is_user_input_vertical": true 50 | } 51 | ] -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-verticals-prompt.json: -------------------------------------------------------------------------------- 1 | { 2 | "site_topic_header": "What will your blog be about?", 3 | "site_topic_subheader": "We'll use your answer to add sections to your website.", 4 | "site_topic_placeholder": "e.g., Landscaping, Consulting... etc", 5 | } 6 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-verticals-single.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "vertical_id": "p25v48", 4 | "parent": "p25", 5 | "vertical_slug": "Landscaping", 6 | "vertical_name": "Landscaping", 7 | "preview": [ 8 | 9 | ], 10 | "is_user_input_vertical": false 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-viewers-delete-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unauthorized", 3 | "message": "User cannot access this private blog." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-viewers-delete-bad-json.json: -------------------------------------------------------------------------------- 1 | { 2 | "viewer_ID": "321", 3 | "deleted": true 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-viewers-delete-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "unknown_viewer", 3 | "message": "Requested viewer was not found." 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-viewers-delete-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "viewer_ID": "321", 3 | "deleted": true 4 | } 5 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/site-zendesk-metadata-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "sites": [ 3 | { "ID": 123, "zendesk_site_meta": { "plan": "free", "addon": [] } } 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/sites-invites-links-disable-empty.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/sites-invites-links-disable.json: -------------------------------------------------------------------------------- 1 | [ 2 | "a2f81a37e785b1ee2983d9e4b49d272d", 3 | "91eb1463d088c280769620255f7adea0", 4 | "549a9338268d59c340d2be3848f2e1f0", 5 | "7feec5f2707601deaf15b6b2abb9f6ca", 6 | "5c21559e4065727c52a2405d40257a56" 7 | ] 8 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/sites-invites-links-generate.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "invite_key": "a2f81a37e785b1ee2983d9e4b49d272d", 4 | "link": "https://wordpress.com/accept-invite/01234/abcda", 5 | "role": "administrator", 6 | "is_group_invite": true 7 | }, 8 | { 9 | "invite_key": "91eb1463d088c280769620255f7adea0", 10 | "link": "https://wordpress.com/accept-invite/01234/asfasdf", 11 | "role": "editor", 12 | "is_group_invite": true 13 | }, 14 | { 15 | "invite_key": "549a9338268d59c340d2be3848f2e1f0", 16 | "link": "https://wordpress.com/accept-invite/01234/dfsdfdsfs", 17 | "role": "author", 18 | "is_group_invite": true 19 | }, 20 | { 21 | "invite_key": "7feec5f2707601deaf15b6b2abb9f6ca", 22 | "link": "https://wordpress.com/accept-invite/01234/wfasdfa", 23 | "role": "contributor", 24 | "is_group_invite": true 25 | }, 26 | { 27 | "invite_key": "5c21559e4065727c52a2405d40257a56", 28 | "link": "https://wordpress.com/accept-invite/01234/dddassdd", 29 | "role": "follower", 30 | "is_group_invite": true 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/stats-emails-summary.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": [ 3 | { 4 | "id": 53978, 5 | "href": "https://www.test1.com", 6 | "date": "2023-12-29 16:02:54", 7 | "title": "A great testing post", 8 | "type": "post", 9 | "opens": 453192, 10 | "clicks": 2202 11 | }, 12 | { 13 | "id": 52362, 14 | "href": "http://www.test2.com", 15 | "date": "2023-06-27 19:15:12", 16 | "title": "Hot Off the Press: A new testing post", 17 | "type": "post", 18 | "opens": 450055, 19 | "clicks": 5385 20 | }, 21 | { 22 | "id": 54733, 23 | "href": "http://www.test3.com", 24 | "date": "2024-03-06 21:06:28", 25 | "title": "Case Study: A new testing post", 26 | "type": "post", 27 | "opens": 382335, 28 | "clicks": 4063 29 | }, 30 | { 31 | "id": 53668, 32 | "href": "http://www.test4.com", 33 | "date": "2023-11-07 20:18:07", 34 | "title": "Test without opens and clicks", 35 | "type": "post" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/stats-file-downloads.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2019-07-29", 3 | "period": "month", 4 | "days": { 5 | "2019-07-01": { 6 | "files": [ 7 | { 8 | "filename": "/2019/07/test.pdf", 9 | "downloads": 11 10 | }, 11 | { 12 | "filename": "/2019/07/sampleaudio.mp3", 13 | "downloads": 4 14 | } 15 | ], 16 | "other_downloads": 0, 17 | "total_downloads": 15 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/stats-published-posts.json: -------------------------------------------------------------------------------- 1 | { 2 | "found": 3, 3 | "posts": [ 4 | { 5 | "ID": 41038, 6 | "title": "Announcing Newspack by WordPress.com — A New Publishing Solution for News Organizations", 7 | "URL": "http://en.blog.wordpress.com/2019/01/14/newspack-by-wordpress-com/" 8 | }, 9 | { 10 | "ID": 41015, 11 | "title": "Customize Your WordPress.com Dashboard", 12 | "URL": "http://en.blog.wordpress.com/2019/01/08/customize-your-wordpress-com-dashboard/" 13 | }, 14 | { 15 | "ID": 40978, 16 | "title": "Introducing the 2019 ‘Anything Is Possible’ List", 17 | "URL": "http://en.blog.wordpress.com/2019/01/03/introducing-the-2019-anything-is-possible-list/" 18 | } 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/stats-referrer-mark-as-spam.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/stats-streak-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "streak": { 3 | "long": { 4 | "start": "2018-03-28", 5 | "end": "2018-03-29", 6 | "length": 2 7 | }, 8 | "current": { 9 | "start": "2019-02-07", 10 | "end": "2019-02-07", 11 | "length": 1 12 | } 13 | }, 14 | "data": { 15 | "1517591300": 1, 16 | "1518633236": 1, 17 | "1521558018": 1, 18 | "1522252824": 1, 19 | "1522339253": 1, 20 | "1524502854": 1, 21 | "1525386728": 1, 22 | "1525879841": 1, 23 | "1526299248": 1, 24 | "1526325694": 1, 25 | "1529366487": 1, 26 | "1529589654": 1, 27 | "1529598045": 1, 28 | "1532369350": 1, 29 | "1532617690": 1, 30 | "1533214805": 1, 31 | "1534248027": 1, 32 | "1535736246": 1, 33 | "1536768805": 1, 34 | "1537873250": 1, 35 | "1538150452": 1, 36 | "1540912843": 1, 37 | "1541516765": 1, 38 | "1542216078": 1, 39 | "1542742308": 1, 40 | "1543265213": 1, 41 | "1543420856": 1, 42 | "1543863772": 1, 43 | "1545235219": 1, 44 | "1546513243": 1, 45 | "1546971252": 1, 46 | "1547431243": 1, 47 | "1549555242": 1 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/stats-summary.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2024-01-31", 3 | "period": "week", 4 | "views": 43607, 5 | "visitors": 3497, 6 | "likes": 2378, 7 | "reblogs": 0, 8 | "comments": 154, 9 | "followers": 103296780 10 | } 11 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/stats-visits-month-unit-week.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2024-01-31", 3 | "unit": "week", 4 | "fields": [ 5 | "period", 6 | "views", 7 | "visitors", 8 | "likes", 9 | "reblogs", 10 | "comments", 11 | "posts" 12 | ], 13 | "data": [ 14 | [ 15 | "2024W01W01", 16 | 25724, 17 | 16227, 18 | 908, 19 | 0, 20 | 50, 21 | 1 22 | ], 23 | [ 24 | "2024W01W08", 25 | 24147, 26 | 14715, 27 | 1398, 28 | 0, 29 | 89, 30 | 8 31 | ], 32 | [ 33 | "2024W01W15", 34 | 18581, 35 | 12151, 36 | 1057, 37 | 0, 38 | 58, 39 | 1 40 | ], 41 | [ 42 | "2024W01W22", 43 | 19386, 44 | 12458, 45 | 1182, 46 | 0, 47 | 84, 48 | 2 49 | ], 50 | [ 51 | "2024W01W29", 52 | 5589, 53 | 3472, 54 | 137, 55 | 0, 56 | 12, 57 | 0 58 | ] 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/stats-visits-month.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2019-02-21", 3 | "unit": "month", 4 | "fields": [ 5 | "period", 6 | "views", 7 | "visitors", 8 | "comments", 9 | "likes" 10 | ], 11 | "data": [ 12 | [ 13 | "2018-05-01", 14 | 3496, 15 | 398, 16 | 0, 17 | 72 18 | ], 19 | [ 20 | "2018-06-01", 21 | 3361, 22 | 417, 23 | 0, 24 | 52 25 | ], 26 | [ 27 | "2018-07-01", 28 | 3537, 29 | 461, 30 | 0, 31 | 71 32 | ], 33 | [ 34 | "2018-08-01", 35 | 3097, 36 | 526, 37 | 0, 38 | 68 39 | ], 40 | [ 41 | "2018-09-01", 42 | 2851, 43 | 534, 44 | 0, 45 | 75 46 | ], 47 | [ 48 | "2018-10-01", 49 | 4144, 50 | 669, 51 | 0, 52 | 149 53 | ], 54 | [ 55 | "2018-11-01", 56 | 4579, 57 | 884, 58 | 0, 59 | 162 60 | ], 61 | [ 62 | "2018-12-01", 63 | 2674, 64 | 460, 65 | 0, 66 | 106 67 | ], 68 | [ 69 | "2019-01-01", 70 | 3499, 71 | 584, 72 | 0, 73 | 114 74 | ], 75 | [ 76 | "2019-02-01", 77 | 2569, 78 | 334, 79 | 0, 80 | 116 81 | ] 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/supported-states-empty.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/validate-domain-contact-information-response-fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": false, 3 | "messages": { 4 | "phone": ["Enter a valid country code followed by a dot (for example +1.6285550199)."], 5 | "city": ["This field is required."], 6 | "address_1": ["Enter your address."], 7 | "last_name": ["Enter your last name."], 8 | "email": ["The 'Email' field does not appear to be valid.", "“gmail” does not appear to be a valid domain."], 9 | "country_code": ["This field is required."], 10 | "postal_code": ["This field is required."], 11 | "first_name": ["Enter your first name."] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/validate-domain-contact-information-response-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | } 4 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/videopress-private-video.json: -------------------------------------------------------------------------------- 1 | { 2 | "guid": "OO4thna8", 3 | "title": "VideoPress demo", 4 | "description": "", 5 | "width": 1280, 6 | "height": 720, 7 | "duration": 143700, 8 | "display_embed": true, 9 | "allow_download": false, 10 | "rating": "G", 11 | "poster": "https://videos.files.wordpress.com/OO4thna8/videopress2-web2_hd.original.jpg", 12 | "original": "https://videos.files.wordpress.com/OO4thna8/videopress2-web2.mov", 13 | "watermark": "https://wptv.files.wordpress.com/2010/07/wptv.png", 14 | "bg_color": "", 15 | "blog_id": 9999, 16 | "post_id": 1913, 17 | "privacy_setting": 1, 18 | "finished": true 19 | } 20 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/videopress-public-video.json: -------------------------------------------------------------------------------- 1 | { 2 | "guid": "OO4thna8", 3 | "title": "VideoPress demo", 4 | "description": "", 5 | "width": 1280, 6 | "height": 720, 7 | "duration": 143700, 8 | "display_embed": true, 9 | "allow_download": false, 10 | "rating": "G", 11 | "poster": "https://videos.files.wordpress.com/OO4thna8/videopress2-web2_hd.original.jpg", 12 | "original": "https://videos.files.wordpress.com/OO4thna8/videopress2-web2.mov", 13 | "watermark": "https://wptv.files.wordpress.com/2010/07/wptv.png", 14 | "bg_color": "", 15 | "blog_id": 9999, 16 | "post_id": 1913, 17 | "privacy_setting": 0, 18 | "finished": true 19 | } 20 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/videopress-site-default-video.json: -------------------------------------------------------------------------------- 1 | { 2 | "guid": "OO4thna8", 3 | "title": "VideoPress demo", 4 | "description": "", 5 | "width": 1280, 6 | "height": 720, 7 | "duration": 143700, 8 | "display_embed": true, 9 | "allow_download": false, 10 | "rating": "G", 11 | "poster": "https://videos.files.wordpress.com/OO4thna8/videopress2-web2_hd.original.jpg", 12 | "original": "https://videos.files.wordpress.com/OO4thna8/videopress2-web2.mov", 13 | "watermark": "https://wptv.files.wordpress.com/2010/07/wptv.png", 14 | "bg_color": "", 15 | "blog_id": 9999, 16 | "post_id": 1913, 17 | "privacy_setting": 2, 18 | "finished": true 19 | } 20 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/videopress-token.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata_token": "videopress-token" 3 | } -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/wp-admin-post-new.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/xmlrpc-malformed-request-xml-error.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | faultCode 8 | 9 | -32700 10 | 11 | 12 | 13 | faultString 14 | 15 | parse error. not well formed 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/xmlrpc-metaweblog-editpost-bad-xml-failure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/xmlrpc-metaweblog-editpost-change-format-failure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | faultCode 8 | 9 | 404 10 | 11 | 12 | 13 | faultString 14 | 15 | Invalid post format. 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/xmlrpc-metaweblog-editpost-change-type-failure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | faultCode 8 | 9 | 401 10 | 11 | 12 | 13 | faultString 14 | 15 | The post type may not be changed. 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/xmlrpc-metaweblog-editpost-success.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/xmlrpc-metaweblog-newpost-bad-xml-failure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/xmlrpc-metaweblog-newpost-invalid-posttype-failure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | faultCode 8 | 9 | 401 10 | 11 | 12 | 13 | faultString 14 | 15 | Invalid post type. 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/xmlrpc-metaweblog-newpost-success.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/xmlrpc-response-valid-but-unexpected-dictionary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | foo 9 | 10 | 11 | 12 | 1 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Mock Data/xmlrpc-wp-getpost-invalid-id-failure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | faultCode 8 | 404 9 | 10 | 11 | faultString 12 | Invalid post ID. 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/AnnouncementServiceRemoteTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import OHHTTPStubs 4 | import OHHTTPStubsSwift 5 | 6 | @testable import WordPressKit 7 | 8 | class AnnouncementServiceRemoteTests: XCTestCase { 9 | 10 | func testNoAnnouncement() { 11 | stub(condition: isPath("/wpcom/v2/mobile/feature-announcements") && containsQueryParams(["app_id": "test-app"])) { _ in 12 | HTTPStubsResponse(jsonObject: ["announcements": [String]()], statusCode: 200, headers: nil) 13 | } 14 | 15 | let remote = AnnouncementServiceRemote(wordPressComRestApi: .init(oAuthToken: "fake")) 16 | var result: Result<[Announcement], Error>? = nil 17 | let completed = expectation(description: "API call completed") 18 | remote.getAnnouncements(appId: "test-app", appVersion: "2.0", locale: "en") { 19 | result = $0 20 | completed.fulfill() 21 | } 22 | wait(for: [completed], timeout: 0.3) 23 | 24 | try XCTAssertEqual(XCTUnwrap(result).get().count, 0) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/Date+WordPressComTests.swift: -------------------------------------------------------------------------------- 1 | @testable import WordPressKit 2 | import XCTest 3 | 4 | // This is an incomplete test for implementing RFC 3339. 5 | // It's purpose is to ensure our code "works". 6 | // 7 | // See also: 8 | // 9 | // - https://developer.wordpress.com/docs/api/ 10 | // - https://datatracker.ietf.org/doc/html/rfc3339 11 | class DateWordPressComTests: XCTestCase { 12 | 13 | func testValidRFC3339DateFromString() { 14 | XCTAssertEqual( 15 | Date.with(wordPressComJSONString: "2023-03-19T15:00:00Z"), 16 | Date(timeIntervalSince1970: 1_679_238_000) 17 | ) 18 | } 19 | 20 | func testInvalidRFC3339DateFromString() { 21 | XCTAssertNil(Date.with(wordPressComJSONString: "2024-01-01")) 22 | } 23 | 24 | func testInvalidDateFromString() { 25 | XCTAssertNil(Date.with(wordPressComJSONString: "not a date")) 26 | } 27 | 28 | func testValidRFC3339StringFromDate() { 29 | XCTAssertEqual( 30 | Date(timeIntervalSince1970: 1_679_238_000).wordPressComJSONString, 31 | // Apparently, NSDateFormatter doesn't offer a way to specify Z vs +0000. 32 | // This might go all the way back to the ISO 8601 and RFC 3339 specs overlap. 33 | "2023-03-19T15:00:00+0000" 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/DateFormatter+WordPressComTests.swift: -------------------------------------------------------------------------------- 1 | @testable import WordPressKit 2 | import XCTest 3 | 4 | class DateFormatterWordPressComTests: XCTestCase { 5 | 6 | func testDateFormatterConfiguration() throws { 7 | let rfc3339Formatter = try XCTUnwrap(DateFormatter.wordPressCom) 8 | 9 | XCTAssertEqual(rfc3339Formatter.timeZone, TimeZone(secondsFromGMT: 0)) 10 | XCTAssertEqual(rfc3339Formatter.locale, Locale(identifier: "en_US_POSIX")) 11 | XCTAssertEqual(rfc3339Formatter.dateFormat, "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/MockServiceRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import WordPressKit 3 | 4 | struct MockServiceRequest: ServiceRequest { 5 | var path: String { 6 | return "localhost/path/" 7 | } 8 | 9 | var apiVersion: WordPressComRESTAPIVersion { 10 | return ._1_2 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/Models/Stats/V2/Emails/StatsEmailsSummaryDataTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | final class StatsEmailsSummaryDataTests: XCTestCase { 5 | func testEmailsSummaryDecoding() throws { 6 | let json = getJSON("stats-emails-summary") 7 | 8 | let emailsSummary = StatsEmailsSummaryData(jsonDictionary: json) 9 | XCTAssertNotNil(emailsSummary, "StatsEmailsSummaryTimeIntervalData not decoded as expected") 10 | let post = emailsSummary!.posts[0] 11 | 12 | XCTAssertEqual(emailsSummary?.posts.count, 4) 13 | XCTAssertEqual(post.link, URL(string: "https://www.test1.com")) 14 | XCTAssertEqual(post.title, "A great testing post") 15 | XCTAssertEqual(post.type, .post) 16 | XCTAssertEqual(post.clicks, 2202) 17 | XCTAssertEqual(post.opens, 453192) 18 | } 19 | } 20 | 21 | private extension StatsEmailsSummaryDataTests { 22 | func getJSON(_ fileName: String) -> [String: AnyObject] { 23 | let path = Bundle(for: type(of: self)).path(forResource: fileName, ofType: "json")! 24 | let data = try! Data(contentsOf: URL(fileURLWithPath: path)) 25 | return try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: AnyObject] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/Models/Stats/V2/Insights/MockData/stats-insight-publicize.json: -------------------------------------------------------------------------------- 1 | { 2 | "services": [ 3 | { 4 | "service": "twitter", 5 | "followers": 3210876 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/Models/Stats/V2/Insights/MockData/stats-insight-summary.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2024-03-26", 3 | "period": "day", 4 | "views": 1234, 5 | "visitors": 910, 6 | "likes": 65, 7 | "reblogs": 2, 8 | "comments": 3, 9 | "followers": 104796150 10 | } 11 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | final class StatsSubscribersSummaryDataTests: XCTestCase { 5 | func testEmailsSummaryDecoding() throws { 6 | let json = getJSON("stats-subscribers") 7 | 8 | let summary = StatsSubscribersSummaryData(date: Date(), period: .day, jsonDictionary: json) 9 | XCTAssertNotNil(summary, "StatsSubscribersSummaryData not decoded as expected") 10 | let history = summary!.history 11 | let mostRecentDay = history.last! 12 | 13 | XCTAssertEqual(mostRecentDay.date, StatsSubscribersSummaryData.dateFormatter.date(from: "2024-04-22")) 14 | XCTAssertEqual(mostRecentDay.count, 77) 15 | } 16 | } 17 | 18 | private extension StatsSubscribersSummaryDataTests { 19 | func getJSON(_ fileName: String) -> [String: AnyObject] { 20 | let path = Bundle(for: type(of: self)).path(forResource: fileName, ofType: "json")! 21 | let data = try! Data(contentsOf: URL(fileURLWithPath: path)) 22 | return try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: AnyObject] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/NSDate+WordPressComTests.swift: -------------------------------------------------------------------------------- 1 | @testable import WordPressKit 2 | import XCTest 3 | 4 | class NSDateWordPressComTests: XCTestCase { 5 | 6 | func testValidRFC3339DateFromString() { 7 | XCTAssertEqual( 8 | NSDate.with(wordPressComJSONString: "2023-03-19T15:00:00Z"), 9 | Date(timeIntervalSince1970: 1_679_238_000) 10 | ) 11 | } 12 | 13 | func testInvalidRFC3339DateFromString() { 14 | XCTAssertNil(NSDate.with(wordPressComJSONString: "2024-01-01")) 15 | } 16 | 17 | func testInvalidDateFromString() { 18 | XCTAssertNil(NSDate.with(wordPressComJSONString: "not a date")) 19 | } 20 | 21 | func testValidRFC3339StringFromDate() { 22 | XCTAssertEqual( 23 | NSDate(timeIntervalSince1970: 1_679_238_000).wordPressComJSONString(), 24 | // Apparently, NSDateFormatter doesn't offer a way to specify Z vs +0000. 25 | // This might go all the way back to the ISO 8601 and RFC 3339 specs overlap. 26 | "2023-03-19T15:00:00+0000" 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/RESTTestable.swift: -------------------------------------------------------------------------------- 1 | @testable import WordPressKit 2 | 3 | /// Protocol to be used when testing REST Remotes 4 | /// 5 | protocol RESTTestable { 6 | func getRestApi() -> WordPressComRestApi 7 | } 8 | 9 | extension RESTTestable { 10 | func getRestApi() -> WordPressComRestApi { 11 | return WordPressComRestApi(oAuthToken: nil, userAgent: nil) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/RemoteReaderSiteInfoSubscriptionTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | class RemoteReaderSiteInfoSubscriptionTests: XCTestCase { 5 | func testRemoteReaderSiteInfoSubscriptionPost() { 6 | let postSubscription = RemoteReaderSiteInfoSubscriptionPost(dictionary: ["send_posts": false]) 7 | XCTAssertNotNil(postSubscription) 8 | XCTAssertFalse(postSubscription.sendPosts) 9 | } 10 | 11 | func testRemoteReaderSiteInfoSubscriptionEmail() { 12 | let emailSubscription = RemoteReaderSiteInfoSubscriptionEmail(dictionary: ["send_posts": true, 13 | "send_comments": false, 14 | "post_delivery_frequency": "instantly"]) 15 | XCTAssertNotNil(emailSubscription) 16 | XCTAssertFalse(emailSubscription.sendComments) 17 | XCTAssertTrue(emailSubscription.sendPosts) 18 | XCTAssertEqual("instantly", emailSubscription.postDeliveryFrequency) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/SiteCreationResponseDecodingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | final class SiteCreationResponseDecodingTests: XCTestCase { 5 | 6 | func testSiteCreationResponseDecoding_IsSuccessful() { 7 | // Given 8 | let testClass: AnyClass = SiteCreationResponseDecodingTests.self 9 | let bundle = Bundle(for: testClass) 10 | let url = bundle.url(forResource: "site-creation-success", withExtension: "json") 11 | 12 | XCTAssertNotNil(url) 13 | let jsonURL = url! 14 | 15 | XCTAssertNoThrow(try Data(contentsOf: jsonURL)) 16 | let jsonData = try! Data(contentsOf: jsonURL) 17 | 18 | // When 19 | let decoder = JSONDecoder() 20 | XCTAssertNoThrow(try decoder.decode(SiteCreationResponse.self, from: jsonData)) 21 | let response = try! decoder.decode(SiteCreationResponse.self, from: jsonData) 22 | 23 | // Then 24 | XCTAssertTrue(response.success) 25 | 26 | let site = response.createdSite 27 | XCTAssertEqual(site.identifier, "156355635") 28 | XCTAssertEqual(site.title, "10711c") 29 | XCTAssertEqual(site.urlString, "https://10711c.wordpress.com/") 30 | XCTAssertEqual(site.xmlrpcString, "https://10711c.wordpress.com/xmlrpc.php") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/SiteCreationSegmentsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | final class SiteCreationSegmentsTests: RemoteTestCase, RESTTestable { 5 | 6 | func testSiteSegmentsRequest_Succeeds() { 7 | // Given 8 | let endpoint = "segments" 9 | let fileName = "site-segments-multiple.json" 10 | stubRemoteResponse(endpoint, filename: fileName, contentType: .ApplicationJSON) 11 | 12 | let expectedSegmentsCount = 5 13 | 14 | // When, Then 15 | let segmentsExpectation = expectation(description: "Initiate site segments request") 16 | let remote = WordPressComServiceRemote(wordPressComRestApi: getRestApi()) 17 | remote.retrieveSegments(completion: { result in 18 | segmentsExpectation.fulfill() 19 | switch result { 20 | case .success(let segments): 21 | XCTAssertNotNil(segments) 22 | 23 | let mobileSegmentsCount = segments.count 24 | XCTAssertEqual(mobileSegmentsCount, expectedSegmentsCount) 25 | 26 | case .failure: 27 | XCTFail() 28 | } 29 | }) 30 | 31 | waitForExpectations(timeout: timeout) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/SitePluginTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import WordPressKit 4 | 5 | class SitePluginTests: XCTestCase { 6 | func testSitePluginCapabilitiesEquatableSucceeds() { 7 | let sitePluginCapabilitiesA = SitePluginCapabilities(modify: true, autoupdate: true) 8 | let sitePluginCapabilitiesB = SitePluginCapabilities(modify: true, autoupdate: true) 9 | 10 | XCTAssertEqual(sitePluginCapabilitiesA, sitePluginCapabilitiesB) 11 | } 12 | 13 | func testSitePluginCapabilitiesFails() { 14 | let sitePluginCapabilitiesA = SitePluginCapabilities(modify: true, autoupdate: true) 15 | let sitePluginCapabilitiesB = SitePluginCapabilities(modify: false, autoupdate: false) 16 | 17 | XCTAssertNotEqual(sitePluginCapabilitiesA, sitePluginCapabilitiesB) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/SiteVerticalsPromptResponseDecodingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | final class SiteVerticalsPromptResponseDecodingTests: XCTestCase { 5 | 6 | func testSiteVerticalsPromptResponseDecoding_IsSuccessful() { 7 | // Given 8 | let testClass: AnyClass = SiteVerticalsPromptResponseDecodingTests.self 9 | let bundle = Bundle(for: testClass) 10 | let url = bundle.url(forResource: "site-verticals-prompt", withExtension: "json") 11 | 12 | XCTAssertNotNil(url) 13 | let jsonURL = url! 14 | 15 | XCTAssertNoThrow(try Data(contentsOf: jsonURL)) 16 | let jsonData = try! Data(contentsOf: jsonURL) 17 | 18 | // When 19 | let decoder = JSONDecoder() 20 | XCTAssertNoThrow(try decoder.decode(SiteVerticalsPrompt.self, from: jsonData)) 21 | let response = try! decoder.decode(SiteVerticalsPrompt.self, from: jsonData) 22 | 23 | let expectedTitle = "What will your blog be about?" 24 | let expectedSubtitle = "We'll use your answer to add sections to your website." 25 | let expectedHint = "e.g., Landscaping, Consulting... etc" 26 | 27 | // Then 28 | XCTAssertEqual(response.title, expectedTitle) 29 | XCTAssertEqual(response.subtitle, expectedSubtitle) 30 | XCTAssertEqual(response.hint, expectedHint) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/TestCollector+Constants.swift: -------------------------------------------------------------------------------- 1 | import BuildkiteTestCollector 2 | import Foundation 3 | 4 | extension TestCollector { 5 | 6 | static let apiHost = "analytics-api.buildkite.com" 7 | } 8 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/TransactionsServiceRemoteTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | class TransactionsServiceRemoteTests: RemoteTestCase, RESTTestable { 5 | 6 | let supportedCountriesSuccessFileName = "supported-countries-success.json" 7 | var remote: TransactionsServiceRemote! 8 | 9 | override func setUp() { 10 | super.setUp() 11 | remote = TransactionsServiceRemote(wordPressComRestApi: getRestApi()) 12 | } 13 | 14 | func testGetSupportedCountries() { 15 | let expect = expectation(description: "Get supported countries success") 16 | 17 | stubRemoteResponse("me/transactions/supported-countries/", 18 | filename: supportedCountriesSuccessFileName, 19 | contentType: .ApplicationJSON, 20 | status: 200) 21 | 22 | remote.getSupportedCountries(success: { (countryList) in 23 | expect.fulfill() 24 | XCTAssert(countryList.count == 239) 25 | XCTAssert(countryList[0].code == "TR") 26 | XCTAssert(countryList[0].name == "Turkey") 27 | }) { (_) in 28 | XCTFail("This callback shouldn't get called") 29 | expect.fulfill() 30 | } 31 | waitForExpectations(timeout: timeout, handler: nil) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/Utilities/ChecksumUtilTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | class ChecksumUtilTests: XCTestCase { 5 | private let blockSettingsNOTThemeJSONResponseFilename = "wp-block-editor-v1-settings-success-NotThemeJSON" 6 | private let blockSettingsThemeJSONResponseFilename = "wp-block-editor-v1-settings-success-ThemeJSON" 7 | 8 | func testChecksumGeneration() { 9 | let firstObject = try! JSONDecoder().decode(RemoteBlockEditorSettings.self, from: mockedData(withFilename: blockSettingsNOTThemeJSONResponseFilename)) 10 | let firstChecksum = ChecksumUtil.checksum(from: firstObject) 11 | XCTAssertFalse(firstChecksum.isEmpty) 12 | 13 | let secondObject = try! JSONDecoder().decode(RemoteBlockEditorSettings.self, from: mockedData(withFilename: blockSettingsThemeJSONResponseFilename)) 14 | let secondChecksum = ChecksumUtil.checksum(from: secondObject) 15 | XCTAssertFalse(secondChecksum.isEmpty) 16 | 17 | XCTAssertNotEqual(firstChecksum, secondChecksum) 18 | } 19 | 20 | func mockedData(withFilename filename: String) -> Data { 21 | let json = Bundle(for: ChecksumUtilTests.self).url(forResource: filename, withExtension: "json")! 22 | return try! Data(contentsOf: json) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/Utilities/FeatureFlagSerializationTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | class FeatureFlagSerializationTest: XCTestCase { 5 | /// The `FeatureFlagList` typealias can't have its `encode` method overridden, so instead we ensure that it's serializing its messages properly. 6 | func testThatSingleFeatureFlagIsSerializedCorrectly() throws { 7 | let title = UUID().uuidString 8 | let value = Bool.random() 9 | 10 | let flags = [FeatureFlag(title: title, value: value)] 11 | let json = String(bytes: try! JSONEncoder().encode(flags.dictionaryValue), encoding: .utf8) 12 | XCTAssertEqual("{\"\(title)\":\(value)}", json) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/Utilities/HTTPBodyEncodingTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | @testable import WordPressKit 4 | 5 | class HTTPBodyEncodingTests: XCTestCase { 6 | 7 | func testRoundTrip() throws { 8 | // The test strings are generated using the following command line: 9 | // $ echo -n "👋" | iconv -f UTF-8 -t | base64 10 | 11 | XCTAssertEqual(try decode(base64EncodedBody: "8J+Riw==", charset: "UTF-8"), "👋") 12 | XCTAssertEqual(try decode(base64EncodedBody: "2D3cSw==", charset: "UTF-16BE"), "👋") 13 | XCTAssertEqual(try decode(base64EncodedBody: "PdhL3A==", charset: "UTF-16LE"), "👋") 14 | } 15 | 16 | private func decode(base64EncodedBody: String, charset: String, file: StaticString = #file, line: UInt = #line) throws -> String? { 17 | let response = try XCTUnwrap(HTTPURLResponse(url: URL(string: "https://wordpress.org")!, statusCode: 200, httpVersion: "2", headerFields: [ 18 | "Content-Type": "text/html; charset=\(charset)" 19 | ])) 20 | let originalBodyData = try XCTUnwrap(Data(base64Encoded: base64EncodedBody)) 21 | return HTTPAPIResponse(response: response, body: originalBodyData).bodyText 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/WordPressComServiceRemoteTests+SiteVerticals.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | final class SiteCreationVerticalsTests: RemoteTestCase, RESTTestable { 5 | 6 | func testSiteVerticalsRequest_Succeeds() { 7 | // Given 8 | let endpoint = "verticals" 9 | let fileName = "site-verticals-multiple.json" 10 | stubRemoteResponse(endpoint, filename: fileName, contentType: .ApplicationJSON) 11 | 12 | let expectedSearch = "landscap" 13 | let expectedLimit = 5 14 | 15 | let request = SiteVerticalsRequest(search: expectedSearch, limit: expectedLimit) 16 | 17 | // When, Then 18 | let verticalsExpectation = expectation(description: "Initiate site verticals request") 19 | let remote = WordPressComServiceRemote(wordPressComRestApi: getRestApi()) 20 | remote.retrieveVerticals(request: request) { result in 21 | verticalsExpectation.fulfill() 22 | 23 | switch result { 24 | case .success(let verticals): 25 | XCTAssertNotNil(verticals) 26 | 27 | let actualLimit = verticals.count 28 | XCTAssertEqual(actualLimit, expectedLimit) 29 | 30 | case .failure: 31 | XCTFail() 32 | } 33 | } 34 | 35 | waitForExpectations(timeout: timeout) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/WordPressComServiceRemoteTests+SiteVerticalsPrompt.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WordPressKit 3 | 4 | final class SiteCreationVerticalsPromptTests: RemoteTestCase, RESTTestable { 5 | 6 | func testSiteVerticalsPromptRequest_Succeeds() { 7 | // Given 8 | let endpoint = "verticals/prompt" 9 | let fileName = "site-verticals-prompt.json" 10 | stubRemoteResponse(endpoint, filename: fileName, contentType: .ApplicationJSON) 11 | 12 | // When 13 | let request = Int64(1) as SiteVerticalsPromptRequest 14 | 15 | // Then 16 | let promptExpectation = expectation(description: "Initiate site verticals prompt request") 17 | let remote = WordPressComServiceRemote(wordPressComRestApi: getRestApi()) 18 | remote.retrieveVerticalsPrompt(request: request) { prompt in 19 | promptExpectation.fulfill() 20 | XCTAssertNotNil(prompt) 21 | } 22 | 23 | waitForExpectations(timeout: timeout) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/Tests/XMLRPCTestable.swift: -------------------------------------------------------------------------------- 1 | @testable import WordPressKit 2 | 3 | enum XMLRPCTestableConstants { 4 | static let xmlRpcUrl = "http://test.com/xmlrpc.php" 5 | static let xmlRpcUserName = "username" 6 | static let xmlRpcPassword = "password" 7 | 8 | static let xmlRpcUnauthorizedErrorCode = 401 9 | static let xmlRpcForbiddenErrorCode = 403 10 | static let xmlRpcNotFoundErrorCode = 404 11 | static let xmlRpcParseErrorCode = -32700 12 | 13 | static let xmlRpcBadAuthFailureFilename = "xmlrpc-bad-username-password-error.xml" 14 | static let xmlRpcMalformedRequestXMLFailureFilename = "xmlrpc-malformed-request-xml-error.xml" 15 | } 16 | 17 | /// Protocol to be used when testing XMLRPC Remotes 18 | /// 19 | protocol XMLRPCTestable { 20 | func getXmlRpcApi() -> WordPressOrgXMLRPCApi 21 | } 22 | 23 | extension XMLRPCTestable { 24 | func getXmlRpcApi() -> WordPressOrgXMLRPCApi { 25 | return WordPressOrgXMLRPCApi(endpoint: URL(string: XMLRPCTestableConstants.xmlRpcUrl)!, userAgent: nil) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/WordPressKitTests/WordPressKitTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /WordPressKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WordPressKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WordPressKit.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WordPressKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WordPressKitTests/backup-get-backup-status-complete-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "downloadId": 283844, 3 | "rewindId": "1608510088.971", 4 | "backupPoint": "2020-12-21T00:21:28+00:00", 5 | "startedAt": "2020-12-21T08:12:41+00:00", 6 | "downloadCount": 0, 7 | "validUntil": "2020-12-22T08:12:48+00:00", 8 | "url": "https://public-api.wordpress.com/wpcom/v2/sites/185351267/rewind/downloads/283844/data?token=DNAjse5rAVkbPfNjVQbwXpiBr2JQAAsNTUs6T83mD3C3MrYRqRJRsrqNw5HlgTNh" 9 | } 10 | -------------------------------------------------------------------------------- /WordPressKitTests/backup-get-backup-status-in-progress-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "downloadId": 283987, 3 | "rewindId": "1608555731.536", 4 | "backupPoint": "2020-12-21T13:02:11+00:00", 5 | "startedAt": "2020-12-22T00:06:42+00:00", 6 | "progress": 88 7 | } 8 | -------------------------------------------------------------------------------- /WordPressKitTests/backup-prepare-backup-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "downloadId": 283844, 3 | "rewindId": "1608510088.971", 4 | "backupPoint": "2020-12-21T00:21:28+00:00", 5 | "startedAt": "2020-12-21T08:12:40+00:00", 6 | "progress": 0 7 | } 8 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-auth-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "authorization_required", 3 | "message": "An active access token must be used to query information about the current user." 4 | } 5 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-change-aboutme-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "I like the color blue and paperclips!" 3 | } 4 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-change-display-name-bad-json-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "display_name": "jimmy" 3 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-change-display-name-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "display_name": "jimmy" 3 | } 4 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-change-email-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_email": "jimthetester@thetestemail.org", 3 | "user_email_change_pending": true, 4 | "new_user_email": "jimthetester-newemail@thetestemail.org" 5 | } 6 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-change-firstname-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "first_name": "Jimmy" 3 | } 4 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-change-invalid-input-failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "invalid_input", 3 | "message": "No settings were provided to the endpoint to set." 4 | } 5 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-change-lastname-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_name": "Test" 3 | } 4 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-change-primary-site-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "primary_site_ID": 11112222 3 | } 4 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-change-web-address-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_URL": "http:\/\/jimthetester.wordpress.com" 3 | } 4 | -------------------------------------------------------------------------------- /WordPressKitTests/me-settings-revert-email-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_email_change_pending": false, 3 | "new_user_email": "", 4 | "user_email": "jimthetester@thetestemail.org" 5 | } 6 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | default_platform(:ios) 4 | 5 | SWIFTLINT_PATH = './Pods/SwiftLint/swiftlint' 6 | 7 | platform :ios do 8 | lane :lint do 9 | swiftlint( 10 | executable: SWIFTLINT_PATH, 11 | strict: true, 12 | quiet: true 13 | ) 14 | end 15 | 16 | lane :format do 17 | swiftlint( 18 | executable: SWIFTLINT_PATH, 19 | strict: true, 20 | quiet: true, 21 | mode: :fix, 22 | format: true 23 | ) 24 | end 25 | 26 | desc 'Builds the project and runs tests' 27 | lane :test do 28 | run_tests( 29 | scheme: 'WordPressKit', 30 | prelaunch_simulator: true, 31 | result_bundle: true, 32 | buildlog_path: File.join(__dir__, '.build', 'logs'), 33 | derived_data_path: File.join(__dir__, '.build', 'derived-data') 34 | ) 35 | rescue StandardError => e 36 | $stdout.puts '^^^ +++' 37 | $stdout.puts 'Test failed!' 38 | 39 | result_bundle_path = 'fastlane/test_output/WordPressKit.xcresult' 40 | if File.exist?(File.join(Bundler.root, result_bundle_path)) 41 | zip( 42 | path: result_bundle_path, 43 | output_path: "#{result_bundle_path}.zip", 44 | verbose: false 45 | ) 46 | end 47 | 48 | raise e 49 | end 50 | end 51 | --------------------------------------------------------------------------------