Skip to content
This repository was archived by the owner on Jun 4, 2025. It is now read-only.

Commit 06b0b76

Browse files
committed
Revert "Remove unused Gravatar"
This reverts commit 5f04d80.
1 parent 1bc9958 commit 06b0b76

File tree

6 files changed

+375
-0
lines changed

6 files changed

+375
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import Foundation
2+
3+
/// Helper Enum that specifies all of the available Gravatar Image Ratings
4+
/// TODO: Convert into a pure Swift String Enum. It's done this way to maintain ObjC Compatibility
5+
///
6+
@available(*, deprecated, message: "Use `Rating` from the Gravatar iOS SDK. See: https://github.com/Automattic/Gravatar-SDK-iOS.")
7+
@objc
8+
public enum GravatarRatings: Int {
9+
case g
10+
case pg
11+
case r
12+
case x
13+
case `default`
14+
15+
func stringValue() -> String {
16+
switch self {
17+
case .default:
18+
fallthrough
19+
case .g:
20+
return "g"
21+
case .pg:
22+
return "pg"
23+
case .r:
24+
return "r"
25+
case .x:
26+
return "x"
27+
}
28+
}
29+
}
30+
31+
/// Helper Enum that specifies some of the options for default images
32+
/// To see all available options, visit : https://en.gravatar.com/site/implement/images/
33+
///
34+
@available(*, deprecated, message: "Use `DefaultAvatarOption` from the Gravatar iOS SDK. See: https://github.com/Automattic/Gravatar-SDK-iOS.")
35+
public enum GravatarDefaultImage: String {
36+
case fileNotFound = "404"
37+
case mp
38+
case identicon
39+
}
40+
41+
@available(*, deprecated, message: "Use `AvatarURL` from the Gravatar iOS SDK. See: https://github.com/Automattic/Gravatar-SDK-iOS")
42+
public struct Gravatar {
43+
fileprivate struct Defaults {
44+
static let scheme = "https"
45+
static let host = "secure.gravatar.com"
46+
static let unknownHash = "ad516503a11cd5ca435acc9bb6523536"
47+
static let baseURL = "https://gravatar.com/avatar"
48+
static let imageSize = 80
49+
}
50+
51+
public let canonicalURL: URL
52+
53+
public func urlWithSize(_ size: Int, defaultImage: GravatarDefaultImage? = nil) -> URL {
54+
var components = URLComponents(url: canonicalURL, resolvingAgainstBaseURL: false)!
55+
components.query = "s=\(size)&d=\(defaultImage?.rawValue ?? GravatarDefaultImage.fileNotFound.rawValue)"
56+
return components.url!
57+
}
58+
59+
public static func isGravatarURL(_ url: URL) -> Bool {
60+
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
61+
return false
62+
}
63+
64+
guard let host = components.host, host.hasSuffix(".gravatar.com") else {
65+
return false
66+
}
67+
68+
guard url.path.hasPrefix("/avatar/") else {
69+
return false
70+
}
71+
72+
return true
73+
}
74+
75+
/// Returns the Gravatar URL, for a given email, with the specified size + rating.
76+
///
77+
/// - Parameters:
78+
/// - email: the user's email
79+
/// - size: required download size
80+
/// - rating: image rating filtering
81+
///
82+
/// - Returns: Gravatar's URL
83+
///
84+
public static func gravatarUrl(for email: String,
85+
defaultImage: GravatarDefaultImage? = nil,
86+
size: Int? = nil,
87+
rating: GravatarRatings = .default) -> URL? {
88+
let hash = gravatarHash(of: email)
89+
let targetURL = String(format: "%@/%@?d=%@&s=%d&r=%@",
90+
Defaults.baseURL,
91+
hash,
92+
defaultImage?.rawValue ?? GravatarDefaultImage.fileNotFound.rawValue,
93+
size ?? Defaults.imageSize,
94+
rating.stringValue())
95+
return URL(string: targetURL)
96+
}
97+
98+
/// Returns the gravatar hash of an email
99+
///
100+
/// - Parameter email: the email associated with the gravatar
101+
/// - Returns: hashed email
102+
///
103+
/// This really ought to be in a different place, like Gravatar.swift, but there's
104+
/// lots of duplication around gravatars -nh
105+
private static func gravatarHash(of email: String) -> String {
106+
return email
107+
.lowercased()
108+
.trimmingCharacters(in: .whitespaces)
109+
.sha256Hash()
110+
}
111+
}
112+
113+
@available(*, deprecated, message: "Usage of the deprecated type: Gravatar.")
114+
extension Gravatar: Equatable {}
115+
116+
@available(*, deprecated, message: "Usage of the deprecated type: Gravatar.")
117+
public func ==(lhs: Gravatar, rhs: Gravatar) -> Bool {
118+
return lhs.canonicalURL == rhs.canonicalURL
119+
}
120+
121+
@available(*, deprecated, message: "Usage of the deprecated type: Gravatar.")
122+
public extension Gravatar {
123+
@available(*, deprecated, message: "Usage of the deprecated type: Gravatar.")
124+
init?(_ url: URL) {
125+
guard Gravatar.isGravatarURL(url) else {
126+
return nil
127+
}
128+
129+
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
130+
return nil
131+
}
132+
133+
components.scheme = Defaults.scheme
134+
components.host = Defaults.host
135+
components.query = nil
136+
137+
// Treat [email protected] as a nil url
138+
guard url.lastPathComponent != Defaults.unknownHash else {
139+
return nil
140+
}
141+
142+
guard let sanitizedURL = components.url else {
143+
return nil
144+
}
145+
146+
self.canonicalURL = sanitizedURL
147+
}
148+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import Foundation
2+
import UIKit
3+
4+
#if SWIFT_PACKAGE
5+
import WordPressUIObjC
6+
#endif
7+
8+
/// Wrapper class used to ensure removeObserver is called
9+
private class GravatarNotificationWrapper {
10+
let observer: NSObjectProtocol
11+
12+
init(observer: NSObjectProtocol) {
13+
self.observer = observer
14+
}
15+
16+
deinit {
17+
NotificationCenter.default.removeObserver(observer)
18+
}
19+
}
20+
21+
/// UIImageView Helper Methods that allow us to download a Gravatar, given the User's Email
22+
///
23+
extension UIImageView {
24+
25+
/// Downloads and sets the User's Gravatar, given his email.
26+
/// TODO: This is a convenience method. Please, remove once all of the code has been migrated over to Swift.
27+
///
28+
/// - Parameters:
29+
/// - email: the user's email
30+
/// - rating: expected image rating
31+
///
32+
/// This method uses deprecated types. Please check the deprecation warning in `GravatarRatings`. Also check out the UIImageView extension from the Gravatar iOS SDK as an alternative to download images. See: https://github.com/Automattic/Gravatar-SDK-iOS.
33+
@available(*, deprecated, message: "Usage of the deprecated type: GravatarRatings.")
34+
@objc
35+
public func downloadGravatarWithEmail(_ email: String, rating: GravatarRatings) {
36+
downloadGravatarWithEmail(email, rating: rating, placeholderImage: .gravatarPlaceholderImage)
37+
}
38+
39+
/// Downloads and sets the User's Gravatar, given his email.
40+
///
41+
/// - Parameters:
42+
/// - email: the user's email
43+
/// - rating: expected image rating
44+
/// - placeholderImage: Image to be used as Placeholder
45+
/// This method uses deprecated types. Please check the deprecation warning in `GravatarRatings`. Also check out the UIImageView extension from the Gravatar iOS SDK as an alternative to download images. See: https://github.com/Automattic/Gravatar-SDK-iOS.
46+
@available(*, deprecated, message: "Usage of the deprecated type: GravatarRatings.")
47+
@objc
48+
public func downloadGravatarWithEmail(_ email: String, rating: GravatarRatings = .default, placeholderImage: UIImage = .gravatarPlaceholderImage) {
49+
let gravatarURL = Gravatar.gravatarUrl(for: email, size: gravatarDefaultSize(), rating: rating)
50+
51+
listenForGravatarChanges(forEmail: email)
52+
downloadImage(from: gravatarURL, placeholderImage: placeholderImage)
53+
}
54+
55+
/// Configures the UIImageView to listen for changes to the gravatar it is displaying
56+
public func listenForGravatarChanges(forEmail trackedEmail: String) {
57+
if let currentObersver = gravatarWrapper?.observer {
58+
NotificationCenter.default.removeObserver(currentObersver)
59+
gravatarWrapper = nil
60+
}
61+
62+
let observer = NotificationCenter.default.addObserver(forName: .GravatarImageUpdateNotification, object: nil, queue: nil) { [weak self] (notification) in
63+
guard let userInfo = notification.userInfo,
64+
let email = userInfo[Defaults.emailKey] as? String,
65+
email == trackedEmail,
66+
let image = userInfo[Defaults.imageKey] as? UIImage else {
67+
return
68+
}
69+
70+
self?.image = image
71+
}
72+
gravatarWrapper = GravatarNotificationWrapper(observer: observer)
73+
}
74+
75+
/// Stores the gravatar observer
76+
///
77+
fileprivate var gravatarWrapper: GravatarNotificationWrapper? {
78+
get {
79+
return objc_getAssociatedObject(self, &Defaults.gravatarWrapperKey) as? GravatarNotificationWrapper
80+
}
81+
set {
82+
objc_setAssociatedObject(self, &Defaults.gravatarWrapperKey, newValue as AnyObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
83+
}
84+
}
85+
86+
/// Downloads the provided Gravatar.
87+
///
88+
/// - Parameters:
89+
/// - gravatar: the user's Gravatar
90+
/// - placeholder: Image to be used as Placeholder
91+
/// - animate: enable/disable fade in animation
92+
/// - failure: Callback block to be invoked when an error occurs while fetching the Gravatar image
93+
///
94+
/// This method uses deprecated types. Please check the deprecation warning in `GravatarRatings`. Also check out the UIImageView extension from the Gravatar iOS SDK as an alternative to download images. See: https://github.com/Automattic/Gravatar-SDK-iOS.
95+
@available(*, deprecated, message: "Usage of the deprecated type: Gravatar.")
96+
public func downloadGravatar(_ gravatar: Gravatar?, placeholder: UIImage, animate: Bool, failure: ((Error?) -> Void)? = nil) {
97+
guard let gravatar = gravatar else {
98+
self.image = placeholder
99+
return
100+
}
101+
102+
// Starting with iOS 10, it seems `initWithCoder` uses a default size
103+
// of 1000x1000, which was messing with our size calculations for gravatars
104+
// on newly created table cells.
105+
// Calling `layoutIfNeeded()` forces UIKit to calculate the actual size.
106+
layoutIfNeeded()
107+
108+
let size = Int(ceil(frame.width * UIScreen.main.scale))
109+
let url = gravatar.urlWithSize(size)
110+
111+
self.downloadImage(from: url,
112+
placeholderImage: placeholder,
113+
success: { image in
114+
guard image != self.image else {
115+
return
116+
}
117+
118+
self.image = image
119+
if animate {
120+
self.fadeInAnimation()
121+
}
122+
}, failure: { error in
123+
failure?(error)
124+
})
125+
}
126+
127+
/// Sets an Image Override in both, AFNetworking's Private Cache + NSURLCache
128+
///
129+
/// - Parameters:
130+
/// - image: new UIImage
131+
/// - rating: rating for the new image.
132+
/// - email: associated email of the new gravatar
133+
/// - Note: You may want to use `updateGravatar(image:, email:)` instead
134+
///
135+
/// *WHY* is this required?. *WHY* life has to be so complicated?, is the universe against us?
136+
/// This has been implemented as a workaround. During Upload, we want any async calls made to the
137+
/// `downloadGravatar` API to return the "Fresh" image.
138+
///
139+
/// Note II:
140+
/// We cannot just clear NSURLCache, since the helper that's supposed to do that, is broken since iOS 8.
141+
/// Ref: Ref: http://blog.airsource.co.uk/2014/10/11/nsurlcache-ios8-broken/
142+
///
143+
/// P.s.:
144+
/// Hope buddah, and the code reviewer, can forgive me for this hack.
145+
///
146+
@available(*, deprecated, message: "Usage of the deprecated type: GravatarRatings.")
147+
@objc public func overrideGravatarImageCache(_ image: UIImage, rating: GravatarRatings, email: String) {
148+
guard let gravatarURL = Gravatar.gravatarUrl(for: email, size: gravatarDefaultSize(), rating: rating) else {
149+
return
150+
}
151+
152+
listenForGravatarChanges(forEmail: email)
153+
overrideImageCache(for: gravatarURL, with: image)
154+
}
155+
156+
/// Updates the gravatar image for the given email, and notifies all gravatar image views
157+
///
158+
/// - Parameters:
159+
/// - image: the new UIImage
160+
/// - email: associated email of the new gravatar
161+
@objc public func updateGravatar(image: UIImage, email: String?) {
162+
self.image = image
163+
guard let email = email else {
164+
return
165+
}
166+
NotificationCenter.default.post(name: .GravatarImageUpdateNotification, object: self, userInfo: [Defaults.emailKey: email, Defaults.imageKey: image])
167+
}
168+
169+
// MARK: - Private Helpers
170+
171+
/// Returns the required gravatar size. If the current view's size is zero, falls back to the default size.
172+
///
173+
private func gravatarDefaultSize() -> Int {
174+
guard bounds.size.equalTo(.zero) == false else {
175+
return Defaults.imageSize
176+
}
177+
178+
let targetSize = max(bounds.width, bounds.height) * UIScreen.main.scale
179+
return Int(targetSize)
180+
}
181+
182+
/// Private helper structure: contains the default Gravatar parameters
183+
///
184+
private struct Defaults {
185+
static let imageSize = 80
186+
static var gravatarWrapperKey = 0x1000
187+
static let emailKey = "email"
188+
static let imageKey = "image"
189+
}
190+
}
191+
192+
public extension NSNotification.Name {
193+
static let GravatarImageUpdateNotification = NSNotification.Name(rawValue: "GravatarImageUpdateNotification")
194+
}

Sources/WordPressUI/Extensions/UIImageView+Networking.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import Foundation
22
import UIKit
33

4+
#if SWIFT_PACKAGE
5+
import WordPressUIObjC
6+
#endif
7+
48
public extension UIImageView {
59
enum ImageDownloadError: Error {
610
case noURLSpecifiedInRequest
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#import <Foundation/Foundation.h>
2+
3+
@interface NSString (Gravatar)
4+
5+
- (NSString *)sha256Hash;
6+
7+
@end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#import "NSString+Gravatar.h"
2+
#import <CommonCrypto/CommonDigest.h>
3+
4+
5+
@implementation NSString (Gravatar)
6+
7+
- (NSString *)sha256Hash
8+
{
9+
const char *cStr = [self UTF8String];
10+
unsigned char result[CC_SHA256_DIGEST_LENGTH];
11+
12+
CC_SHA256(cStr, (CC_LONG)strlen(cStr), result);
13+
14+
NSMutableString *hashString = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH*2];
15+
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
16+
[hashString appendFormat:@"%02x",result[i]];
17+
}
18+
return hashString;
19+
}
20+
21+
@end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../Extensions/NSString+Gravatar.h

0 commit comments

Comments
 (0)