{"action":"closed","issue":{"url":"https://api.github.com/repos/plone/Products.CMFPlone/issues/1263","repository_url":"https://api.github.com/repos/plone/Products.CMFPlone","labels_url":"https://api.github.com/repos/plone/Products.CMFPlone/issues/1263/labels{/name}","comments_url":"https://api.github.com/repos/plone/Products.CMFPlone/issues/1263/comments","events_url":"https://api.github.com/repos/plone/Products.CMFPlone/issues/1263/events","html_url":"https://github.com/plone/Products.CMFPlone/issues/1263","id":118814729,"node_id":"MDU6SXNzdWUxMTg4MTQ3Mjk=","number":1263,"title":"PLIP: Handle Portraits using PAS and PropertySheets only","user":{"login":"jensens","id":157140,"node_id":"MDQ6VXNlcjE1NzE0MA==","avatar_url":"https://avatars1.githubusercontent.com/u/157140?v=4","gravatar_id":"","url":"https://api.github.com/users/jensens","html_url":"https://github.com/jensens","followers_url":"https://api.github.com/users/jensens/followers","following_url":"https://api.github.com/users/jensens/following{/other_user}","gists_url":"https://api.github.com/users/jensens/gists{/gist_id}","starred_url":"https://api.github.com/users/jensens/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jensens/subscriptions","organizations_url":"https://api.github.com/users/jensens/orgs","repos_url":"https://api.github.com/users/jensens/repos","events_url":"https://api.github.com/users/jensens/events{/privacy}","received_events_url":"https://api.github.com/users/jensens/received_events","type":"User","site_admin":false},"labels":[{"id":274242289,"node_id":"MDU6TGFiZWwyNzQyNDIyODk=","url":"https://api.github.com/repos/plone/Products.CMFPlone/labels/03%20type:%20feature%20(plip)","name":"03 type: feature (plip)","color":"0000fa","default":false},{"id":213990385,"node_id":"MDU6TGFiZWwyMTM5OTAzODU=","url":"https://api.github.com/repos/plone/Products.CMFPlone/labels/31%20needs:%20help","name":"31 needs: help","color":"00fafa","default":false}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":{"url":"https://api.github.com/repos/plone/Products.CMFPlone/milestones/4","html_url":"https://github.com/plone/Products.CMFPlone/milestone/4","labels_url":"https://api.github.com/repos/plone/Products.CMFPlone/milestones/4/labels","id":1198783,"node_id":"MDk6TWlsZXN0b25lMTE5ODc4Mw==","number":4,"title":"Future","description":null,"creator":{"login":"jaroel","id":70700,"node_id":"MDQ6VXNlcjcwNzAw","avatar_url":"https://avatars1.githubusercontent.com/u/70700?v=4","gravatar_id":"","url":"https://api.github.com/users/jaroel","html_url":"https://github.com/jaroel","followers_url":"https://api.github.com/users/jaroel/followers","following_url":"https://api.github.com/users/jaroel/following{/other_user}","gists_url":"https://api.github.com/users/jaroel/gists{/gist_id}","starred_url":"https://api.github.com/users/jaroel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jaroel/subscriptions","organizations_url":"https://api.github.com/users/jaroel/orgs","repos_url":"https://api.github.com/users/jaroel/repos","events_url":"https://api.github.com/users/jaroel/events{/privacy}","received_events_url":"https://api.github.com/users/jaroel/received_events","type":"User","site_admin":false},"open_issues":23,"closed_issues":12,"state":"open","created_at":"2015-07-08T13:44:46Z","updated_at":"2019-06-20T20:50:01Z","due_on":null,"closed_at":null},"comments":2,"created_at":"2015-11-25T11:16:36Z","updated_at":"2019-06-20T20:50:01Z","closed_at":"2019-06-20T20:50:01Z","author_association":"MEMBER","body":"## Motivation\n\nPlone handles user properties with PAS - except portraits. Now any pas-plugin providing portrait images need to monkey patch Products.PlonePAS. Prominent examples are Products.Remember or SQL/LDAP plugins.\n\nThe root of the problem: Plone stores portraits in portal_memberdata in a BTree and uses its API (and at some part portal_membership) everywhere portraits are involved. It is in no way connected to the usual MutablePropertySheet.\nAssumptions\n\nAlso methods of MembershipTool and MemberdataTool starting with an underscore may be used outside of this class and can not be considered private.\nProposal & Implementation\n## Proposal\n- In future all portrait images are read (and stored) on (from) PropertySheets.\n- By default portrait images are stored as NamedBlobImage on the normal MutablePropertysheet.  \n- Images not located in ZODB (ldap, sql, ...) can be exposed too.\n- Images not delivered by Zope (i.e. Gravatar, Twitter, Github, Google) can be used too  \n  (implemenatation out of plip scope)\n- new clean API is introduced (utility) handling all tasks around portrait images.\n- current API on MembershipTool and MemberdataTool is kept but code replaced to use the new API, old API gets deprecated.\n- adapters on PropertySheet are responsible for sheet specific implementations.\n- add migration functionality \n## Implementation\n\nProducts.PlonePAS.sheet.MutablePropertySheet mutable_properties gets a new default property portrait. The name of the portrait property is not customizable to avoid confusion. The property is expected to implement plone.namedfile.interfaces.INamedImage using the implemenation plone.namedfile.file.NamedBlobImage.\n\nIntroduce view @@portrait_image providing new interface IPortraitView. This view acts similar to plone.namedfile.scaling.ImageScaling but works with/is bound to the sheets IPortraitPropertyPlugin. Parts can be recycled (possibly inherited).\n\nA new Portrait API is provided as a utility IPortraitUtility The utility is fetched and used as sketched below::\n\n```\n>>> from Products.PlonePAS.utils import get_portrait_utility\n>>> portrait_util = get_portrait_utility\n\n>>> portrait_util.set(userid, image)\n>>> portrait_util.delete(userid)\n>>> portrait_util.get(userid)\n<Some_INamedFile_implemenation at ...>\n\n>>> portrait_util.get(nonexistentuserid)\n<DefaultPortrait_INamedFile_implemenation at ...>\n\n>>> portrait_util.url(userid)\n'http://localhost:8080/Plone/acl_users/@@portrait_image/userid'\n\n>>> portrait_util.url(userid, scale='mini')\n'http://localhost:8080/Plone/acl_users/@@portrait_image/userid/mini'\n\n>>> portrait_util.tag(userid)\n'<img src=\"http://localhost:8080/Plone/acl_users/@@portrait_image/userid\" />'\n\n>>> portrait_util.tag(userid)\n'<img src=\"http://localhost:8080/Plone/acl_users/@@portrait_image/userid\" />'\n\n>>> portrait_util.tag(userid, scale='mini')\n'<img src=\"http://localhost:8080/Plone/acl_users/@@portrait_image/userid/mini\" />'\n```\n\nUtility has a helper method to get the first propertysheet with a property portrait. This can be restricted to return the first writable propertysheet (in case of get/delete).\n\nIntroducing a new Interface IPortraitPropertyHandler which provides methods convert_for_storage(nameimagedata), convert_from_storage(otherdata) and url(data). Thus also raw data (from ldap, sql, ...) can be converted to some INamedImage implemenation and vice versa. In case of externally delivered images relevant information can be returned.\n\nIn case of utilities get the first sheet with portrait property is fetched. Sheet is adapted by IImagePropertyHandler and convert_from_storage(data) is called. If data was empty the default portrait is used.\n\nIn case of set some INamedImage implemenation is expected as input. First writable sheet with portrait property is fetched. IImagePropertyHandler is adapted to the plugin. convert_for_storage(data) is called.\n\nIn case of delete an empty value is set.\n\nIn case of tag and url the first sheet with portrait property is fetched. Sheet is adapted by IImagePropertyHandler and url(data) is called. In case of tag a tag is build around the url.\n\nBy default PlonePAS provide two types of IPortraitHandler-adpaters:\n\n```\nOne for propertysheets such as the default Products.PlonePAS.sheet.MutablePropertySheet enabled to read and store plone.namedfile.interfaces.INamedBlobImage. \n\nAnother enabled to store raw image data only, such as ldap or sql integrations. Plugins-authors may use this or built upon it for more special cases. \n```\n\nOn MembershipTool the following methods are modified to use the new portrait API:\n\n```\ndeletePersonalPortrait\nchangeMemberPortrait\ngetPersonalPortrait\ndefault_portrait property on module and class, slighly different!?, both unused, we can remove this one.\ngetBadMembers - get rid of this one\nmanage_portrait_fix (property - dtml using getBadMembers) \n```\n\nOn MemberdataTool the following methods are modified to use the new portrait API:\n\n```\nportraits (property, at the moment a BTreeFolder)\n_getPortrait\n_setPortrait\n_deletePortrait\npruneMemberDataContents \n```\n\nNot part of this implementation, but an important use-case: Support of externally delivered images, i.e. Gravatar. In case of external hosted (usally non-mutable) images the provided api works too. For Gravatar portrait contains the reference i.e. E-Mail adress connected to Gravatar. The tag renderer of the utility needs renders a tag to the external image. Theres no need to get, set or delete an Gravatar image through the Plone.\n## Risks\n\nCurrently existing plugins may patch methods of MembershipTool and MemberdataTool. Such plugins probably break, but OTOH proposed change simplifies those plugins and is made for them. \n\nIf there is in a customized plone already a property portrait for whatever reason, it may result in data loss or weird behaviour. \n\nOn sites with lots of users there are lots of portraits to migrate. \n## Participants\n\nWho is signed up to do the work?\n- Jens W. Klein (sometime in future)\n- any takers?\n## Progress\n- sketched first implemenation in a customer project\n- lessons learned, new ideas discussion on plone-dev ist\n- plip written\n- plip discussion open \n"}}
{"actor":{"display_login":"jensens"}}