Changeset 3738687
- Timestamp:
- 10/10/2026 08:35:50 PM (7 days ago)
- Location:
- indieweb-post-kinds
- Files:
-
- 50 added
- 52 deleted
- 132 edited
- 1 copied
-
tags/4.0.0 (copied) (copied from indieweb-post-kinds/trunk )
-
tags/4.0.0/.gitattributes (modified) (2 diffs)
-
tags/4.0.0/css/kind.admin.min.css (modified) (1 diff)
-
tags/4.0.0/css/kind.admin.min.css.map (modified) (1 diff)
-
tags/4.0.0/css/kind.css (deleted)
-
tags/4.0.0/css/kind.css.map (deleted)
-
tags/4.0.0/css/kind.min.css (modified) (1 diff)
-
tags/4.0.0/css/kind.min.css.map (modified) (1 diff)
-
tags/4.0.0/develop.txt (modified) (1 diff)
-
tags/4.0.0/gruntlint.js (deleted)
-
tags/4.0.0/includes/class-kind-config.php (modified) (23 diffs)
-
tags/4.0.0/includes/class-kind-fields.php (modified) (24 diffs)
-
tags/4.0.0/includes/class-kind-media-metadata.php (modified) (13 diffs)
-
tags/4.0.0/includes/class-kind-media-view.php (modified) (7 diffs)
-
tags/4.0.0/includes/class-kind-menu-widget.php (modified) (11 diffs)
-
tags/4.0.0/includes/class-kind-metabox.php (modified) (40 diffs)
-
tags/4.0.0/includes/class-kind-plugins.php (modified) (7 diffs)
-
tags/4.0.0/includes/class-kind-post-widget.php (modified) (6 diffs)
-
tags/4.0.0/includes/class-kind-post.php (modified) (45 diffs)
-
tags/4.0.0/includes/class-kind-taxonomy.php (modified) (85 diffs)
-
tags/4.0.0/includes/class-kind-time.php (added)
-
tags/4.0.0/includes/class-kind-upgrade.php (added)
-
tags/4.0.0/includes/class-kind-view.php (modified) (36 diffs)
-
tags/4.0.0/includes/class-mf2-post.php (modified) (33 diffs)
-
tags/4.0.0/includes/class-post-kind.php (modified) (3 diffs)
-
tags/4.0.0/includes/compat.php (modified) (1 diff)
-
tags/4.0.0/includes/kind-functions.php (modified) (1 diff)
-
tags/4.0.0/includes/register-kinds.php (modified) (45 diffs)
-
tags/4.0.0/includes/time-functions.php (modified) (10 diffs)
-
tags/4.0.0/indieweb-post-kinds.php (modified) (16 diffs)
-
tags/4.0.0/js/clone-media-fragment.js (modified) (1 diff)
-
tags/4.0.0/js/kind.js (modified) (2 diffs)
-
tags/4.0.0/languages/indieweb-post-kinds.pot (modified) (14 diffs)
-
tags/4.0.0/lib/parse-this/LICENSE (added)
-
tags/4.0.0/lib/parse-this/includes/aliases.php (added)
-
tags/4.0.0/lib/parse-this/includes/autoload.php (modified) (1 diff)
-
tags/4.0.0/lib/parse-this/includes/class-base.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-discovery.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-html.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-json.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-jsonfeed.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-jsonld.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-mf2-utils.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-mf2.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-opml.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-base.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-discovery.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-html.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-instagram.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-json.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-jsonfeed.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-jsonld.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-mf2-utils.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-mf2.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-opml.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-restapi.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-rss.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-twitter.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this-youtube.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parse-this.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-parser.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-rest-endpoint.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-rest-parse-this.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/class-restapi.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-rss.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-twitter.php (added)
-
tags/4.0.0/lib/parse-this/includes/class-youtube.php (added)
-
tags/4.0.0/lib/parse-this/includes/compat-functions.php (deleted)
-
tags/4.0.0/lib/parse-this/includes/functions.php (modified) (36 diffs)
-
tags/4.0.0/lib/parse-this/js (added)
-
tags/4.0.0/lib/parse-this/js/debug.js (added)
-
tags/4.0.0/lib/parse-this/lib/html5/CREDITS (added)
-
tags/4.0.0/lib/parse-this/lib/html5/HTML5.php (modified) (1 diff)
-
tags/4.0.0/lib/parse-this/lib/html5/HTML5/Elements.php (modified) (4 diffs)
-
tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/CharacterReference.php (modified) (1 diff)
-
tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/DOMTreeBuilder.php (modified) (4 diffs)
-
tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/StringInputStream.php (modified) (5 diffs)
-
tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/Tokenizer.php (modified) (7 diffs)
-
tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/TreeBuildingRules.php (modified) (1 diff)
-
tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/UTF8Utils.php (modified) (2 diffs)
-
tags/4.0.0/lib/parse-this/lib/html5/HTML5/Serializer/HTML5Entities.php (deleted)
-
tags/4.0.0/lib/parse-this/lib/html5/HTML5/Serializer/OutputRules.php (modified) (9 diffs)
-
tags/4.0.0/lib/parse-this/lib/html5/LICENSE.txt (added)
-
tags/4.0.0/lib/parse-this/lib/mf2/Parser.php (modified) (13 diffs)
-
tags/4.0.0/lib/parse-this/parse-this.php (modified) (1 diff)
-
tags/4.0.0/lib/parse-this/readme.txt (modified) (2 diffs)
-
tags/4.0.0/package-lock.json (deleted)
-
tags/4.0.0/phpcs-templates.xml (added)
-
tags/4.0.0/readme.txt (modified) (7 diffs)
-
tags/4.0.0/templates/feed-atom-comments.php (deleted)
-
tags/4.0.0/templates/feed-atom.php (deleted)
-
tags/4.0.0/templates/feed-rss2-comments.php (deleted)
-
tags/4.0.0/templates/feed-rss2.php (deleted)
-
tags/4.0.0/templates/reply-author.php (modified) (1 diff)
-
tags/4.0.0/templates/reply-details.php (modified) (1 diff)
-
tags/4.0.0/templates/reply-metabox.php (modified) (5 diffs)
-
tags/4.0.0/templates/reply-time.php (modified) (1 diff)
-
tags/4.0.0/uninstall.php (added)
-
tags/4.0.0/views/kind-article.php (modified) (1 diff)
-
tags/4.0.0/views/kind-audio.php (modified) (5 diffs)
-
tags/4.0.0/views/kind-bookmark.php (modified) (3 diffs)
-
tags/4.0.0/views/kind-checkin.php (modified) (5 diffs)
-
tags/4.0.0/views/kind-drink.php (modified) (4 diffs)
-
tags/4.0.0/views/kind-eat.php (modified) (4 diffs)
-
tags/4.0.0/views/kind-event.php (modified) (2 diffs)
-
tags/4.0.0/views/kind-favorite.php (modified) (3 diffs)
-
tags/4.0.0/views/kind-issue.php (modified) (3 diffs)
-
tags/4.0.0/views/kind-itinerary.php (modified) (2 diffs)
-
tags/4.0.0/views/kind-jam.php (modified) (4 diffs)
-
tags/4.0.0/views/kind-like.php (modified) (3 diffs)
-
tags/4.0.0/views/kind-listen.php (modified) (4 diffs)
-
tags/4.0.0/views/kind-note.php (modified) (1 diff)
-
tags/4.0.0/views/kind-photo.php (modified) (4 diffs)
-
tags/4.0.0/views/kind-read.php (modified) (5 diffs)
-
tags/4.0.0/views/kind-rsvp.php (modified) (3 diffs)
-
tags/4.0.0/views/kind-video.php (modified) (4 diffs)
-
tags/4.0.0/views/kind-watch.php (modified) (4 diffs)
-
tags/4.0.0/views/kind.php (modified) (5 diffs)
-
trunk/.gitattributes (modified) (2 diffs)
-
trunk/css/kind.admin.min.css (modified) (1 diff)
-
trunk/css/kind.admin.min.css.map (modified) (1 diff)
-
trunk/css/kind.css (deleted)
-
trunk/css/kind.css.map (deleted)
-
trunk/css/kind.min.css (modified) (1 diff)
-
trunk/css/kind.min.css.map (modified) (1 diff)
-
trunk/develop.txt (modified) (1 diff)
-
trunk/gruntlint.js (deleted)
-
trunk/includes/class-kind-config.php (modified) (23 diffs)
-
trunk/includes/class-kind-fields.php (modified) (24 diffs)
-
trunk/includes/class-kind-media-metadata.php (modified) (13 diffs)
-
trunk/includes/class-kind-media-view.php (modified) (7 diffs)
-
trunk/includes/class-kind-menu-widget.php (modified) (11 diffs)
-
trunk/includes/class-kind-metabox.php (modified) (40 diffs)
-
trunk/includes/class-kind-plugins.php (modified) (7 diffs)
-
trunk/includes/class-kind-post-widget.php (modified) (6 diffs)
-
trunk/includes/class-kind-post.php (modified) (45 diffs)
-
trunk/includes/class-kind-taxonomy.php (modified) (85 diffs)
-
trunk/includes/class-kind-time.php (added)
-
trunk/includes/class-kind-upgrade.php (added)
-
trunk/includes/class-kind-view.php (modified) (36 diffs)
-
trunk/includes/class-mf2-post.php (modified) (33 diffs)
-
trunk/includes/class-post-kind.php (modified) (3 diffs)
-
trunk/includes/compat.php (modified) (1 diff)
-
trunk/includes/kind-functions.php (modified) (1 diff)
-
trunk/includes/register-kinds.php (modified) (45 diffs)
-
trunk/includes/time-functions.php (modified) (10 diffs)
-
trunk/indieweb-post-kinds.php (modified) (16 diffs)
-
trunk/js/clone-media-fragment.js (modified) (1 diff)
-
trunk/js/kind.js (modified) (2 diffs)
-
trunk/languages/indieweb-post-kinds.pot (modified) (14 diffs)
-
trunk/lib/parse-this/LICENSE (added)
-
trunk/lib/parse-this/includes/aliases.php (added)
-
trunk/lib/parse-this/includes/autoload.php (modified) (1 diff)
-
trunk/lib/parse-this/includes/class-base.php (added)
-
trunk/lib/parse-this/includes/class-discovery.php (added)
-
trunk/lib/parse-this/includes/class-html.php (added)
-
trunk/lib/parse-this/includes/class-json.php (added)
-
trunk/lib/parse-this/includes/class-jsonfeed.php (added)
-
trunk/lib/parse-this/includes/class-jsonld.php (added)
-
trunk/lib/parse-this/includes/class-mf2-utils.php (added)
-
trunk/lib/parse-this/includes/class-mf2.php (added)
-
trunk/lib/parse-this/includes/class-opml.php (added)
-
trunk/lib/parse-this/includes/class-parse-this-base.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-discovery.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-html.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-instagram.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-json.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-jsonfeed.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-jsonld.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-mf2-utils.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-mf2.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-opml.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-restapi.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-rss.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-twitter.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this-youtube.php (deleted)
-
trunk/lib/parse-this/includes/class-parse-this.php (deleted)
-
trunk/lib/parse-this/includes/class-parser.php (added)
-
trunk/lib/parse-this/includes/class-rest-endpoint.php (added)
-
trunk/lib/parse-this/includes/class-rest-parse-this.php (deleted)
-
trunk/lib/parse-this/includes/class-restapi.php (added)
-
trunk/lib/parse-this/includes/class-rss.php (added)
-
trunk/lib/parse-this/includes/class-twitter.php (added)
-
trunk/lib/parse-this/includes/class-youtube.php (added)
-
trunk/lib/parse-this/includes/compat-functions.php (deleted)
-
trunk/lib/parse-this/includes/functions.php (modified) (36 diffs)
-
trunk/lib/parse-this/js (added)
-
trunk/lib/parse-this/js/debug.js (added)
-
trunk/lib/parse-this/lib/html5/CREDITS (added)
-
trunk/lib/parse-this/lib/html5/HTML5.php (modified) (1 diff)
-
trunk/lib/parse-this/lib/html5/HTML5/Elements.php (modified) (4 diffs)
-
trunk/lib/parse-this/lib/html5/HTML5/Parser/CharacterReference.php (modified) (1 diff)
-
trunk/lib/parse-this/lib/html5/HTML5/Parser/DOMTreeBuilder.php (modified) (4 diffs)
-
trunk/lib/parse-this/lib/html5/HTML5/Parser/StringInputStream.php (modified) (5 diffs)
-
trunk/lib/parse-this/lib/html5/HTML5/Parser/Tokenizer.php (modified) (7 diffs)
-
trunk/lib/parse-this/lib/html5/HTML5/Parser/TreeBuildingRules.php (modified) (1 diff)
-
trunk/lib/parse-this/lib/html5/HTML5/Parser/UTF8Utils.php (modified) (2 diffs)
-
trunk/lib/parse-this/lib/html5/HTML5/Serializer/HTML5Entities.php (deleted)
-
trunk/lib/parse-this/lib/html5/HTML5/Serializer/OutputRules.php (modified) (9 diffs)
-
trunk/lib/parse-this/lib/html5/LICENSE.txt (added)
-
trunk/lib/parse-this/lib/mf2/Parser.php (modified) (13 diffs)
-
trunk/lib/parse-this/parse-this.php (modified) (1 diff)
-
trunk/lib/parse-this/readme.txt (modified) (2 diffs)
-
trunk/package-lock.json (deleted)
-
trunk/phpcs-templates.xml (added)
-
trunk/readme.txt (modified) (7 diffs)
-
trunk/templates/feed-atom-comments.php (deleted)
-
trunk/templates/feed-atom.php (deleted)
-
trunk/templates/feed-rss2-comments.php (deleted)
-
trunk/templates/feed-rss2.php (deleted)
-
trunk/templates/reply-author.php (modified) (1 diff)
-
trunk/templates/reply-details.php (modified) (1 diff)
-
trunk/templates/reply-metabox.php (modified) (5 diffs)
-
trunk/templates/reply-time.php (modified) (1 diff)
-
trunk/uninstall.php (added)
-
trunk/views/kind-article.php (modified) (1 diff)
-
trunk/views/kind-audio.php (modified) (5 diffs)
-
trunk/views/kind-bookmark.php (modified) (3 diffs)
-
trunk/views/kind-checkin.php (modified) (5 diffs)
-
trunk/views/kind-drink.php (modified) (4 diffs)
-
trunk/views/kind-eat.php (modified) (4 diffs)
-
trunk/views/kind-event.php (modified) (2 diffs)
-
trunk/views/kind-favorite.php (modified) (3 diffs)
-
trunk/views/kind-issue.php (modified) (3 diffs)
-
trunk/views/kind-itinerary.php (modified) (2 diffs)
-
trunk/views/kind-jam.php (modified) (4 diffs)
-
trunk/views/kind-like.php (modified) (3 diffs)
-
trunk/views/kind-listen.php (modified) (4 diffs)
-
trunk/views/kind-note.php (modified) (1 diff)
-
trunk/views/kind-photo.php (modified) (4 diffs)
-
trunk/views/kind-read.php (modified) (5 diffs)
-
trunk/views/kind-rsvp.php (modified) (3 diffs)
-
trunk/views/kind-video.php (modified) (4 diffs)
-
trunk/views/kind-watch.php (modified) (4 diffs)
-
trunk/views/kind.php (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
indieweb-post-kinds/tags/4.0.0/.gitattributes
r2491232 r3738687 5 5 /tests export-ignore 6 6 /bin export-ignore 7 /node -modules export-ignore7 /node_modules export-ignore 8 8 /sass export-ignore 9 9 … … 17 17 /readme.md export-ignore 18 18 /phpcs.xml export-ignore 19 /phpcs-templates.xml export-ignore 19 20 /phpunit.xml export-ignore 20 /Gruntfile.js export-ignore 21 /gruntlint.js export-ignore 21 /eslint.config.js export-ignore -
indieweb-post-kinds/tags/4.0.0/css/kind.admin.min.css
r2572705 r3738687 1 .svg-icon svg{fill:currentColor;padding:2px;vertical-align:middle;top:-0.0625em}#kind-all svg{width:1rem;height:1rem;color:gray}#kind-menu svg,#kind-posts svg,#tempus-onthisday svg,#tempus-thisweek svg,.kind-titles svg{width:1.5rem;height:1.5rem;color:#fff;mix-blend-mode:difference}#kind-menu ul,#kind-posts ul,#tempus-onthisday ul,#tempus-thisweek ul,.kind-titles ul{list-style-type:none}.response svg{width:2rem;height:2rem;color:#fff;mix-blend-mode:difference}.response figure{margin:auto}#replybox-meta.is-loading .loading{visibility:visible}#replybox-meta label{display:block}#replybox-meta .field-row{overflow:auto;height:100%}#replybox-meta .half{float:left;width:46%;margin:0 1%}#replybox-meta .half:first-of-type{margin-left:0;width:48%}#replybox-meta .half:last-of-type{margin-right:0;width:49%}#replybox-meta .quarter{float:left;margin-left:2%;width:12%}#replybox-meta .eighth{float:left;margin-left:2%;margin-right:1%;width:6%}#replybox-meta input[type=number]{width:100%}#replybox-meta .three-quarters{float:left;width:85%}#replybox-meta .is-loading .loading{visibility:visible}#replybox-meta .loading{visibility:hidden;position:absolute;top:0;bottom:0;left:0;right:0;z-index:10;background-color:#000;background-color:rgba(0,0,0,.4)}#replybox-meta .loading-spinner{margin:0 auto;display:block;position:relative;top:2em}/*# sourceMappingURL=kind.admin.min.css.map */1 :where(.svg-icon){display:inline-block;width:1em;height:1em;margin-right:.5rem;vertical-align:middle}:where(.svg-icon>span,.svg-icon svg){display:block;width:100%;height:100%}:where(.svg-icon svg){fill:currentColor}:where(#kind-all .svg-icon){width:1rem;height:1rem;color:gray}:where(#kind-menu,#kind-posts,#tempus-onthisday,#tempus-thisweek,.kind-titles) :where(.svg-icon){width:1.5rem;height:1.5rem;color:#fff;mix-blend-mode:difference}#kind-menu ul,#kind-posts ul,#tempus-onthisday ul,#tempus-thisweek ul,.kind-titles ul{list-style-type:none}:where(.response) :where(.svg-icon){width:2rem;height:2rem;color:#fff;mix-blend-mode:difference}.response figure{margin:auto}#replybox-meta.is-loading .loading{visibility:visible}#replybox-meta label{display:block}#replybox-meta .field-row{overflow:auto;height:100%}#replybox-meta .half{float:left;width:46%;margin:0 1%}#replybox-meta .half:first-of-type{margin-left:0;width:48%}#replybox-meta .half:last-of-type{margin-right:0;width:49%}#replybox-meta .quarter{float:left;margin-left:2%;width:12%}#replybox-meta .eighth{float:left;margin-left:2%;margin-right:1%;width:6%}#replybox-meta input[type=number]{width:100%}#replybox-meta .three-quarters{float:left;width:85%}#replybox-meta .is-loading .loading{visibility:visible}#replybox-meta .loading{visibility:hidden;position:absolute;top:0;bottom:0;left:0;right:0;z-index:10;background-color:#000;background-color:rgba(0,0,0,.4)}#replybox-meta .loading-spinner{margin:0 auto;display:block;position:relative;top:2em}/*# sourceMappingURL=kind.admin.min.css.map */ -
indieweb-post-kinds/tags/4.0.0/css/kind.admin.min.css.map
r2572705 r3738687 1 {"version":3,"sourceRoot":" ","sources":["../sass/_kinds.scss","../sass/_meta-box.scss"],"names":[],"mappings":"AAAA,cACC,kBACA,YACA,sBACA,cAIA,cACC,WACA,YACA,WAKD,2FACC,aACA,cACA,WACA,0BAED,sFACC,qBAKM,cACQ,WACA,YACd,WACc,0BAEf,iBACC,YCnCF,mCACC,mBAIA,qBACC,cAED,0BACC,cACA,YAED,qBACC,WACA,UACA,YAED,mCACC,cACA,UAED,kCACC,eACA,UAED,wBACC,WACA,eACA,UAED,uBACC,WACA,eACA,gBACA,SAED,kCACC,WAED,+BACC,WACA,UAED,oCACC,mBAED,wBACC,kBACA,kBACA,MACA,SACA,OACA,QACA,WACA,sBACA,gCAED,gCACC,cACA,cACA,kBACA","file":"kind.admin.min.css"}1 {"version":3,"sourceRoot":"..\/","sources":["sass\/_kinds.scss","sass\/_meta-box.scss"],"names":[],"mappings":"AAGA,kBACC,qBACA,UACA,WACA,mBACA,sBAGD,qCACC,cACA,WACA,YAGD,sBACC,kBAGD,4BACC,WACA,YACA,WAGD,iGACC,aACA,cACA,WACA,0BAIA,sFACC,qBAIF,oCACC,WACA,YACA,WACA,0BAIA,iBACC,YCjDF,mCACC,mBAIA,qBACC,cAED,0BACC,cACA,YAED,qBACC,WACA,UACA,YAED,mCACC,cACA,UAED,kCACC,eACA,UAED,wBACC,WACA,eACA,UAED,uBACC,WACA,eACA,gBACA,SAED,kCACC,WAED,+BACC,WACA,UAED,oCACC,mBAED,wBACC,kBACA,kBACA,MACA,SACA,OACA,QACA,WACA,sBACA,gCAED,gCACC,cACA,cACA,kBACA","file":"kind.admin.min.css"} -
indieweb-post-kinds/tags/4.0.0/css/kind.min.css
r2588189 r3738687 1 .svg-icon svg{fill:currentColor;padding:2px;vertical-align:middle;top:-0.0625em}#kind-all svg{width:1rem;height:1rem;color:gray}#kind-menu svg,#kind-posts svg,#tempus-onthisday svg,#tempus-thisweek svg,.kind-titles svg{width:1.5rem;height:1.5rem;color:#fff;mix-blend-mode:difference}#kind-menu ul,#kind-posts ul,#tempus-onthisday ul,#tempus-thisweek ul,.kind-titles ul{list-style-type:none}.response svg{width:2rem;height:2rem;color:#fff;mix-blend-mode:difference}.response figure{margin:auto}.response{margin-bottom:10px;margin-top:10px;margin-left:20px;padding:5px;font-size:16px;border-bottom:inset #d6d6d6}.response .kind-icon{margin-right:3px}.response blockquote{margin-left:25px}.response a:link{text-decoration:none}.response a:hover{text-decoration:none}.response img{padding:0;margin:2px}ul.cites{list-style:none;display:inline}.kind-embeds{text-align:center;position:relative;width:100%;margin-top:5px}.kind-photo-thumbnail{margin-bottom:5px}/*# sourceMappingURL=kind.min.css.map */1 :where(.svg-icon){display:inline-block;width:1em;height:1em;margin-right:.5rem;vertical-align:middle}:where(.svg-icon>span,.svg-icon svg){display:block;width:100%;height:100%}:where(.svg-icon svg){fill:currentColor}:where(#kind-all .svg-icon){width:1rem;height:1rem;color:gray}:where(#kind-menu,#kind-posts,#tempus-onthisday,#tempus-thisweek,.kind-titles) :where(.svg-icon){width:1.5rem;height:1.5rem;color:#fff;mix-blend-mode:difference}#kind-menu ul,#kind-posts ul,#tempus-onthisday ul,#tempus-thisweek ul,.kind-titles ul{list-style-type:none}:where(.response) :where(.svg-icon){width:2rem;height:2rem;color:#fff;mix-blend-mode:difference}.response figure{margin:auto}.response{margin-bottom:10px;margin-top:10px;margin-left:20px;padding:5px;font-size:16px;border-bottom:inset #d6d6d6}.response .kind-icon{margin-right:3px}.response blockquote{margin-left:25px}.response a:link{text-decoration:none}.response a:hover{text-decoration:none}.response img{padding:0;margin:2px}ul.cites{list-style:none;display:inline}.kind-embeds{text-align:center;position:relative;width:100%;margin-top:5px}.kind-photo-thumbnail{margin-bottom:5px}/*# sourceMappingURL=kind.min.css.map */ -
indieweb-post-kinds/tags/4.0.0/css/kind.min.css.map
r2588189 r3738687 1 {"version":3,"sourceRoot":" ","sources":["../sass/_kinds.scss","../sass/_frontend.scss"],"names":[],"mappings":"AAAA,cACC,kBACA,YACA,sBACA,cAIA,cACC,WACA,YACA,WAKD,2FACC,aACA,cACA,WACA,0BAED,sFACC,qBAKM,cACQ,WACA,YACd,WACc,0BAEf,iBACC,YCnCF,UACI,mBACA,gBACA,iBACA,YACA,eACA,4BACF,qBACC,iBAED,qBACC,iBAGF,iBACC,qBAGD,kBACC,qBAGD,cACE,UACA,WAIH,SACI,gBACA,eAGJ,aACI,kBACA,kBACA,WACA,eAGJ,sBACI","file":"kind.min.css"}1 {"version":3,"sourceRoot":"..\/","sources":["sass\/_kinds.scss","sass\/_frontend.scss"],"names":[],"mappings":"AAGA,kBACC,qBACA,UACA,WACA,mBACA,sBAGD,qCACC,cACA,WACA,YAGD,sBACC,kBAGD,4BACC,WACA,YACA,WAGD,iGACC,aACA,cACA,WACA,0BAIA,sFACC,qBAIF,oCACC,WACA,YACA,WACA,0BAIA,iBACC,YCjDF,UACI,mBACA,gBACA,iBACA,YACA,eACA,4BACF,qBACC,iBAED,qBACC,iBAGF,iBACC,qBAGD,kBACC,qBAGD,cACE,UACA,WAIH,SACI,gBACA,eAGJ,aACI,kBACA,kBACA,WACA,eAGJ,sBACI","file":"kind.min.css"} -
indieweb-post-kinds/tags/4.0.0/develop.txt
r1787314 r3738687 1 == Functions == 1 Developer notes for Post Kinds 4.0.0. Every function, method and hook listed here has a docblock in the code with its full parameters and return values. 2 2 3 `get_post_kind_slug($id)` - Return the kind slug for a given post. If `$id` is not specified, use current post. 4 `get_post_kind($id)` - Return the kind string for a given post. If `$id` is not specified, use current post. 3 == Template functions == 5 4 6 `has_post_kind($kind, $post)` - Returns true/false if kind is in post. If post is empty, then use current post 5 These global functions are loaded on `plugins_loaded`, from `includes/kind-functions.php`. 7 6 8 `get_kind_context_class( $class, $classtype )` - Returns the CSS class to be applied to the response/context if the kind is one for which there is context. Classtype defaults to u, other option is usually p. Sets the class to the kind slug and for specially specified slugs, sets appropriate mf2 classes as well. $class specifies any additional classes to be added. 7 `get_post_kind_slug( $post = null )` - Returns the kind slug of a post, or false if it has no kind. Defaults to the current post. 8 `get_post_kind( $post = null )` - Returns the translated kind name of a post, or false if it has no kind. Defaults to the current post. 9 `has_post_kind( $kinds = array(), $post = null )` - Returns whether a post has any of the given kinds, or any kind at all if `$kinds` is empty. 10 `set_post_kind( $post, $kind )` - Gives a post a kind. Returns a WP_Error if the post or the kind is not valid. 11 `get_post_kind_slugs()` - Returns the slugs of every registered kind. 12 `get_post_kind_string( $slug )` - Returns the translated name of a kind, or an empty string if it is not registered. 13 `get_post_kind_link( $kind )` - Returns the URL of a kind's archive. 14 `kind_display( $post_id = null )` - Prints the response markup for a post. Use it with the `kind_content_display` filter to place the markup in the theme. 15 `get_kind_view_part( $slug, $name = null, $args = null )` - Returns a rendered view. See Kind_View::get_view_part(). 16 `kind_get_the_title( $post = null, $args = array() )` - Returns a post's title, or a generated one (the kind's icon or name and a cited name or excerpt) for a post without one. 17 `kind_get_the_link( $post = null, $cls = null, $date_cls = null )` - Returns a link to a post, using kind_get_the_title(), followed by its date. 18 `kind_flatten_array( $a )` - Removes empty values from an array and unwraps single-item lists, recursively. 19 `kind_src_url_in_content( $content )` - Returns every src attribute value in some markup, or 0 if there are none. 9 20 21 == Registering kinds == 10 22 23 `register_post_kind( $slug, $args )` - Registers a kind. Call it on `init`. `includes/register-kinds.php` registers the built-in kinds the same way, and Kind_Taxonomy::register_post_kind() documents every argument. 24 `set_post_kind_visibility( $slug, $show = true )` - Shows or hides a kind in Settings, including the kinds reserved for future use. 11 25 12 == Filters ==26 == Time functions == 13 27 14 `get_the_kind` - Filter get_the_kinds 28 Also global, from `includes/time-functions.php`. 15 29 16 `the_kinds` - Filter get_the_kinds_list 30 `kind_safe_datetime( $value, $timezone = null, $immutable = true )` - Since 4.0.0. Creates a date from a value, returning false instead of throwing on invalid input. 31 `kind_safe_interval( $value )` - Since 4.0.0. Creates a DateInterval from an ISO 8601 duration such as `PT3M30S`, returning false instead of throwing on invalid input. 32 `calculate_duration( $start_string, $end_string )` - Returns the DateInterval between two dates, or false. 33 `date_interval_to_iso8601( $interval )` - Converts a DateInterval to an ISO 8601 duration string. 34 `build_iso8601_time( $date, $time, $offset )` - Joins a date, time and offset into an ISO 8601 string. 35 `build_iso8601_duration( $values )` - Builds an ISO 8601 duration from its pieces (Y, M, D, H, I, S). 36 `get_gmt_offsets()` - Returns the UTC offsets in use by any time zone, sorted. 37 `get_default_offset()` - Returns the site's UTC offset, such as `-05:00`. 38 `tz_seconds_to_offset( $seconds )`, `tz_offset_to_seconds( $offset )` - Convert between a UTC offset in seconds and an offset string. 39 `tz_seconds_to_timezone( $seconds )`, `tz_timezone_to_seconds( $timezone )` - Convert between a UTC offset in seconds and a DateTimeZone. 17 40 18 `kind_classes` - Filter get_kind_class 41 == Classes == 19 42 20 ` kind_verb` - Filter get_kind_verbs43 `Kind_Post` reads and writes a post's response data. Create one with a post ID or WP_Post. Its main methods: 21 44 22 `kind-response-display` - Filters the output being added to the_content or to custom location in theme 45 * `get_kind()`, `get_cite( $key = null )`, `get_author()`, `get_photo()`, `get_audio()`, `get_video()`, `get_duration()`, `get_datetime_property( $property )` and `get( $key )` read data. 46 * `set( $key, $value )`, `set_duration( $value )`, `set_datetime_property( $key, $value )` and `delete( $key )` write it. Response data is stored as post meta prefixed `mf2_`. 47 * `get_cite_type()` - Since 4.0.0. Returns the jf2 type of the post's citation: `card` for a checkin, `food` for eat and drink, and `cite` for every other kind. 48 * `Kind_Post::get_attachment_id( $url )` - Since 4.0.0. Static. A cached wrapper for attachment_url_to_postid(). 49 50 `Kind_Time` - Since 4.0.0. Static helpers for the editor's date, time and duration fields: `display_formatted_datetime()`, `divide_datetime()`, `build_datetime()`, `get_datetime_offset()`, `divide_interval()` and `build_interval()`. 51 52 `Kind_View` builds the response markup. `Kind_View::get_display( $post_id = null )` returns it for a post, `Kind_View::get_view_part( $slug, $name, $args = null )` renders one view, and `Kind_View::get_hcard( $author, $args = null )` returns an author's h-card. Since 4.0.0, its helpers return escaped markup; the 4.0.0 changelog lists the ones that changed. 53 54 `Kind_Media_Metadata::has_media( $post )` - Since 4.0.0. Returns whether a post may show audio or video. The media fragment script loads only for these posts. 55 56 `Kind_Upgrade` - Since 4.0.0. Upgrades stored post data in the background, in batches, after the plugin is updated. It gives published posts without a kind one, moves citations out of older storage locations, repairs citations that earlier versions damaged, and records the media in each post. 57 58 == WP-CLI == 59 60 `wp post-kinds upgrade` - Since 4.0.0. Runs the upgrade now instead of in the background. It is safe to run more than once. 61 62 * `--dry-run` - Report what would change without saving. 63 * `--post=<id>` - Upgrade only this post. 64 65 == REST API == 66 67 * The `kind` field on posts reads and sets a post's kind slug. 68 * `GET /wp-json/post-kinds/1.0/fields?kind={slug}` returns a kind's registration, including its field schema. An unknown kind returns a 404. It requires the `edit_posts` capability. 69 * The editor fetches details for a cited URL from the bundled Parse This library's `parse-this/1.0/parse` route. 70 71 == Filters == 72 73 Display: 74 75 `kind_content_display` - Whether to add response markup to post content, excerpts and feeds automatically. Return false to place it with kind_display() instead. Default true. 76 `kind_response_display` - The response markup for a post. Receives the markup and the post ID. 77 `kind_view_paths` - The folders searched for views before the plugin's `views` folder. Default the theme's `kind_views` folder. 78 `kind_icon_display` - Whether to show the kind's icon or name before the response. Return false to hide it and remove the option from Settings. 79 `post_kinds_icon_attributes` - The HTML attributes on the span that wraps a kind icon. Output as-is, so it must be escaped. Default empty since 4.0.0; before that, an inline style. 80 `kind_get_the_title_content` - The text of a generated title. Receives escaped text and the post. 81 `kind_get_the_title_before` - The markup before a generated title, the kind's icon or name by default. Output as-is, so it must be escaped. 82 `get_hcard_data` - The author data before it becomes an h-card. 83 `get_hcard` - The h-card markup for an author. Return non-empty, escaped markup to replace the plugin's own. 84 `post_kind_embed_approvelist` - The domains whose cited URLs are embedded with oEmbed. 85 `kind_post_type_string` - The phrase for what a known site's URL points to, such as "a video" for YouTube. 86 87 Kinds and archives: 88 89 `kind_include` - The kinds offered in the editor and listed by the Kind Menu widget. Default the kinds selected in Settings, plus note, reply and article. 90 `kind_base_slug` - Since 4.0.0. The start of kind archive URLs, as in `/kind/like/`. Default the Kind base on Settings > Permalinks, or `kind`. 91 `kind_rewrite_taxonomies` - The taxonomies that get `/kind/{kind}/{taxonomy}/{term}/` archives. Default post_tag, category and series. 92 `kind_exclude_slug` - The slug for archives that exclude kinds, as in `/exclude/kind/note,like/`. Default `exclude`. 93 `kind_photos_slug` - The slug for the archive of posts with photos. Default `photos`. 94 `kind_firehose_slug` - The slug for the feed that includes every kind. Default `firehose`. 95 96 After changing a slug or the rewrite taxonomies, flush rewrite rules: visit Settings > Permalinks and save. 97 98 Upgrade: 99 100 `kind_upgrade_batch_size` - Since 4.0.0. The number of posts the background upgrade processes per batch. Default 50. 101 102 Micropub (these run only when the Micropub plugin is active): 103 104 `post_kinds_micropub_enrich` - Since 4.0.0. Whether to fill in citation URLs from Micropub requests with data parsed from the cited page. Return false to store citations exactly as the client sent them. Default true. 105 `post_kinds_micropub_parse_timeout` - Since 4.0.0. The timeout, in seconds, for parsing a citation while the client waits. Citations that time out are retried in the background. Default 5. 106 `post_kinds_micropub_unavailable_retry_delay` - Since 4.0.0. How long to wait before retrying, once, a citation whose page returned 404, 401 or 403. Default 6 hours. 107 `post_kinds_micropub_post_types` - Since 4.0.0. The post types advertised to Micropub clients in `q=config`. 108 109 == Actions == 110 111 `change_kind` - Fires when a post's kind changes. Receives the post ID and the old and new kind slugs; either slug can be an empty string. 112 `post_kinds_citation_enrichment` - Since 4.0.0. Fires after each attempt to parse a Micropub citation. Receives the log entry and the post ID. 113 114 == Deprecated == 115 116 These still work, but report a deprecation notice when WP_DEBUG is on. 117 118 * Since 4.0.0, the global functions `display_formatted_datetime()`, `divide_datetime()`, `build_datetime()`, `get_datetime_offset()`, `divide_interval()` and `build_interval()`. Use the Kind_Time methods of the same names. They will be removed in 5.0.0. 119 * Since 4.0.0, the `MF2_Post` class. Use Kind_Post. It will be removed in a future major version. 120 121 == Styling icons == 122 123 Kind icons are `<span class="svg-icon svg-{kind}">` elements wrapping an SVG. The plugin's stylesheet sizes the span, and the SVG fills it. Its rules use `:where()`, so they have no specificity and any theme rule wins. For example, `.svg-icon { width: 1rem; height: 1rem; }` resizes every icon, and `#kind-menu .svg-icon` only those in the Kind Menu widget. Icons take their colour from the `color` property. 124 125 == Overriding views == 126 127 The markup added to each post comes from a view file in the plugin's `views` folder: `kind-{slug}.php` for a kind (for example `kind-like.php`), or `kind.php` for a kind without its own view. 128 129 To change a view, copy it into a `kind_views` folder in your theme and edit the copy. If a child theme has a `kind_views` folder, it is used instead of the parent theme's; the two aren't combined. For each post, the plugin looks for `kind-{slug}.php` in your theme and then in the plugin, and only then for `kind.php` in the same order. So a theme `kind.php` replaces only the kinds that have no view of their own. The `kind_view_paths` filter changes which folders are searched before the plugin's. 130 131 Copies don't receive updates. After upgrading, compare them with the plugin's views. Since 4.0.0, views escape everything they print, and the `Kind_View` helpers return escaped markup. 132 133 Each view is rendered by `Kind_View::get_view_part()` with these variables in scope: 134 135 * `$kind_post` (Kind_Post) - the post being displayed. 136 * `$kind` (string) - the kind's slug, such as `like`. 137 * `$type` (string) - the kind's microformats property, such as `like-of`. 138 * `$cite` (array) - the citation, normalized by `Kind_Post::normalize_cite()`, so every key is present. `name` falls back to the URL. 139 * `$url` (string) - the cited URL. 140 * `$author` (string|false) - the cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. `$cite['authors']` lists each author as a card with `name`, `url` and `photo`. 141 * `$embed` (string) - embed markup for the cited URL, or an empty string. 142 * `$photos` (int[]|false) - attachment IDs of the post's photos, from `Kind_Post::get_photo()`. 143 * `$args` (array) - the arguments passed to `Kind_View::get_view_part()`; `post_id` is the post's ID. -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-config.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Post Kind Configuration Class3 * Kind_Config class. 4 4 * 5 * @package Post Kinds6 * Sets Up Configuration Options for the Plugin.5 * @package Post_Kinds 6 * @since 2.0.2 7 7 */ 8 8 9 defined( 'ABSPATH' ) || exit; 10 9 11 /** 10 * Static Class to Configure Admin Options.12 * Registers the plugin settings and builds the settings page. 11 13 * 12 * @ package Post Kinds14 * @since 2.0.2 13 15 */ 14 16 class Kind_Config { … … 16 18 /** 17 19 * Function to Initialize the Configuration. 20 * 21 * @since 2.0.2 18 22 * 19 23 * @access public … … 22 26 add_action( 'admin_init', array( static::class, 'admin_init' ) ); 23 27 add_action( 'admin_menu', array( static::class, 'admin_menu' ), 11 ); 24 // Add post help tab 28 // Add post help tab. 25 29 add_action( 'load-post.php', array( static::class, 'add_post_help_tab' ), 20 ); 26 27 $args = array( 28 'type' => 'array', 29 'description' => 'Kinds Enabled on This Site', 30 'show_in_rest' => false, 31 'default' => array( 'article', 'reply', 'bookmark' ), 30 add_action( 'load-options-permalink.php', array( static::class, 'save_kind_base' ) ); 31 32 $args = array( 33 'type' => 'array', 34 'description' => 'Kinds Enabled on This Site', 35 'show_in_rest' => false, 36 'default' => array( 'article', 'reply', 'bookmark' ), 37 'sanitize_callback' => array( static::class, 'sanitize_kind_list' ), 32 38 ); 33 39 register_setting( 'iwt_options', 'kind_termslist', $args ); 34 40 $args = array( 35 'type' => 'string', 36 'description' => 'Default Kind', 37 'show_in_rest' => false, 38 'default' => 'note', 41 'type' => 'string', 42 'description' => 'Default Kind', 43 'show_in_rest' => false, 44 'default' => 'note', 45 'sanitize_callback' => array( static::class, 'sanitize_default_kind' ), 39 46 ); 40 47 register_setting( 'iwt_options', 'kind_default', $args ); 41 48 $args = array( 42 'type' => 'boolean', 43 'description' => 'Rich Embed Support for Whitelisted Sites', 44 'show_in_rest' => false, 45 'default' => 1, 49 'type' => 'boolean', 50 'description' => 'Rich Embed Support for Whitelisted Sites', 51 'show_in_rest' => false, 52 'default' => 1, 53 'sanitize_callback' => array( static::class, 'sanitize_checkbox' ), 46 54 ); 47 55 register_setting( 'iwt_options', 'kind_embeds', $args ); 48 56 $args = array( 49 'type' => 'array', 50 'description' => 'Kinds Showing in Main Archive', 51 'show_in_rest' => false, 52 'default' => array(), 57 'type' => 'array', 58 'description' => 'Kinds Showing in Main Archive', 59 'show_in_rest' => false, 60 'default' => array(), 61 'sanitize_callback' => array( static::class, 'sanitize_kind_list' ), 53 62 ); 54 63 register_setting( 'iwt_options', 'kind_firehose', $args ); 55 64 56 65 $args = array( 57 'type' => 'boolean', 58 'description' => 'Response Information Should Be After Content', 59 'show_in_rest' => false, 60 'default' => 0, 66 'type' => 'boolean', 67 'description' => 'Response Information Should Be After Content', 68 'show_in_rest' => false, 69 'default' => 0, 70 'sanitize_callback' => array( static::class, 'sanitize_checkbox' ), 61 71 ); 62 72 register_setting( 'iwt_options', 'kind_bottom', $args ); 63 73 $args = array( 64 'type' => 'string', 65 'description' => 'Display Preferences for Before Kind', 66 'show_in_rest' => false, 67 'default' => 'icon', 74 'type' => 'string', 75 'description' => 'Display Preferences for Before Kind', 76 'show_in_rest' => false, 77 'default' => 'icon', 78 'sanitize_callback' => array( static::class, 'sanitize_display' ), 68 79 ); 69 80 register_setting( 'iwt_options', 'kind_display', $args ); 70 81 $args = array( 71 'type' => 'string', 72 'description' => 'KSES Content Protection on Responses', 73 'show_in_rest' => false, 74 'default' => str_replace( '},"', "},\r\n\"", wp_json_encode( wp_kses_allowed_html( 'post' ), 128 ) ), 82 'type' => 'string', 83 'description' => 'KSES Content Protection on Responses', 84 'show_in_rest' => false, 85 'default' => str_replace( '},"', "},\r\n\"", wp_json_encode( wp_kses_allowed_html( 'post' ), 128 ) ), 86 'sanitize_callback' => array( static::class, 'sanitize_kses' ), 75 87 ); 76 88 register_setting( 'iwt_options', 'kind_kses', $args ); 77 89 $args = array( 78 'type' => 'boolean', 79 'description' => 'Automatically add the Kind to the Title', 80 'show_in_rest' => false, 81 'default' => 0, 90 'type' => 'boolean', 91 'description' => 'Automatically add the Kind to the Title', 92 'show_in_rest' => false, 93 'default' => 0, 94 'sanitize_callback' => array( static::class, 'sanitize_checkbox' ), 82 95 ); 83 96 register_setting( 'iwt_options', 'kind_title', $args ); 97 $args = array( 98 'type' => 'string', 99 'description' => 'Kind Base', 100 'show_in_rest' => false, 101 'default' => '', 102 'sanitize_callback' => array( static::class, 'sanitize_kind_base' ), 103 ); 104 register_setting( 'permalink', 'kind_base', $args ); 105 } 106 107 /** 108 * Sanitizes the kind_base setting. 109 * 110 * @since 4.0.0 111 * 112 * @param mixed $value Submitted value. 113 * @return string The base, such as 'type' or 'blog/type', or an empty string for the default. 114 */ 115 public static function sanitize_kind_base( $value ) { 116 if ( ! is_string( $value ) ) { 117 return ''; 118 } 119 $segments = array_filter( array_map( 'sanitize_title', explode( '/', $value ) ) ); 120 $base = implode( '/', $segments ); 121 return 'kind' === $base ? '' : $base; 122 } 123 124 /** 125 * Saves the Kind base field on Settings > Permalinks. 126 * 127 * Core's permalink page doesn't save fields that plugins add, so this runs 128 * before it, with its nonce. Core flushes the rewrite rules when the page 129 * reloads. 130 * 131 * @since 4.0.0 132 */ 133 public static function save_kind_base() { 134 if ( ! isset( $_POST['kind_base'] ) || ! is_string( $_POST['kind_base'] ) || ! current_user_can( 'manage_options' ) ) { 135 return; 136 } 137 check_admin_referer( 'update-permalink' ); 138 // Sanitized by sanitize_kind_base(), the setting's sanitize callback. 139 update_option( 'kind_base', wp_unslash( $_POST['kind_base'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 140 } 141 142 /** 143 * Outputs the Kind base field on Settings > Permalinks. 144 * 145 * @since 4.0.0 146 */ 147 public static function kind_base_callback() { 148 printf( 149 '<input name="kind_base" id="kind_base" type="text" value="%1$s" class="regular-text code" placeholder="kind" />', 150 esc_attr( get_option( 'kind_base', '' ) ) 151 ); 152 } 153 154 /** 155 * Sanitizes a list of kinds: the kind_termslist and kind_firehose settings. 156 * 157 * @since 4.0.0 158 * 159 * @param mixed $value Submitted value. 160 * @return string[] Registered kind slugs. 161 */ 162 public static function sanitize_kind_list( $value ) { 163 if ( ! is_array( $value ) ) { 164 return array(); 165 } 166 $value = array_map( 'sanitize_key', array_filter( $value, 'is_string' ) ); 167 return array_values( array_unique( array_intersect( $value, Kind_Taxonomy::get_kind_list() ) ) ); 168 } 169 170 /** 171 * Sanitizes the default kind setting. 172 * 173 * @since 4.0.0 174 * 175 * @param mixed $value Submitted value. 176 * @return string A registered kind slug, or note. 177 */ 178 public static function sanitize_default_kind( $value ) { 179 $value = is_string( $value ) ? sanitize_key( $value ) : ''; 180 return in_array( $value, Kind_Taxonomy::get_kind_list(), true ) ? $value : 'note'; 181 } 182 183 /** 184 * Sanitizes the display setting for the text before a kind. 185 * 186 * @since 4.0.0 187 * 188 * @param mixed $value Submitted value. 189 * @return string icon, text, both or hide. Defaults to icon. 190 */ 191 public static function sanitize_display( $value ) { 192 return in_array( $value, array( 'icon', 'text', 'both', 'hide' ), true ) ? $value : 'icon'; 193 } 194 195 /** 196 * Sanitizes a checkbox setting. 197 * 198 * @since 4.0.0 199 * 200 * @param mixed $value Submitted value. 201 * @return int 1 if checked, otherwise 0. 202 */ 203 public static function sanitize_checkbox( $value ) { 204 return empty( $value ) ? 0 : 1; 205 } 206 207 /** 208 * Sanitizes the allowed HTML setting, a JSON object as wp_kses() expects. 209 * 210 * Invalid JSON is rejected and the previous value kept. 211 * 212 * @since 4.0.0 213 * 214 * @param mixed $value Submitted value. 215 * @return string JSON-encoded allowed HTML. 216 */ 217 public static function sanitize_kses( $value ) { 218 $decoded = is_string( $value ) ? json_decode( $value, true ) : null; 219 if ( is_array( $decoded ) ) { 220 return $value; 221 } 222 // The settings error API is only loaded in the admin. 223 if ( function_exists( 'add_settings_error' ) ) { 224 add_settings_error( 'kind_kses', 'kind_kses_invalid', __( 'The allowed HTML must be a valid JSON object. The previous value has been kept.', 'indieweb-post-kinds' ) ); 225 } 226 $previous = get_option( 'kind_kses' ); 227 return is_string( $previous ) ? $previous : ''; 228 } 229 230 /** 231 * Returns the kinds enabled in settings (the kind_termslist option). 232 * 233 * Always returns an array of registered kind slugs, whatever is stored: the 234 * option can be missing, false or hold kinds that are no longer registered. 235 * Note, which is always available, is not included unless it was saved. 236 * 237 * @since 4.0.0 238 * 239 * @return string[] Kind slugs. 240 */ 241 public static function get_termslist() { 242 $termslist = get_option( 'kind_termslist' ); 243 if ( ! is_array( $termslist ) ) { 244 return array(); 245 } 246 return array_values( array_intersect( $termslist, Kind_Taxonomy::get_kind_list() ) ); 84 247 } 85 248 86 249 /** 87 250 * Function to Set up Settings. 251 * 252 * @since 2.0.2 88 253 * 89 254 * @access public … … 92 257 add_action( 'admin_bar_menu', array( static::class, 'dashbar_links' ), 20 ); 93 258 add_action( 'admin_bar_menu', array( static::class, 'remove_dashbar_post' ), 200 ); 259 add_settings_field( 260 'kind_base', 261 __( 'Kind base', 'indieweb-post-kinds' ), 262 array( static::class, 'kind_base_callback' ), 263 'permalink', 264 'optional', 265 array( 'label_for' => 'kind_base' ) 266 ); 94 267 add_settings_section( 95 268 'iwt-content', … … 189 362 ) 190 363 ); 191 // Add Query Var to Admin 364 // Add Query Var to Admin. 192 365 add_filter( 'query_vars', array( static::class, 'query_var' ) ); 193 366 } … … 195 368 /** 196 369 * Function to add our kindurl query var. 370 * 371 * @since 2.3.3 197 372 * 198 373 * @access public … … 209 384 * Function to remove "post" from the "New" admin bar section. 210 385 * 211 * @access public 212 * 213 * @param WP_Admin_Bar $wp_admin_bar 386 * @since 3.0.7 387 * 388 * @access public 389 * 390 * @param WP_Admin_Bar $wp_admin_bar The admin bar. 214 391 */ 215 392 public static function remove_dashbar_post( $wp_admin_bar ) { … … 220 397 * Function to add our Post Kind post links to the "New" admin bar section. 221 398 * 222 * @access public 223 * @param WP_Admin_Bar $wp_admin_bar 399 * @since 3.0.7 400 * 401 * @access public 402 * 403 * @param WP_Admin_Bar $wp_admin_bar The admin bar. 224 404 */ 225 405 public static function dashbar_links( $wp_admin_bar ) { 226 $termslist = get_option( 'kind_termslist');227 // Note can never be removed 406 $termslist = self::get_termslist(); 407 // Note can never be removed. 228 408 array_unshift( $termslist, 'note' ); 229 foreach ( $termslistas $term ) {409 foreach ( array_unique( $termslist ) as $term ) { 230 410 $wp_admin_bar->add_menu( 231 411 array( … … 242 422 * Adds Options Page for Plugin Options. 243 423 * 424 * @since 2.0.2 425 * 244 426 * @access public 245 427 */ … … 249 431 add_submenu_page( 250 432 'indieweb', 251 __( 'Post Kinds', 'indieweb-post-kinds' ), // page title 252 __( 'Post Kinds', 'indieweb-post-kinds' ), // menu title 253 'manage_options', // access capability 433 __( 'Post Kinds', 'indieweb-post-kinds' ), // page title. 434 __( 'Post Kinds', 'indieweb-post-kinds' ), // menu title. 435 'manage_options', // access capability. 254 436 'kind_options', 255 437 array( static::class, 'options_form' ) … … 263 445 * Callback for Options on Options Page. 264 446 * 447 * @since 2.0.2 448 * 265 449 * @access public 266 450 */ … … 270 454 /** 271 455 * Generate a Checkbox. 456 * 457 * @since 2.0.2 272 458 * 273 459 * @access public … … 280 466 $option = get_option( $args['name'] ); 281 467 $checked = intval( $option ?? 0 ); 282 printf( '<input name="%1$s" type="hidden" value="0" />', esc_attr( $args['name'] ) ); // phpcs:ignore283 printf( '<input name="%1$s" type="checkbox" value="1" %2$s />', esc_attr( $args['name'] ), checked( 1, $checked, false ) ); // phpcs:ignore468 printf( '<input name="%1$s" type="hidden" value="0" />', esc_attr( $args['name'] ) ); 469 printf( '<input name="%1$s" type="checkbox" value="1" %2$s />', esc_attr( $args['name'] ), checked( 1, $checked, false ) ); 284 470 } 285 471 286 472 /** 287 473 * Generate a Textbox. 474 * 475 * @since 2.0.2 288 476 * 289 477 * @access public … … 296 484 $option = get_option( $args['name'] ); 297 485 if ( is_array( $option ) ) { 298 $option = print_r( $option, true);299 } 300 echo "<textarea rows='10' cols='50' class='large-text code' name='" . esc_attr( $args['name'] ) . "'>" . $option . '</textarea> '; // phpcs:ignore486 $option = wp_json_encode( $option, JSON_PRETTY_PRINT ); 487 } 488 echo "<textarea rows='10' cols='50' class='large-text code' name='" . esc_attr( $args['name'] ) . "'>" . esc_textarea( (string) $option ) . '</textarea> '; 301 489 } 302 490 303 491 /** 304 492 * Generate a Term List. 493 * 494 * @since 2.5.0 305 495 * 306 496 * @access public … … 309 499 $terms = Kind_Taxonomy::get_kind_list(); 310 500 // Hide these terms until ready for use for now. 311 $termslist = get_option( 'kind_termslist');501 $termslist = self::get_termslist(); 312 502 echo '<div id="kind-all">'; 313 503 foreach ( $terms as $term ) { 314 504 $value = Kind_Taxonomy::get_post_kind_info( $term ); 315 505 if ( $value->show ) { 316 printf( '<input name="kind_termslist[]" type="checkbox" value="%1$s" %2$s />', esc_attr( $term ), checked( in_array( $term, $termslist, true ), true, false ) ); // phpcs:ignore317 printf( '%1$s<strong>%2$s</strong> - %3$s<br />', Kind_Taxonomy::get_icon( $term ), sanitize_text_field( $value->singular_name ), sanitize_text_field( $value->description ) ); // phpcs:ignore506 printf( '<input name="kind_termslist[]" type="checkbox" value="%1$s" %2$s />', esc_attr( $term ), checked( in_array( $term, $termslist, true ), true, false ) ); 507 printf( '%1$s<strong>%2$s</strong> - %3$s<br />', Kind_Taxonomy::get_icon( $term ), esc_html( $value->singular_name ), esc_html( $value->description ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The plugin's own SVG icon markup. 318 508 } 319 509 } … … 322 512 323 513 /** 324 * Generate a Term List. 325 * 326 * @access public 514 * Outputs a multiple select of kinds for a setting. 515 * 516 * @since 3.6.0 517 * 518 * @access public 519 * 520 * @param array $args Field arguments. 'name' is the option name. 327 521 */ 328 522 public static function kindmultiselect_callback( array $args ) { 329 $terms = get_option( 'kind_termslist');523 $terms = self::get_termslist(); 330 524 $terms[] = 'note'; 525 $terms = array_unique( $terms ); 331 526 332 527 $kindlist = get_option( $args['name'] ); … … 339 534 foreach ( $terms as $term ) { 340 535 $value = Kind_Taxonomy::get_post_kind_info( $term ); 341 printf( '<option value="%1$s" %2$s />%3$s</option>', esc_attr($term), selected( in_array( $term, $kindlist ), true, false ), sanitize_text_field( $value->singular_name ) ); // phpcs:ignore536 printf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $term ), selected( in_array( $term, $kindlist, true ), true, false ), esc_html( $value->singular_name ) ); 342 537 } 343 538 echo '</select>'; … … 349 544 * Generate a Kind Select List. 350 545 * 546 * @since 2.3.3 547 * 351 548 * @access public 352 549 */ 353 550 public static function defaultkind_callback() { 354 $terms = get_option( 'kind_termslist');551 $terms = self::get_termslist(); 355 552 $terms[] = 'note'; 553 $terms = array_unique( $terms ); 356 554 sort( $terms, SORT_STRING ); 357 555 … … 361 559 foreach ( $terms as $term ) { 362 560 $value = Kind_Taxonomy::get_post_kind_info( $term ); 363 printf( '<option value="%1$s" %2$s />%3$s</option>', esc_attr($term), selected( $term, $defaultkind, false ), sanitize_text_field( $value->singular_name ) ); // phpcs:ignore561 printf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $term ), selected( $term, $defaultkind, false ), esc_html( $value->singular_name ) ); 364 562 } 365 563 echo '</select>'; … … 368 566 /** 369 567 * Generate Radio Options. 568 * 569 * @since 2.6.6 370 570 * 371 571 * @access public … … 376 576 $display = get_option( 'kind_display' ); 377 577 foreach ( $args['options'] as $key => $value ) { 378 printf( '<input id="%1$s" name="%1$s" type="radio" value="%2$s" class="%3$s" %4$s />%5$s<br />', esc_attr( $args['name'] ), esc_attr( $key ), esc_attr( $args['class'] ), checked( $key, $display, false ), sanitize_text_field( $value ) ); // phpcs:ignore578 printf( '<input id="%1$s" name="%1$s" type="radio" value="%2$s" class="%3$s" %4$s />%5$s<br />', esc_attr( $args['name'] ), esc_attr( $key ), esc_attr( $args['class'] ), checked( $key, $display, false ), esc_html( $value ) ); 379 579 } 380 580 } … … 382 582 /** 383 583 * Generate Options Form. 584 * 585 * @since 2.0.2 384 586 * 385 587 * @access public … … 406 608 /** 407 609 * Function to generate a help tab in the WordPress admin help tab. 610 * 611 * @since 2.3.0 408 612 * 409 613 * @access public -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-fields.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Kind Fields Class3 * Kind_Fields class. 4 4 * 5 * @package Post Kind 6 * Used to Generate Form Fields for a Post UI 5 * @package Post_Kinds 6 * @since 3.3.4 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Generates form fields for the posting UI from a kind's property schema. 7 13 * 8 * Fields currently supported and their options{9 *datetime10 *duration11 * author - is a representation of an author and consists of name, url, and photo by default 12 * select 13 * options - associative array of values for the select. Key being the value and the value being the description 14 * url 15 * text16 * list 17 * textarea 18 * number 19 * cite - is a representation of an h-cite object 20 * } 14 * Supported field types and their options: 15 * - datetime 16 * - duration 17 * - author: a name, URL and photo by default. 18 * - select: `options` is an associative array of value => label. 19 * - url 20 * - text 21 * - list 22 * - textarea 23 * - number 24 * - cite: an h-cite object. 25 * 26 * @since 3.3.4 21 27 */ 22 23 28 class Kind_Fields { 24 29 30 /** 31 * Returns a list of time zones with translated labels. 32 * 33 * @since 3.3.4 34 * 35 * @return string[] Labels, keyed by time zone identifier. 36 */ 25 37 public static function timezone_list() { 26 38 return array( … … 279 291 } 280 292 293 /** 294 * Returns the UTC offsets in use by any time zone, sorted. 295 * 296 * @since 3.3.4 297 * 298 * @return string[] Offsets such as '-05:00'. 299 */ 281 300 public static function get_offset_list() { 282 301 $o = wp_cache_get( 'kind_offset_list' ); … … 288 307 foreach ( $t_zones as $a ) { 289 308 $datetime = new DateTime( 'now', new DateTimeZone( $a ) ); 290 $o[] = get_datetime_offset( $datetime );309 $o[] = Kind_Time::get_datetime_offset( $datetime ); 291 310 } 292 311 $o = array_unique( $o ); … … 297 316 298 317 /** 299 * Function to render date/time field inputs. 300 * 301 * @access public 302 * 303 * @param string|DateTime $time Date/time value. 304 * @param string $name Property name 305 * @param array $args 306 * @param string|array $class Class to use for fields. 307 * @return string 318 * Renders date, time and offset inputs. 319 * 320 * @since 3.3.4 321 * 322 * @param array $args Field arguments, as returned by validate(): 'name', 'label' and 'class'. 323 * @param DateTime|string|false $datetime Date, or a date string. Default now. 324 * @return string The inputs. 308 325 */ 309 326 public static function field_datetime( $args, $datetime ) { … … 315 332 } 316 333 $return = array(); 317 $time = divide_datetime( $datetime );334 $time = Kind_Time::divide_datetime( $datetime ); 318 335 $return[] = sprintf( '<label for="mf2_%1$s" class="%2$s">%3$s', $args['name'], $args['class'], $args['label'] ); 319 336 $return[] = sprintf( '<input type="date" name="mf2_%1$s_date" id="mf2_%1$s_date" value="%2$s"/>', $args['name'], ( $time['date'] ?? '' ) ); … … 328 345 329 346 /** 330 * Function to render dateinterval field inputs. 331 * 332 * @access public 333 * 334 * @param string|Kind_DateTime $time Date/time value. 335 * @param string $name Property name 336 * @param array $args 337 * @param string|array $class Class to use for fields. 338 * @return string 347 * Renders duration inputs. 348 * 349 * @since 3.3.4 350 * 351 * @param array $args Field arguments, as returned by validate(), including 'pieces'. 352 * @param DateInterval|string $interval Duration, or an ISO 8601 duration. Default 0. 353 * @return string The inputs. 339 354 */ 340 355 public static function field_duration( $args, $interval ) { … … 346 361 } 347 362 $return = array(); 348 $duration = divide_interval( $interval );363 $duration = Kind_Time::divide_interval( $interval ); 349 364 $max = array( 350 365 'Y' => 1000, … … 364 379 365 380 /** 366 * Function to render author inputs. 367 * 368 * @access public 369 * 370 * @param string $prefix Field prefix. 371 * @param string|array $author Defautl values. If string, considered to be the name property below. { 372 * @param string $name Author Name 373 * @param string $url Author URL 374 * @param string $photo Author Photo 375 * } 376 * @return string 381 * Renders author name, URL and photo inputs. 382 * 383 * @since 3.3.4 384 * 385 * @param array $args Field arguments, as returned by validate(). 386 * @param array|false $author Values, with 'name', 'url' and 'photo' keys. 387 * @return string The inputs. 377 388 */ 378 389 public static function field_author( $args, $author ) { … … 394 405 ), 395 406 ); 396 // Ensure all props are set for values 407 // Ensure all props are set for values. 397 408 foreach ( array_keys( $props ) as $prop ) { 398 409 if ( ! array_key_exists( $prop, $author ) ) { … … 405 416 406 417 /** 407 * Function to render a select.408 * 409 * @ access public410 * 411 * @param string $name412 * @param string $selected Selected field type.413 * @return string 418 * Renders a select. The options are in $args['options']. 419 * 420 * @since 3.3.4 421 * 422 * @param array $args Field arguments, as returned by validate(). 423 * @param string $selected Value of the selected option. 424 * @return string The input. 414 425 */ 415 426 public static function field_select( $args, $selected ) { … … 426 437 427 438 /** 428 * Function to render a url.429 * 430 * @ access public431 * 432 * @param string $name433 * @param string $url 434 * @return string 439 * Renders a URL input. 440 * 441 * @since 3.3.4 442 * 443 * @param array $args Field arguments, as returned by validate(). 444 * @param string $url Value. 445 * @return string The input. 435 446 */ 436 447 public static function field_url( $args, $url ) { … … 443 454 444 455 /** 445 * Function to render a number.446 * 447 * @ access public448 * 449 * @param string $name450 * @param string $value 451 * @return string 456 * Renders a number input. The step is in $args['step']. 457 * 458 * @since 3.3.4 459 * 460 * @param array $args Field arguments, as returned by validate(). 461 * @param string $value Value. 462 * @return string The input. 452 463 */ 453 464 public static function field_number( $args, $value ) { … … 460 471 461 472 /** 462 * Function to render a text.463 * 464 * @ access public465 * 466 * @param string $name467 * @param string $value 468 * @return string 473 * Renders a text input. 474 * 475 * @since 3.3.4 476 * 477 * @param array $args Field arguments, as returned by validate(). 478 * @param string $value Value. 479 * @return string The input. 469 480 */ 470 481 public static function field_text( $args, $value ) { … … 477 488 478 489 /** 479 * Function to rendera textarea.480 * 481 * @ access public482 * 483 * @param string $name484 * @param string $value 485 * @return string 490 * Renders a textarea. 491 * 492 * @since 3.3.4 493 * 494 * @param array $args Field arguments, as returned by validate(). 495 * @param string $value Value. 496 * @return string The input. 486 497 */ 487 498 public static function field_textarea( $args, $value ) { … … 493 504 } 494 505 506 /** 507 * Renders a textarea for a list, with items separated by semicolons. 508 * 509 * @since 3.3.4 510 * 511 * @param array $args Field arguments, as returned by validate(). 512 * @param string|array $value Value, or a list of values. 513 * @return string The input. 514 */ 495 515 public static function field_list( $args, $value ) { 496 516 if ( is_array( $value ) ) { … … 500 520 } 501 521 522 /** 523 * Renders the inputs for a venue's properties. 524 * 525 * @since 3.3.4 526 * 527 * @param array $args Field arguments, as returned by validate(), including 'properties'. 528 * @param array $value Values, keyed by property. 529 * @return string The inputs. 530 */ 502 531 public static function field_venue( $args, $value ) { 503 532 return self::render( $args['properties'], $value ); 504 533 } 505 534 535 /** 536 * Renders the inputs for a citation's properties. 537 * 538 * @since 3.3.4 539 * 540 * @param array $args Field arguments, as returned by validate(), including 'properties'. 541 * @param array $value Values, keyed by property. 542 * @return string The inputs. 543 */ 506 544 public static function field_cite( $args, $value ) { 507 545 return self::render( $args['properties'], $value ); 508 546 } 509 547 548 /** 549 * Checks whether a string starts with a prefix. 550 * 551 * @since 3.3.4 552 * 553 * @param string $source The source string. 554 * @param string $prefix The prefix to look for. 555 * @return bool True if the source starts with the prefix. 556 */ 510 557 public static function str_prefix( $source, $prefix ) { 511 558 return strncmp( $source, $prefix, strlen( $prefix ) ) === 0; 512 559 } 513 560 561 /** 562 * Returns a value from an array. 563 * 564 * @since 3.3.4 565 * 566 * @param string $key Key. 567 * @param array $array The array. 568 * @return mixed The value, or false if it is not set. 569 */ 514 570 private static function get( $key, $array ) { 515 571 if ( ! is_array( $array ) ) { … … 522 578 } 523 579 580 /** 581 * Checks a field definition and fills in defaults. 582 * 583 * @since 3.3.4 584 * 585 * @param array $element Field definition. 586 * @return array|false The field definition, or false if it is not valid. 587 */ 524 588 private static function validate( $element ) { 525 // Everything must have a type 589 // Everything must have a type. 526 590 if ( ! self::get( 'type', $element ) ) { 527 591 return false; 528 592 } 529 // Everything must have a label 593 // Everything must have a label. 530 594 if ( ! self::get( 'label', $element ) ) { 531 595 return false; 532 596 } 533 // If no name property then the label property copies over 597 // If no name property then the label property copies over. 534 598 if ( ! self::get( 'name', $element ) ) { 535 599 $element['name'] = sanitize_title( $element['label'] ); 536 600 } 537 // If no label property then the name property copies over 601 // If no label property then the name property copies over. 538 602 if ( ! self::get( 'label', $element ) ) { 539 603 $element['label'] = $element['name']; 540 604 } 541 // If no class property then the class property is set to empty 605 // If no class property then the class property is set to empty. 542 606 if ( ! self::get( 'class', $element ) ) { 543 607 $element['class'] = ''; 544 608 } 545 // Class can be an array 609 // Class can be an array. 546 610 if ( is_array( $element['class'] ) ) { 547 611 $element['class'] = implode( ' ', $element['class'] ); 548 612 } 549 613 $type = self::get( 'type', $element ); 550 // Any non supported type should be considered to be text 614 // Any non supported type should be considered to be text. 551 615 if ( ! self::supported_type( $type ) ) { 552 616 $type = 'text'; 553 617 } 554 // Type Specific Conditions 618 // Type Specific Conditions. 555 619 switch ( $type ) { 556 620 case 'cite': … … 562 626 case 'number': 563 627 if ( ! array_key_exists( 'step', $element ) ) { 564 $element['step'] = 1; // Defaults to Even Numbers 628 $element['step'] = 1; // Defaults to Even Numbers. 565 629 } 566 630 break; 567 631 case 'select': 568 if ( ! array_key_exists( 'options', $element s) ) {632 if ( ! array_key_exists( 'options', $element ) ) { 569 633 return false; 570 634 } 571 635 break; 572 636 case 'duration': 573 if ( array_key_exists( 'pieces', $element s) ) {574 // Ensure only valid options 575 $element['pieces'] = array_intersect( array( 'Y', 'M', 'D', 'H', 'I', 'S' ), $element s['pieces'] );637 if ( array_key_exists( 'pieces', $element ) ) { 638 // Ensure only valid options. 639 $element['pieces'] = array_intersect( array( 'Y', 'M', 'D', 'H', 'I', 'S' ), $element['pieces'] ); 576 640 } else { 577 // By default only show hours, minutes, seconds 641 // By default only show hours, minutes, seconds. 578 642 $element['pieces'] = array( 'H', 'I', 'S' ); 579 643 } … … 582 646 return $element; 583 647 } 648 649 /** 650 * Whether a field type is supported. 651 * 652 * @since 3.3.4 653 * 654 * @param string $type Field type. 655 * @return bool 656 */ 584 657 public static function supported_type( $type ) { 585 658 return in_array( $type, array( 'cite', 'venue', 'coordinate', 'author', 'datetime', 'duration', 'text', 'url', 'textarea', 'list', 'section' ), true ); … … 588 661 589 662 /** 590 * Sets an array with only the mf2 prefixed meta. 591 * 663 * Returns the properties stored in mf2_ prefixed post meta, without the prefix. 664 * 665 * Reads the current post; the $post argument is not used. 666 * 667 * @since 3.3.4 668 * 669 * @param int|WP_Post $post Not used. 670 * @return array|false The properties, or false if there is no current post. 592 671 */ 593 672 private function get_mf2meta( $post ) { … … 606 685 unset( $meta[ $key ] ); 607 686 $key = str_replace( 'mf2_', '', $key ); 608 // Do not save microput prefixed instructions 687 // Do not save microput prefixed instructions. 609 688 if ( self::str_prefix( $key, 'mp-' ) ) { 610 689 continue; … … 625 704 626 705 /** 627 * Function to render a form from a schema array628 * 629 * @ access public630 * 631 * @param array $schema 632 * 633 * @return string Form706 * Renders a form from a field schema. 707 * 708 * @since 3.3.4 709 * 710 * @param array $schema Field definitions, keyed by property. 711 * @param array $values Optional. Values, keyed by property. Default empty array. 712 * @return string The form. 634 713 */ 635 714 public static function render( $schema, $values = array() ) { … … 649 728 } 650 729 651 /* Extracts microformats elements from post data. 652 * Microformats elements are prefixed by mf2_ 653 * After that, underscore would indicate properties that need to be reconstituted 654 */ 730 /** 731 * Extracts microformats properties from submitted form data. 732 * 733 * Properties are prefixed by mf2_. After that, an underscore marks the 734 * pieces of a property that need to be put back together. 735 * 736 * @since 3.3.4 737 * 738 * @param array $data Form data, such as $_POST. 739 * @return array The properties. 740 */ 655 741 public static function rebuild_data( $data ) { 656 742 $raw = array(); … … 669 755 $raw[ $pieces[0] ] = $pieces[1]; 670 756 unset( $raw[ $key ] ); 671 // If this has the elements of a duration 757 // If this has the elements of a duration. 672 758 if ( ! empty( array_intersect( array( 'Y', 'M', 'D', 'H', 'I', 'S' ), $raw[ $pieces[0] ] ) ) ) { 673 $interval = build_interval( $raw[ $pieces[0] ] );759 $interval = Kind_Time::build_interval( $raw[ $pieces[0] ] ); 674 760 $raw[ $pieces[0] ] = date_interval_to_iso8601( $interval ); 675 761 } 676 // If this has the elements of a datetime 762 // If this has the elements of a datetime. 677 763 if ( ! empty( array_intersect( array( 'date', 'time', 'offset' ), $raw[ $pieces[0] ] ) ) ) { 678 $datetime = build_datetime( $raw[ $pieces[0] ] );764 $datetime = Kind_Time::build_datetime( $raw[ $pieces[0] ] ); 679 765 $raw[ $pieces[0] ] = $datetime->format( DATE_W3C ); 680 766 } -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-media-metadata.php
r3001401 r3738687 1 1 <?php 2 2 /** 3 * Kind_Media_Metadata class. 3 4 * 4 * Enhances Metadata for Media 5 * @package Post_Kinds 6 * @since 3.1.0 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Enhances the metadata stored for media attachments, and records the media a post uses. 13 * 14 * @since 3.1.0 5 15 */ 6 16 class Kind_Media_Metadata { … … 9 19 * Function to Initialize the Configuration. 10 20 * 21 * @since 3.1.0 22 * 11 23 * @access public 12 24 */ 13 25 public static function init() { 14 $wp_version = get_bloginfo( 'version' );15 26 add_filter( 'wp_generate_attachment_metadata', array( static::class, 'wp_generate_attachment_metadata' ), 33, 2 ); 16 if ( version_compare( $wp_version, '5.3', '>' ) ) { 17 add_filter( 'wp_update_attachment_metadata', array( static::class, 'wp_sanitize_media_metadata' ), 9, 2 ); 18 } 27 add_filter( 'wp_update_attachment_metadata', array( static::class, 'wp_sanitize_media_metadata' ), 9, 2 ); 19 28 add_action( 'wp_enqueue_scripts', array( static::class, 'enqueue' ) ); 20 29 21 add_action( 'save_post', array( static::class, 'save_post' ), 20 ); 30 // Kinds apply to posts only; revisions and autosaves have their own post type. 31 add_action( 'save_post_post', array( static::class, 'save_post' ), 20 ); 32 33 // The photo property can change outside a save, for example from Micropub. 34 foreach ( array( 'added_post_meta', 'updated_post_meta', 'deleted_post_meta' ) as $hook ) { 35 add_action( $hook, array( static::class, 'photo_meta_changed' ), 10, 3 ); 36 } 22 37 23 38 add_filter( 'attachment_fields_to_edit', array( static::class, 'attachment_fields_to_edit' ), 10, 2 ); … … 25 40 } 26 41 42 /** 43 * Whether the request is for an AMP page, according to the AMP plugin. 44 * 45 * @since 3.5.1 46 * 47 * @return bool 48 */ 27 49 public static function is_amp_endpoint() { 28 50 if ( function_exists( 'is_amp_endpoint' ) ) { … … 32 54 } 33 55 56 /** 57 * Enqueues the media fragment script on single posts that have media. 58 * 59 * @since 3.3.0 60 */ 34 61 public static function enqueue() { 35 if ( ! is_front_page() && is_singular() && ! self::is_amp_endpoint() ) { 36 wp_enqueue_script( 37 'media-fragment', 38 plugins_url( 'js/clone-media-fragment.js', __DIR__ ), 39 array(), 40 '1.0', 41 true 42 ); 43 } 62 if ( is_front_page() || ! is_singular() || self::is_amp_endpoint() || ! self::has_media( get_queried_object() ) ) { 63 return; 64 } 65 wp_enqueue_script( 66 'media-fragment', 67 plugins_url( 'js/clone-media-fragment.js', __DIR__ ), 68 array(), 69 '1.0', 70 // WordPress 6.2 reads the array as a true $in_footer; 6.3 and later also defer it. 71 array( 72 'in_footer' => true, 73 'strategy' => 'defer', 74 ) 75 ); 76 } 77 78 /** 79 * Returns whether a post may show audio or video, which the media 80 * fragment script links to. 81 * 82 * @since 4.0.0 83 * 84 * @param mixed $post Post object, usually the queried object. 85 * @return bool True if the post has an audio or video kind or property, or 86 * audio or video in its content. 87 */ 88 public static function has_media( $post ) { 89 if ( ! $post instanceof WP_Post ) { 90 return false; 91 } 92 if ( 'post' === $post->post_type ) { 93 if ( has_post_kind( array( 'audio', 'video', 'listen', 'watch', 'jam' ), $post ) ) { 94 return true; 95 } 96 if ( get_post_meta( $post->ID, 'mf2_audio', true ) || get_post_meta( $post->ID, 'mf2_video', true ) ) { 97 return true; 98 } 99 } 100 return (bool) preg_match( '/<(audio|video)[\s>]|\[(audio|video|playlist)[\s\]]/i', $post->post_content ); 44 101 } 45 102 … … 47 104 /** 48 105 * Displays the Media Creator Description on the Attachment Page. 106 * 107 * @since 3.4.0 49 108 * 50 109 * @param array $form_fields See attachment_fields_to_edit filter in WordPress. … … 55 114 56 115 $author = get_post_meta( $post->ID, 'mf2_author', true ); 57 if ( Parse_This_MF2_Utils::is_microformat( $author ) ) {58 $author = mf2_to_jf2( $author );116 if ( \ParseThis\MF2_Utils::is_microformat( $author ) ) { 117 $author = \ParseThis\mf2_to_jf2( $author ); 59 118 } else { 60 119 $author = array(); … … 81 140 * Saves Extra Fields. 82 141 * 83 * @param array $post Attachment post data. 84 * @param array $attachment data. 142 * @since 3.4.0 143 * 144 * @param array $post Attachment post data. 145 * @param array $attachment Attachment fields submitted. 85 146 */ 86 147 public static function attachment_fields_to_save( $post, $attachment ) { 148 // Core checks this before saving; check again as the filter can be applied elsewhere. 149 if ( empty( $post['ID'] ) || ! current_user_can( 'edit_post', $post['ID'] ) ) { 150 return $post; 151 } 152 // Core passes the request data slashed. 87 153 $author = array(); 88 if ( isset( $attachment['author_name'] ) && ! empty( $attachment['author_name'] ) ) {89 $author['name'] = $attachment['author_name'];90 } 91 if ( isset( $attachment['author_url'] ) && ! empty( $attachment['author_url'] ) ) {92 $author['url'] = $attachment['author_url'];154 if ( isset( $attachment['author_name'] ) && is_string( $attachment['author_name'] ) ) { 155 $author['name'] = sanitize_text_field( wp_unslash( $attachment['author_name'] ) ); 156 } 157 if ( isset( $attachment['author_url'] ) && is_string( $attachment['author_url'] ) ) { 158 $author['url'] = sanitize_url( trim( wp_unslash( $attachment['author_url'] ) ), array( 'http', 'https' ) ); 93 159 } 94 160 $author = array_filter( $author ); … … 97 163 } else { 98 164 $author['type'] = 'card'; 99 update_post_meta( $post['ID'], 'mf2_author', jf2_to_mf2( $author ) ); 165 // The metadata API unslashes, so slash the sanitized values. 166 update_post_meta( $post['ID'], 'mf2_author', wp_slash( \ParseThis\jf2_to_mf2( $author ) ) ); 100 167 } 101 168 return $post; 102 169 } 103 170 104 /* 171 /** 172 * Finds the media library attachments used in a content block. 173 * 174 * The content is parsed once for images, audio and video. An image's ID 175 * is taken from its wp-image-{id} class when it has one; otherwise, and 176 * for audio and video sources, the URL is looked up. 177 * 178 * @since 4.0.0 179 * 180 * @param string $content Content. 181 * @return array { 182 * Attachment IDs found, each list without duplicates or zeros. 183 * 184 * @type int[] $img Image attachment IDs. 185 * @type int[] $audio Audio attachment IDs. 186 * @type int[] $video Video attachment IDs. 187 * } 188 */ 189 private static function get_media_from_content( $content ) { 190 $return = array( 191 'img' => array(), 192 'audio' => array(), 193 'video' => array(), 194 ); 195 $content = wp_unslash( (string) $content ); 196 // Most content has no media; skip parsing it. 197 if ( false === stripos( $content, '<img' ) && false === stripos( $content, '<audio' ) && false === stripos( $content, '<video' ) ) { 198 return $return; 199 } 200 $doc = \ParseThis\pt_load_domdocument( $content ); 201 202 foreach ( $doc->getElementsByTagName( 'img' ) as $image ) { 203 $id = 0; 204 foreach ( explode( ' ', $image->getAttribute( 'class' ) ) as $class ) { 205 if ( 0 === strpos( $class, 'wp-image-' ) ) { 206 $id = (int) substr( $class, strlen( 'wp-image-' ) ); 207 break; 208 } 209 } 210 if ( ! $id ) { 211 $id = attachment_url_to_postid( $image->getAttribute( 'src' ) ); 212 } 213 $return['img'][] = $id; 214 } 215 foreach ( array( 'audio', 'video' ) as $tag ) { 216 foreach ( $doc->getElementsByTagName( $tag ) as $media ) { 217 foreach ( $media->getElementsByTagName( 'source' ) as $source ) { 218 $return[ $tag ][] = attachment_url_to_postid( remove_query_arg( '_', $source->getAttribute( 'src' ) ) ); 219 } 220 } 221 } 222 foreach ( $return as $type => $ids ) { 223 $return[ $type ] = array_values( array_unique( array_filter( array_map( 'intval', $ids ) ) ) ); 224 } 225 return $return; 226 } 227 228 /** 105 229 * Determine Attached Images from a Content Block. 230 * 231 * @since 3.4.0 106 232 * 107 233 * @param string $content Content. 108 234 * @return array Array of Attachment IDs. 109 */235 */ 110 236 public static function get_img_from_content( $content ) { 111 $content = wp_unslash( $content ); 112 $return = array(); 113 $doc = pt_load_domdocument( $content ); 114 $images = $doc->getElementsByTagName( 'img' ); 115 foreach ( $images as $image ) { 116 $classes = $image->getAttribute( 'class' ); 117 $classes = explode( ' ', $classes ); 118 foreach ( $classes as $class ) { 119 if ( 0 === strpos( $class, 'wp-image-' ) ) { 120 $id = (int) str_replace( 'wp-image-', '', $class ); 121 if ( 0 !== $id ) { 122 $return[] = $id; 123 } 124 break; 125 } 126 } 127 $url = $image->getAttribute( 'src' ); 128 $id = attachment_url_to_postid( $url ); 129 if ( 0 !== $id ) { 130 $return[] = $id; 131 } 132 } 133 return array_unique( $return ); 134 } 135 136 /* 237 return self::get_media_from_content( $content )['img']; 238 } 239 240 /** 137 241 * Determine Attached Audio from a Content Block. 242 * 243 * @since 3.4.0 138 244 * 139 245 * @param string $content Content. 140 246 * @return array Array of Attachment IDs. 141 */247 */ 142 248 public static function get_audio_from_content( $content ) { 143 $content = wp_unslash( $content ); 144 $return = array(); 145 $doc = pt_load_domdocument( $content ); 146 $audios = $doc->getElementsByTagName( 'audio' ); 147 foreach ( $audios as $audio ) { 148 $sources = $audio->getElementsByTagName( 'source' ); 149 foreach ( $sources as $source ) { 150 $url = remove_query_arg( '_', $source->getAttribute( 'src' ) ); 151 $id = attachment_url_to_postid( $url ); 152 $return[] = $id; 153 } 154 } 155 return array_unique( $return ); 156 } 157 158 /* 249 return self::get_media_from_content( $content )['audio']; 250 } 251 252 /** 159 253 * Determine Attached Videos from a Content Block. 254 * 255 * @since 3.4.0 160 256 * 161 257 * @param string $content Content. 162 258 * @return array Array of Attachment IDs. 163 */259 */ 164 260 public static function get_video_from_content( $content ) { 165 $content = wp_unslash( $content ); 166 $return = array(); 167 $doc = pt_load_domdocument( $content ); 168 $videos = $doc->getElementsByTagName( 'video' ); 169 foreach ( $videos as $video ) { 170 $sources = $video->getElementsByTagName( 'source' ); 171 foreach ( $sources as $source ) { 172 $url = remove_query_arg( '_', $source->getAttribute( 'src' ) ); 173 $id = attachment_url_to_postid( $url ); 174 $return[] = $id; 175 } 176 } 177 return array_unique( $return ); 178 } 179 180 private function media_sideload_image( $url, $post_id, $description = null ) { 181 // To prevent sideloading the same URL multiple times check for the original URL which will now be stored 182 $ids = get_posts( 183 array( 184 'post_type' => 'attachment', 185 'suppress_filters' => false, 186 'nopaging' => true, 187 'meta_key' => '_source_url', 188 'meta_value' => $url, 189 'fields' => 'ids', 190 ) 191 ); 192 if ( ! empty( $ids ) ) { 193 return $ids[0]; 194 } 195 $id = media_sideload_image( $url, $post_id, $description, 'id' ); 196 // Version 5.4 adds this automatically. 197 if ( $id && version_compare( $wp_version, '5.3', '>' ) ) { 198 update_post_meta( $id, '_source_url', $url ); 199 } 200 return $id; 201 } 202 203 204 205 /** 206 * Every time the post is saved check for media embedded in content and save a list of attachment IDs. 207 */ 261 return self::get_media_from_content( $content )['video']; 262 } 263 264 /** 265 * Every time a post is saved, stores the attachment IDs of the media in its content. 266 * 267 * The lists are kept in the _content_img_ids, _content_audio_ids and 268 * _content_video_ids meta; a key is deleted when there is no media of that 269 * type, so the photos archive can select posts by whether the key exists. 270 * 271 * @since 3.4.0 272 * 273 * @param int $post_id Post ID. 274 */ 208 275 public static function save_post( $post_id ) { 209 $post = get_post( $post_id ); 210 $content = do_shortcode( $post->post_content ); 211 212 $gallery_ids = array(); 213 $gallery = get_post_gallery( $post_id, false ); 214 215 if ( is_array( $gallery ) ) { 216 if ( array_key_exists( 'ids', $gallery ) ) { 217 $gallery_ids = explode( ',', $gallery['ids'] ); 218 } 219 } 220 $ids = self::get_img_from_content( $content ); 221 if ( is_array( $ids ) && is_array( $gallery_ids ) ) { 222 $ids = array_merge( $ids, $gallery_ids ); 223 } 224 225 if ( ! $ids ) { 226 delete_post_meta( $post_id, '_content_img_ids' ); 276 // The classic editor's draft autosave; the next save will record the media. 277 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { 278 return; 279 } 280 $post = get_post( $post_id ); 281 if ( ! $post ) { 282 return; 283 } 284 $content = $post->post_content; 285 // Shortcodes such as [gallery], [audio] and [video] produce the media markup. 286 if ( false !== strpos( $content, '[' ) ) { 287 $content = do_shortcode( $content ); 288 } 289 $media = self::get_media_from_content( $content ); 290 291 $gallery = get_post_gallery( $post_id, false ); 292 if ( is_array( $gallery ) && ! empty( $gallery['ids'] ) ) { 293 $media['img'] = array_values( array_unique( array_merge( $media['img'], array_filter( array_map( 'intval', explode( ',', $gallery['ids'] ) ) ) ) ) ); 294 } 295 296 foreach ( $media as $type => $ids ) { 297 $key = '_content_' . $type . '_ids'; 298 if ( empty( $ids ) ) { 299 delete_post_meta( $post_id, $key ); 300 } else { 301 update_post_meta( $post_id, $key, $ids ); 302 } 303 } 304 self::update_photo_flag( $post_id ); 305 } 306 307 /** 308 * Updates the photo flag when a post's photo property changes. 309 * 310 * @since 4.0.0 311 * 312 * @param int|int[] $meta_id Meta ID, or IDs when deleting. 313 * @param int $object_id Post ID. 314 * @param string $meta_key Meta key. 315 */ 316 public static function photo_meta_changed( $meta_id, $object_id, $meta_key ) { 317 if ( 'mf2_photo' === $meta_key && 'post' === get_post_type( $object_id ) ) { 318 self::update_photo_flag( $object_id ); 319 } 320 } 321 322 /** 323 * Records whether a post has photos, for the photos archive. 324 * 325 * A post has photos when it has images in its content or a photo 326 * property. The _kind_has_photo meta exists only for those posts, so the 327 * archive can select them with one indexed meta key. 328 * 329 * @since 4.0.0 330 * 331 * @param int $post_id Post ID. 332 */ 333 public static function update_photo_flag( $post_id ) { 334 if ( get_post_meta( $post_id, '_content_img_ids', true ) || get_post_meta( $post_id, 'mf2_photo', true ) ) { 335 update_post_meta( $post_id, '_kind_has_photo', 1 ); 227 336 } else { 228 update_post_meta( $post_id, '_content_img_ids', $ids ); 229 } 230 $ids = self::get_video_from_content( $content ); 231 if ( ! $ids ) { 232 delete_post_meta( $post_id, '_content_video_ids' ); 233 } else { 234 update_post_meta( $post_id, '_content_video_ids', $ids ); 235 } 236 $ids = self::get_audio_from_content( $content ); 237 if ( ! $ids ) { 238 delete_post_meta( $post_id, '_content_audio_ids' ); 239 } else { 240 update_post_meta( $post_id, '_content_audio_ids', $ids ); 337 delete_post_meta( $post_id, '_kind_has_photo' ); 241 338 } 242 339 } … … 248 345 * bad character encodings from causing database calls and API endpoints to fail. 249 346 * 250 * @param array $metadata An existing array with data 347 * @since 3.3.0 348 * 349 * @param array $metadata An existing array with data. 251 350 * 252 351 * @return array Returns array of sanitized metadata. … … 271 370 } 272 371 372 /** 373 * Stores an attachment's author, album and duration from its file metadata. 374 * 375 * The image credit, or the artist of audio or video, becomes the author; 376 * the album becomes the publication. 377 * 378 * @since 3.3.4 379 * 380 * @param array|mixed $data Attachment metadata. 381 * @param int $attachment_id Attachment ID. 382 * @return array|mixed The metadata, with empty values removed. 383 */ 273 384 public static function wp_generate_attachment_metadata( $data, $attachment_id ) { 274 385 if ( ! is_array( $data ) ) { 275 386 return $data; 276 387 } 277 $data = array_filter( $data ); 278 $attachment = get_post( $attachment_id, ARRAY_A ); 388 $data = array_filter( $data ); 279 389 if ( isset( $data['image_meta'] ) ) { 280 390 $meta = $data['image_meta']; … … 283 393 $attachment_id, 284 394 'mf2_author', 285 jf2_to_mf2(395 \ParseThis\jf2_to_mf2( 286 396 array( 287 397 'name' => $meta['credit'], … … 299 409 $attachment_id, 300 410 'mf2_author', 301 jf2_to_mf2(411 \ParseThis\jf2_to_mf2( 302 412 array( 303 413 'name' => $data['artist'], … … 308 418 } 309 419 if ( isset( $data['length'] ) ) { 310 update_post_meta( $attachment_id, 'mf2_duration', array( seconds_to_iso8601( $data['length'] ) ) );420 update_post_meta( $attachment_id, 'mf2_duration', array( \ParseThis\seconds_to_iso8601( $data['length'] ) ) ); 311 421 } 312 422 return $data; -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-media-view.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Kind Media View Class3 * Kind_Media_View class. 4 4 * 5 * @package Post Kind6 * Used to Display Media5 * @package Post_Kinds 6 * @since 3.3.0 7 7 */ 8 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Builds the markup for images, audio and video in kind views. 13 * 14 * @since 3.3.0 15 */ 9 16 class Kind_Media_View { 17 /** 18 * Attachment IDs or media URLs. 19 * 20 * @since 3.3.0 21 * @var int[]|string[]|int|string 22 */ 10 23 private $ids; 24 25 /** 26 * Media type: 'photo', 'audio' or 'video'. 27 * 28 * @since 3.3.0 29 * @var string 30 */ 11 31 private $type; 32 33 /** 34 * Sets up the media to display. 35 * 36 * @since 3.3.0 37 * 38 * @param int[]|string[]|int|string $ids Attachment IDs or media URLs. 39 * @param string $type Media type: 'photo', 'audio' or 'video'. 40 */ 12 41 public function __construct( $ids, $type ) { 13 42 $this->ids = $ids; … … 15 44 } 16 45 46 /** 47 * Returns the markup for the media. 48 * 49 * @since 3.3.0 50 * 51 * @return string The markup, or an empty string if the type is not supported. 52 */ 17 53 public function get() { 18 54 switch ( $this->type ) { … … 27 63 } 28 64 65 /** 66 * Returns a one-column gallery of photos. 67 * 68 * @since 3.3.0 69 * 70 * @param int[] $photos Attachment IDs. 71 * @return string The gallery markup. 72 */ 29 73 private function photo( $photos ) { 30 74 return gallery_shortcode( … … 41 85 * Return a media view for the audio post kind. 42 86 * 87 * @since 3.3.0 88 * 43 89 * @access private 44 * @param int|string $id Audio attachment ID or audio URL 45 * @param mixed $args Arguments for the audio media view. 90 * 91 * @param int|string|array $id Audio attachment ID or audio URL, or a list of them. 92 * @param array|null $args Arguments for the audio media view. 46 93 * 47 94 * @return array|string|void … … 72 119 $return = ''; 73 120 if ( $args['src'] ) { 121 $args = wp_parse_args( $args, $default ); 74 122 $return = wp_audio_shortcode( $args ); 75 123 } … … 80 128 * Return a media view for the video post kind. 81 129 * 130 * @since 3.3.0 131 * 82 132 * @access private 83 * @param int|string $id Video attachment ID or URL. 84 * @param mixed $args Arguments for the video media view. 133 * 134 * @param int|string|array $id Video attachment ID or URL, or a list of them. 135 * @param array|null $args Arguments for the video media view. 85 136 * 86 137 * @return array|string|void … … 98 149 } elseif ( wp_http_validate_url( $id ) ) { 99 150 $args['src'] = $id; 100 } if ( 0 === $id ) {151 } elseif ( 0 === $id ) { 101 152 return ''; 102 153 } else { -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-menu-widget.php
r3067251 r3738687 1 1 <?php 2 /** 3 * Kind_Menu_Widget class. 4 * 5 * @package Post_Kinds 6 * @since 3.1.3 7 */ 2 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Widget that lists links to the archive of each kind. 13 * 14 * @since 3.1.3 15 */ 3 16 class Kind_Menu_Widget extends WP_Widget { 4 17 /** 5 18 * Register widget with WordPress. 19 * 20 * @since 3.1.3 6 21 */ 7 22 public function __construct() { 8 23 parent::__construct( 9 'Kind_Menu_Widget', // Base ID 10 __( 'Kind Menu Widget', 'indieweb-post-kinds' ), // Name 24 'Kind_Menu_Widget', // Base ID. 25 __( 'Kind Menu Widget', 'indieweb-post-kinds' ), // Name. 11 26 array( 12 27 'classname' => 'kind_menu_widget', … … 19 34 /** 20 35 * Front-end display of widget. 36 * 37 * @since 3.1.3 21 38 * 22 39 * @see WP_Widget::widget() … … 35 52 } 36 53 $include = array_merge( $include, array( 'note', 'reply', 'article' ) ); 37 // Filter Kinds 54 /** 55 * This filter is documented in includes/class-kind-taxonomy.php 56 */ 38 57 $include = array_unique( apply_filters( 'kind_include', $include ) ); 39 // Note cannot be removed or disabled without hacking the code 58 // Note cannot be removed or disabled without hacking the code. 40 59 if ( ! in_array( 'note', $include, true ) ) { 41 60 $include[] = 'note'; 42 61 } 43 62 44 // phpcs:ignore 45 echo $args['before_widget']; 63 echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget wrapper markup registered by the theme. 46 64 if ( $title ) { 47 echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore 65 echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Wrapper markup is the theme's; the title is escaped by core's widget_title filter. 48 66 } 49 67 ?> … … 52 70 <ul> 53 71 <?php 54 $feed = is_front_page() ? ' rel="feed"' : '';72 $feed = is_front_page() ? ' rel="feed"' : ''; 55 73 foreach ( $include as $i ) { 56 74 $count = Kind_Taxonomy::get_post_kind_count( $i ); … … 60 78 $name = ( 1 === $count ) ? Kind_Taxonomy::get_kind_info( $i, 'singular_name' ) : Kind_Taxonomy::get_kind_info( $i, 'name' ); 61 79 /* translators: 1. Blog Title 2. Kind Singular Name */ 62 $ title = sprintf( __( 'title="%1$s %2$s Feed"', 'indieweb-post-kinds' ), esc_attr( get_bloginfo( 'name' ) ), esc_attR( Kind_Taxonomy::get_kind_info( $i, 'singular_name' )) );63 if ( 1 === (int) $instance['count']) {64 $ count = sprintf( '%1$s (%2$s)', $name, $count);80 $feed_title = sprintf( __( '%1$s %2$s Feed', 'indieweb-post-kinds' ), get_bloginfo( 'name' ), Kind_Taxonomy::get_kind_info( $i, 'singular_name' ) ); 81 if ( 1 === (int) ( $instance['count'] ?? 0 ) ) { 82 $label = sprintf( '%1$s (%2$s)', $name, number_format_i18n( $count ) ); 65 83 } else { 66 $ count= $name;84 $label = $name; 67 85 } 68 86 printf( 69 '<li><a %4$s %5$stype="text/html" href="%2$s">%1$s%3$s</a></li>',70 Kind_Taxonomy::get_icon( $i ), // phpcs:ignore 87 '<li><a%4$s title="%5$s" type="text/html" href="%2$s">%1$s%3$s</a></li>', 88 Kind_Taxonomy::get_icon( $i ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The plugin's own SVG icon markup. 71 89 esc_url( Kind_Taxonomy::get_post_kind_link( $i ) ), 72 $count, // phpcs:ignore73 $feed, // phpcs:ignore 74 $title // phpcs:ignore75 ); // phpcs:ignore90 esc_html( $label ), 91 $feed, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- A fixed attribute string. 92 esc_attr( $feed_title ) 93 ); 76 94 } 77 if ( 1 === (int) $instance['all']) {78 printf( '<li><a type="text/html" href="%2$s">%1$s%3$s</a></li>', Kind_Taxonomy::get_icon( 'firehose' ), esc_url( get_post_type_archive_link( 'post' ) ), esc_html__( 'All Posts', 'indieweb-post-kinds' ) ); // phpcs:ignore 95 if ( 1 === (int) ( $instance['all'] ?? 0 ) ) { 96 printf( '<li><a type="text/html" href="%2$s">%1$s%3$s</a></li>', Kind_Taxonomy::get_icon( 'firehose' ), esc_url( get_post_type_archive_link( 'post' ) ), esc_html__( 'All Posts', 'indieweb-post-kinds' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The plugin's own SVG icon markup. 79 97 } 80 98 ?> … … 83 101 84 102 <?php 85 // phpcs:ignore 86 echo $args['after_widget']; 103 echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget wrapper markup registered by the theme. 87 104 } 88 105 … … 90 107 /** 91 108 * Sanitize widget form values as they are saved. 109 * 110 * @since 3.1.3 92 111 * 93 112 * @see WP_Widget::update() … … 99 118 */ 100 119 public function update( $new_instance, $old_instance ) { 101 array_walk_recursive( $new_instance, 'sanitize_text_field' ); 102 return $new_instance; 120 $termslist = isset( $new_instance['termslist'] ) && is_array( $new_instance['termslist'] ) ? array_filter( $new_instance['termslist'], 'is_string' ) : array(); 121 $termslist = array_values( array_unique( array_intersect( array_map( 'sanitize_key', $termslist ), Kind_Taxonomy::get_kind_list() ) ) ); 122 return array( 123 'title' => isset( $new_instance['title'] ) && is_string( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '', 124 'termslist' => $termslist, 125 'count' => empty( $new_instance['count'] ) ? 0 : 1, 126 'all' => empty( $new_instance['all'] ) ? 0 : 1, 127 ); 103 128 } 104 129 … … 106 131 /** 107 132 * Create the form for the Widget admin 133 * 134 * @since 3.1.3 108 135 * 109 136 * @see WP_Widget::form() … … 120 147 $termslist = (array) $instance['termslist']; 121 148 ?> 122 <p><label for="title"><?php esc_html_e( 'Title: ', 'indieweb-post-kinds' ); ?></label> 123 <input type="text" size="30" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?> id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" value=" 124 <?php echo esc_html( ( $instance['title'] ?? '' ) ); ?>" /></p> 149 <p><label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title: ', 'indieweb-post-kinds' ); ?></label> 150 <input type="text" size="30" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" value="<?php echo esc_attr( $instance['title'] ?? '' ); ?>" /></p> 125 151 <div id="kind-all"> 126 152 <?php 127 foreach ( get_option( 'kind_termslist', Kind_Taxonomy::get_kind_list()) as $term ) {153 foreach ( Kind_Config::get_termslist() as $term ) { 128 154 $value = Kind_Taxonomy::get_post_kind_info( $term ); 129 155 if ( $value->show ) { … … 135 161 checked( in_array( $term, $termslist, true ), true, false ) 136 162 ); 137 printf( '%1$s %2$s<br />', Kind_Taxonomy::get_icon( $term ), esc_html( $value->singular_name ) ); // phpcs:ignore163 printf( '%1$s %2$s<br />', Kind_Taxonomy::get_icon( $term ), esc_html( $value->singular_name ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The plugin's own SVG icon markup. 138 164 } 139 165 } -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-metabox.php
r3001401 r3738687 1 1 <?php 2 2 /** 3 * Post Kind Post MetaBox Class3 * Kind_Metabox class. 4 4 * 5 * Sets Up Tabbed Metabox in the Posting UI for Kind data. 5 * @package Post_Kinds 6 * @since 2.2.0 6 7 */ 7 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Adds the tabbed Post Kinds metabox to the Classic Editor and saves its fields. 13 * 14 * @since 2.2.0 15 */ 8 16 class Kind_Metabox { 9 17 10 18 /** 19 * Plugin version, used to version enqueued assets. 20 * 11 21 * @var string $version 12 22 */ … … 15 25 /** 16 26 * Function to initiate our metabox. 27 * 28 * @since 2.2.0 17 29 * 18 30 * @access public … … 21 33 self::$version = Post_Kinds_Plugin::$version; 22 34 add_action( 'edit_form_after_title', array( static::class, 'after_title_metabox' ) ); 23 // Add meta box to new post/post pages only 35 // Add meta box to new post/post pages only. 24 36 add_action( 'load-post.php', array( static::class, 'kindbox_setup' ) ); 25 37 add_action( 'load-post-new.php', array( static::class, 'kindbox_setup' ) ); … … 33 45 * Function to change our post kind. 34 46 * 47 * @since 3.1.0 48 * 35 49 * @access public 36 50 * … … 68 82 69 83 /** 70 * Function to change if a new post should be considered empty. 84 * Filters whether a post should be considered empty. 85 * 86 * Many kinds (likes, bookmarks, reposts, and so on) legitimately have no 87 * content, title or excerpt, which core would otherwise reject as empty. 88 * 89 * @since 3.0.0 71 90 * 72 91 * @access public 73 92 * 74 93 * @param bool $maybe_empty Whether or not the post should be considered empty. 75 * @param array $postarr Data for the post to be inserted .94 * @param array $postarr Data for the post to be inserted or updated. 76 95 * 77 96 * @return bool 78 97 */ 79 98 public static function wp_insert_post_empty_content( $maybe_empty, $postarr ) { 80 // Always let updates to trash posts through 81 if ( 'trash' === $postarr['post_status'] ) {99 // Always let updates to trash posts through. 100 if ( isset( $postarr['post_status'] ) && 'trash' === $postarr['post_status'] ) { 82 101 return false; 83 102 } 84 // Let All Micropub Posts through85 if ( isset( $postarr['meta_input'] ) && isset( $postarr['meta_input']['micropub_auth_response'] ) ) {103 // Let all Micropub creates through. 104 if ( isset( $postarr['meta_input']['micropub_auth_response'] ) ) { 86 105 return false; 87 106 } 88 if ( ! isset( $postarr['tax_input'] ) && ! isset( $postarr['tax_input']['kind'] ) ) { 89 return $maybe_empty; 90 } 91 $kind = get_term_by( 'id', $postarr['tax_input']['kind'][0], 'kind' ); 92 $kind = ( $kind instanceof WP_Term ) ? $kind->slug : ''; 93 // Use traditional rules for articles 107 $kind = self::kind_from_postarr( $postarr ); 108 // Use traditional rules for articles, and for posts without a kind. 94 109 if ( 'article' === $kind || ! $kind ) { 95 110 return $maybe_empty; 96 111 } 97 $keys = array( 'cite_url', 'cite_name', 'cite_summary' ); 98 $keys = array_flip( $keys ); 99 $diff = array_filter( array_intersect_key( $_POST, $keys ) ); 100 if ( ! empty( $diff ) ) { 112 // An existing post of a kind other than article may have no content, 113 // for example when Micropub updates a like. 114 if ( ! empty( $postarr['ID'] ) ) { 101 115 return false; 102 116 } 117 // A new post from the metabox needs something to respond to. 118 foreach ( array( 'cite_url', 'cite_name', 'cite_summary' ) as $key ) { 119 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checks whether a field was filled in; save_post() verifies the nonce before saving. 120 if ( isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) && '' !== sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) ) { 121 return false; 122 } 123 } 103 124 return $maybe_empty; 104 125 } 105 126 106 127 /** 128 * Returns the kind slug for post data being inserted or updated. 129 * 130 * The kind comes from tax_input, which the kind metabox submits as a slug and 131 * other callers may pass as term IDs, or failing that from the existing post. 132 * 133 * @since 4.0.0 134 * 135 * @access public 136 * 137 * @param array $postarr Data for the post to be inserted or updated. 138 * @return string Kind slug, or an empty string if none. 139 */ 140 public static function kind_from_postarr( $postarr ) { 141 if ( isset( $postarr['tax_input']['kind'] ) ) { 142 $kind = $postarr['tax_input']['kind']; 143 if ( is_array( $kind ) ) { 144 $kind = reset( $kind ); 145 } 146 $term = is_numeric( $kind ) ? get_term_by( 'id', (int) $kind, 'kind' ) : get_term_by( 'slug', (string) $kind, 'kind' ); 147 return ( $term instanceof WP_Term ) ? $term->slug : ''; 148 } 149 if ( ! empty( $postarr['ID'] ) ) { 150 $kind = get_post_kind_slug( $postarr['ID'] ); 151 return $kind ? $kind : ''; 152 } 153 return ''; 154 } 155 156 /** 107 157 * Execute metaboxes for the current screen, after the post title. 108 158 * 159 * @since 3.0.0 160 * 109 161 * @access public 110 162 * @param WP_Post $post Post object for the current screen. … … 117 169 /** 118 170 * Metabox setup. 171 * 172 * @since 2.2.0 119 173 * 120 174 * @access public … … 124 178 /* Add meta boxes on the 'add_meta_boxes' hook. */ 125 179 add_action( 'add_meta_boxes', array( $cls, 'add_meta_boxes' ) ); 180 add_filter( 'hidden_meta_boxes', array( $cls, 'hidden_meta_boxes' ), 10, 2 ); 126 181 add_action( 'admin_enqueue_scripts', array( $cls, 'enqueue_admin_scripts' ) ); 127 182 } … … 129 184 /** 130 185 * Enqueue our needed assets. 186 * 187 * @since 2.2.0 131 188 * 132 189 * @access public … … 145 202 146 203 // Provide a global object to our JS file containing our REST API endpoint, and API nonce 147 // Nonce must be 'wp_rest' 204 // Nonce must be 'wp_rest'. 148 205 wp_localize_script( 149 206 'kindmeta', … … 154 211 'success_message' => __( 'Your URL has been successfully retrieved and parsed', 'indieweb-post-kinds' ), 155 212 'clear_message' => __( 'Are you sure you want to clear post properties?', 'indieweb-post-kinds' ), 213 'error_message' => __( 'Error: Unable to Retrieve', 'indieweb-post-kinds' ), 214 'invalid_url' => __( 'Invalid URL', 'indieweb-post-kinds' ), 215 'media_title' => __( 'Attach', 'indieweb-post-kinds' ), 216 'media_button' => __( 'Use this media', 'indieweb-post-kinds' ), 156 217 ) 157 218 ); … … 163 224 /** 164 225 * Utility function to concatenate a list of post kinds. 226 * 227 * @since 2.7.5 165 228 * 166 229 * @access public … … 180 243 * Utility function to separate out a list of post kinds. 181 244 * 245 * @since 2.7.5 246 * 182 247 * @access public 183 248 * … … 194 259 /** 195 260 * Function to render date/time field inputs. 261 * 262 * @since 2.5.0 196 263 * 197 264 * @access public … … 206 273 $tz_seconds = get_option( 'gmt_offset' ) * 3600; 207 274 $offset = tz_seconds_to_offset( $tz_seconds ); 208 $time = divide_datetime( $datetime );275 $time = Kind_Time::divide_datetime( $datetime ); 209 276 if ( ! is_array( $time ) ) { 210 277 $time = array(); … … 213 280 $offset = $time['offset']; 214 281 } 215 $string = '<label class="half ' . $class . '" for="' . $prefix . '">' . $label . '<br/>'; 216 $string .= '<input class="date" type="date" name="' . $prefix . '_date" id="' . $prefix . '_date" value="' . ( $time['date'] ?? '' ) . '"/>'; 217 $string .= '<input class="time" type="time" name="' . $prefix . '_time" id="' . $prefix . '_time" step="1" value="' . ( $time['time'] ?? '' ) . '"/>'; 282 $prefix = esc_attr( $prefix ); 283 $string = '<label class="half ' . esc_attr( $class ) . '" for="' . $prefix . '">' . esc_html( $label ) . '<br/>'; 284 $string .= '<input class="date" type="date" name="' . $prefix . '_date" id="' . $prefix . '_date" value="' . esc_attr( $time['date'] ?? '' ) . '"/>'; 285 $string .= '<input class="time" type="time" name="' . $prefix . '_time" id="' . $prefix . '_time" step="1" value="' . esc_attr( $time['time'] ?? '' ) . '"/>'; 218 286 $string .= self::select_offset( $prefix, $offset ); 219 287 $string .= '</label>'; … … 224 292 * Function to render our timezone choices. 225 293 * 294 * @since 2.5.0 295 * 226 296 * @access public 227 297 * … … 231 301 */ 232 302 public static function select_offset( $prefix, $select ) { 233 $string = '<select name="' . $prefix . '_offset" id="' . $prefix. '_offset">';303 $string = '<select name="' . esc_attr( $prefix ) . '_offset" id="' . esc_attr( $prefix ) . '_offset">'; 234 304 $string .= self::timezone_offset_choice( $select ); 235 305 $string .= '</select>'; … … 240 310 * Function to render options for a chosen timezone select field. 241 311 * 312 * @since 3.0.0 313 * 242 314 * @access public 243 315 * … … 248 320 $tzlist = get_gmt_offsets(); 249 321 $string = ''; 250 foreach ( $tzlist as $ key => $value ) {251 $string .= '<option value="' . $value. '"';322 foreach ( $tzlist as $value ) { 323 $string .= '<option value="' . esc_attr( $value ) . '"'; 252 324 if ( $select === $value ) { 253 325 $string .= ' selected'; 254 326 } 255 $string .= '>GMT' . $value. '</option>';327 $string .= '>GMT' . esc_html( $value ) . '</option>'; 256 328 } 257 329 return $string; … … 261 333 * Render the options for the rating select field. 262 334 * 263 * @access public 264 * 265 * @param string $selected Selected Rating choice 335 * @since 3.7.0 336 * 337 * @access public 338 * 339 * @param string $selected Selected Rating choice. 266 340 * @return string 267 341 */ … … 278 352 $ret = ''; 279 353 foreach ( $ratings as $key => $value ) { 280 $ret .= '<option value="' . $key. '"';354 $ret .= '<option value="' . esc_attr( $key ) . '"'; 281 355 if ( $selected === $key ) { 282 356 $ret .= ' selected'; 283 357 } 284 $ret .= '>' . $value. '</option>';358 $ret .= '>' . esc_html( (string) $value ) . '</option>'; 285 359 } 286 360 return $ret; … … 290 364 * Render the options for the RSVP select field. 291 365 * 292 * @access public 293 * 294 * @param string $selected Selected RSVP choice 366 * @since 3.0.0 367 * 368 * @access public 369 * 370 * @param string $selected Selected RSVP choice. 295 371 * @return string 296 372 */ … … 306 382 $string = ''; 307 383 foreach ( $rsvps as $key => $value ) { 308 $string .= '<option value="' . $key. '"';384 $string .= '<option value="' . esc_attr( $key ) . '"'; 309 385 if ( $selected === $key ) { 310 386 $string .= ' selected'; 311 387 } 312 $string .= '>' . $value. '</option>';388 $string .= '>' . esc_html( (string) $value ) . '</option>'; 313 389 } 314 390 return $string; … … 317 393 /** 318 394 * Render our RSVP select input. 395 * 396 * @since 2.5.0 319 397 * 320 398 * @access public … … 324 402 */ 325 403 public static function rsvp_select( $selected ) { 326 $string = '<label for="mf2_rsvp">' . __( 'RSVP', 'indieweb-post-kinds' ) . '</label><br/>';404 $string = '<label for="mf2_rsvp">' . esc_html__( 'RSVP', 'indieweb-post-kinds' ) . '</label><br/>'; 327 405 $string .= '<select name="mf2_rsvp" id="mf2_rsvp">'; 328 406 $string .= self::rsvp_choice( $selected ); … … 334 412 * Render our rating select input. 335 413 * 414 * @since 3.7.0 415 * 336 416 * @access public 337 417 * … … 340 420 */ 341 421 public static function rating_select( $selected ) { 342 $string = '<label for="mf2_rating">' . __( 'Rating', 'indieweb-post-kinds' ) . '</label><br/>';422 $string = '<label for="mf2_rating">' . esc_html__( 'Rating', 'indieweb-post-kinds' ) . '</label><br/>'; 343 423 $string .= '<select name="mf2_rating" id="mf2_rating">'; 344 424 $string .= self::rating_choice( $selected ); … … 348 428 349 429 /** 430 * Keeps the Response Properties box from being saved as hidden. 431 * 432 * The editor script hides the box for kinds without a response, such as 433 * note. WordPress saves every hidden box as unticked in Screen Options 434 * whenever any box is collapsed or any Screen Options box is toggled, so 435 * the box would stay hidden for every kind afterwards. The script shows 436 * and hides it by kind instead. 437 * 438 * @since 4.0.0 439 * 440 * @param string[] $hidden IDs of the hidden meta boxes. 441 * @param WP_Screen $screen The current screen. 442 * @return string[] IDs of the hidden meta boxes. 443 */ 444 public static function hidden_meta_boxes( $hidden, $screen ) { 445 if ( ! is_array( $hidden ) || ! $screen instanceof WP_Screen || 'post' !== $screen->id ) { 446 return $hidden; 447 } 448 return array_values( array_diff( $hidden, array( 'replybox-meta' ) ) ); 449 } 450 451 /** 350 452 * Create one or more meta boxes to be displayed on the post editor screen. 453 * 454 * @since 2.2.0 351 455 * 352 456 * @access public … … 354 458 public static function add_meta_boxes() { 355 459 add_meta_box( 356 'replybox-meta', // Unique ID 357 esc_html__( 'Response Properties', 'indieweb-post-kinds' ), // Title 358 array( static::class, 'reply_metabox' ), // Callback function 460 'replybox-meta', // Unique ID. 461 esc_html__( 'Response Properties', 'indieweb-post-kinds' ), // Title. 462 array( static::class, 'reply_metabox' ), // Callback function. 359 463 'post', 360 'kind_after_title', // Context 361 'default', // Priority 464 'kind_after_title', // Context. 465 'default', // Priority. 362 466 array( 363 467 '__block_editor_compatible_meta_box' => false, … … 370 474 * Render our reply meta box. 371 475 * 476 * @since 3.0.0 477 * 372 478 * @access public 373 479 * … … 382 488 * Process and save meta box data. 383 489 * 384 * @access public 385 * 386 * @param int $post_id Saved post ID. 387 * @param WP_Pos $post Saved post object. 490 * @since 2.2.0 491 * 492 * @access public 493 * 494 * @param int $post_id Saved post ID. 495 * @param WP_Post $post Saved post object. 388 496 */ 389 497 public static function save_post( $post_id, $post ) { … … 399 507 400 508 // Verify that the nonce is valid. 401 if ( ! wp_verify_nonce( $_POST['replykind_metabox_nonce'], 'replykind_metabox' ) ) {509 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['replykind_metabox_nonce'] ) ), 'replykind_metabox' ) ) { 402 510 return; 403 511 } … … 409 517 410 518 // Check the user's permissions. 411 if ( isset( $_POST['post_type'] ) && 'page' === $_POST['post_type']) {519 if ( 'page' === self::posted_text( 'post_type' ) ) { 412 520 if ( ! current_user_can( 'edit_page', $post_id ) ) { 413 521 return; … … 422 530 423 531 if ( isset( $_POST['mf2_start_date'] ) || isset( $_POST['mf2_start_time'] ) ) { 424 $start = build_iso8601_time( s anitize_text_field( $_POST['mf2_start_date'] ), sanitize_text_field( $_POST['mf2_start_time'] ), sanitize_text_field( $_POST['mf2_start_offset']) );532 $start = build_iso8601_time( self::posted_text( 'mf2_start_date' ), self::posted_text( 'mf2_start_time' ), self::posted_text( 'mf2_start_offset' ) ); 425 533 if ( ! $start ) { 426 534 $kind_post->delete( 'start' ); … … 430 538 } 431 539 if ( isset( $_POST['mf2_end_date'] ) || isset( $_POST['mf2_end_time'] ) ) { 432 $end = build_iso8601_time( s anitize_text_field( $_POST['mf2_end_date'] ), sanitize_text_field( $_POST['mf2_end_time'] ), sanitize_text_field( $_POST['mf2_end_offset']) );540 $end = build_iso8601_time( self::posted_text( 'mf2_end_date' ), self::posted_text( 'mf2_end_time' ), self::posted_text( 'mf2_end_offset' ) ); 433 541 if ( ! $end ) { 434 542 $kind_post->delete( 'end' ); … … 443 551 444 552 $durations = array( 445 'Y' => intval( $_POST['duration_years'] ?? ''),446 'M' => intval( $_POST['duration_months'] ?? ''),447 'D' => intval( $_POST['duration_days'] ?? ''),448 'H' => intval( $_POST['duration_hours'] ?? ''),449 'I' => intval( $_POST['duration_minutes'] ?? ''),450 'S' => intval( $_POST['duration_seconds'] ?? ''),553 'Y' => absint( self::posted_text( 'duration_years' ) ), 554 'M' => absint( self::posted_text( 'duration_months' ) ), 555 'D' => absint( self::posted_text( 'duration_days' ) ), 556 'H' => absint( self::posted_text( 'duration_hours' ) ), 557 'I' => absint( self::posted_text( 'duration_minutes' ) ), 558 'S' => absint( self::posted_text( 'duration_seconds' ) ), 451 559 ); 452 560 $duration = build_iso8601_duration( $durations ); … … 456 564 if ( $duration instanceof DateInterval ) { 457 565 $duration = date_interval_to_iso8601( $duration ); 458 error_log( wp_json_encode( $duration ) );459 566 } 460 567 } … … 465 572 } 466 573 467 $kind_post->set( 'rsvp', $_POST['mf2_rsvp'] ); 468 if ( array_key_exists( 'mf2_rating', $_POST ) && is_numeric( $_POST['mf2_rating'] ) ) { 469 $kind_post->set( 'rating', intval( $_POST['mf2_rating'] ) ); 470 } 471 472 if ( isset( $_POST['cite_published_date'] ) || isset( $_POST['published_time'] ) ) { 473 $cite['published'] = build_iso8601_time( sanitize_text_field( $_POST['cite_published_date'] ), sanitize_text_field( $_POST['cite_published_time'] ), sanitize_text_field( $_POST['cite_published_offset'] ) ); 574 if ( isset( $_POST['mf2_rsvp'] ) ) { 575 $rsvp = self::posted_text( 'mf2_rsvp' ); 576 if ( in_array( $rsvp, array( 'yes', 'no', 'maybe', 'interested', 'remote' ), true ) ) { 577 $kind_post->set( 'rsvp', $rsvp ); 578 } else { 579 $kind_post->delete( 'rsvp' ); 580 } 581 } 582 $rating = self::posted_text( 'mf2_rating' ); 583 if ( is_numeric( $rating ) ) { 584 $kind_post->set( 'rating', intval( $rating ) ); 585 } 586 587 if ( isset( $_POST['cite_published_date'] ) || isset( $_POST['cite_published_time'] ) ) { 588 $cite['published'] = build_iso8601_time( self::posted_text( 'cite_published_date' ), self::posted_text( 'cite_published_time' ), self::posted_text( 'cite_published_offset' ) ); 474 589 } 475 590 if ( isset( $_POST['cite_updated_date'] ) || isset( $_POST['cite_updated_time'] ) ) { 476 $cite['updated'] = build_iso8601_time( sanitize_text_field( $_POST['cite_updated_date'] ), sanitize_text_field( $_POST['cite_updated_time'] ), sanitize_text_field( $_POST['cite_updated_offset'] ) ); 477 } 478 $cite['summary'] = wp_kses_post( $_POST['cite_summary'] ?? '' ); 479 $cite['name'] = sanitize_text_field( $_POST['cite_name'] ?? '' ); 480 $cite['url'] = esc_url( $_POST['cite_url'] ?? '' ); 591 $cite['updated'] = build_iso8601_time( self::posted_text( 'cite_updated_date' ), self::posted_text( 'cite_updated_time' ), self::posted_text( 'cite_updated_offset' ) ); 592 } 593 // Summaries are plain text; line breaks are kept. 594 $cite['summary'] = isset( $_POST['cite_summary'] ) && is_string( $_POST['cite_summary'] ) ? sanitize_textarea_field( wp_unslash( $_POST['cite_summary'] ) ) : ''; 595 $cite['name'] = self::posted_text( 'cite_name' ); 596 $cite['url'] = self::posted_urls( 'cite_url' ); 481 597 if ( isset( $_POST['cite_tags'] ) ) { 482 $cite['category'] = array_filter( explode( ';', sanitize_text_field( $_POST['cite_tags'] ) ) ); 483 } 484 $cite['publication'] = sanitize_text_field( $_POST['cite_publication'] ?? '' ); 485 $cite['featured'] = esc_url( $_POST['cite_featured'] ?? '' ); 486 487 $author = array(); 488 $author['name'] = self::explode( sanitize_text_field( $_POST['cite_author_name'] ?? '' ) ); 489 $author['url'] = self::explode( $_POST['cite_author_url'] ?? '' ); 490 if ( is_array( $author['url'] ) ) { 491 $author['url'] = array_map( 'esc_url', $author['url'] ); 598 $cite['category'] = array_values( array_filter( array_map( 'trim', explode( ';', self::posted_text( 'cite_tags' ) ) ) ) ); 599 } 600 $cite['publication'] = self::posted_text( 'cite_publication' ); 601 $cite['featured'] = self::posted_urls( 'cite_featured' ); 602 603 // One h-card per author. Like the other fields, an empty value clears what was stored. 604 $authors = array_map( '\ParseThis\jf2_to_mf2', self::posted_authors() ); 605 if ( empty( $authors ) ) { 606 $cite['author'] = ''; 492 607 } else { 493 $author['url'] = esc_url( $author['url'] ); 494 } 495 496 $author['photo'] = self::explode( $_POST['cite_author_photo'] ?? '' ); 497 498 if ( is_array( $author['photo'] ) ) { 499 $author['photo'] = array_map( 'esc_url', $author['photo'] ); 500 } else { 501 $author['photo'] = esc_url( $author['photo'] ); 502 } 503 504 $author = array_filter( $author ); 505 if ( ! empty( $author ) ) { 506 507 $author['type'] = 'card'; 508 $cite['author'] = jf2_to_mf2( $author ); 509 } 608 $cite['author'] = 1 === count( $authors ) ? $authors[0] : $authors; 609 } 610 510 611 $kind = $kind_post->get_kind(); 511 612 $type = Kind_Taxonomy::get_kind_info( $kind, 'property' ); 512 // Make sure there is no overwrite of properties that might not be handled by the plugin 513 $fetch = $kind_post->get_cite(); 514 if ( ! $fetch ) { 515 $fetch = array(); 516 } else { 517 $fetch = array_filter( $fetch ); 518 } 613 // Make sure there is no overwrite of properties that might not be handled by the plugin. 614 $fetch = self::stored_cite_properties( $kind_post->get_cite() ); 519 615 if ( empty( $_POST['cite_media'] ) ) { 520 616 $cite = array_merge( $fetch, $cite ); … … 531 627 } 532 628 $cite = array( 'properties' => $build ); 533 // Temporary code which assumes everything except a checkin is a citation 629 // Temporary code which assumes everything except a checkin is a citation. 534 630 if ( 'checkin' === $kind ) { 535 631 $cite['type'] = array( 'h-card' ); … … 541 637 } 542 638 } 543 $kind_post->set( $type, $cite ); 639 // Values are unslashed for sanitizing; the metadata API expects them slashed. 640 $kind_post->set( $type, wp_slash( $cite ) ); 641 } 642 643 /** 644 * Returns a posted metabox field as sanitized text. 645 * 646 * Only called from save_post(), after the nonce is verified. 647 * 648 * @since 4.0.0 649 * 650 * @param string $key Field name. 651 * @return string The unslashed, sanitized value, or an empty string if it is missing. 652 */ 653 private static function posted_text( $key ) { 654 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in save_post(). 655 if ( ! isset( $_POST[ $key ] ) || ! is_string( $_POST[ $key ] ) ) { 656 return ''; 657 } 658 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in save_post(). 659 return sanitize_text_field( wp_unslash( $_POST[ $key ] ) ); 660 } 661 662 /** 663 * Returns the properties of a stored citation, keyed by property name. 664 * 665 * Saving merges these with the posted fields, so properties the metabox 666 * does not edit are kept. A citation stored as a plain URL has none. 667 * Before 4.0.0, saving merged the whole stored microformat instead, nesting 668 * it inside its own properties one level deeper on each save; those levels 669 * are flattened here, newer values winning. 670 * 671 * @since 4.0.0 672 * 673 * @param mixed $cite Citation as returned by Kind_Post::get_cite(). 674 * @return array Property values, each a list. 675 */ 676 private static function stored_cite_properties( $cite ) { 677 if ( wp_is_numeric_array( $cite ) && 1 === count( $cite ) ) { 678 $cite = $cite[0]; 679 } 680 if ( ! is_array( $cite ) || empty( $cite['properties'] ) || ! is_array( $cite['properties'] ) ) { 681 return array(); 682 } 683 return array_filter( Kind_Upgrade::flatten_properties( $cite['properties'] ) ); 684 } 685 686 /** 687 * Returns a posted metabox URL field, sanitized. 688 * 689 * Only called from save_post(), after the nonce is verified. 690 * 691 * @since 4.0.0 692 * 693 * @param string $key Field name. 694 * @return string The URL, or an empty string if it is not valid. 695 */ 696 private static function posted_urls( $key ) { 697 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in save_post(). 698 if ( ! isset( $_POST[ $key ] ) || ! is_string( $_POST[ $key ] ) ) { 699 return ''; 700 } 701 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verified in save_post(); sanitized with sanitize_url() (sanitize_text_field() would alter %-encoding). 702 return sanitize_url( trim( wp_unslash( $_POST[ $key ] ) ) ); 703 } 704 705 /** 706 * Returns the authors posted in the metabox's Author tab, as jf2 h-cards. 707 * 708 * Each field holds one value per author, separated by semicolons, so the 709 * first name goes with the first URL and the first photo, and so on. An 710 * author without a URL leaves that position empty, as in "Ann; Bob" and 711 * "; https://example.com/bob". 712 * 713 * Only called from save_post(), after the nonce is verified. 714 * 715 * @since 4.0.0 716 * 717 * @return array[] The authors, each with type 'card' and any of name, url and photo. 718 */ 719 private static function posted_authors() { 720 $fields = array(); 721 foreach ( array( 722 'name' => 'cite_author_name', 723 'url' => 'cite_author_url', 724 'photo' => 'cite_author_photo', 725 ) as $property => $key ) { 726 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verified in save_post(); each value is sanitized below. 727 $value = isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ? wp_unslash( $_POST[ $key ] ) : ''; 728 $values = array_map( 'trim', explode( ';', $value ) ); 729 730 $fields[ $property ] = array_map( 'name' === $property ? 'sanitize_text_field' : 'sanitize_url', $values ); 731 } 732 733 $authors = array(); 734 $count = max( array_map( 'count', $fields ) ); 735 for ( $i = 0; $i < $count; $i++ ) { 736 $author = array(); 737 foreach ( $fields as $property => $values ) { 738 if ( isset( $values[ $i ] ) && '' !== $values[ $i ] ) { 739 $author[ $property ] = $values[ $i ]; 740 } 741 } 742 if ( $author ) { 743 $authors[] = array_merge( array( 'type' => 'card' ), $author ); 744 } 745 } 746 return $authors; 544 747 } 545 748 546 749 /** 547 750 * Function to handle saving our kind data upon post status transition. 751 * 752 * @since 2.2.0 548 753 * 549 754 * @access public -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-plugins.php
r2973430 r3738687 1 1 <?php 2 3 2 /** 4 * Post Kind Plugins Class3 * Kind_Plugins class. 5 4 * 6 * Custom Functions for Specific Other Pugins 5 * @package Post_Kinds 6 * @since 2.5.2 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Integrations with other plugins, such as Micropub and ActivityPub. 7 13 * 8 * @ package Post Kinds14 * @since 2.5.2 9 15 */ 10 16 class Kind_Plugins { 11 17 12 18 /** 19 * WP-Cron hook that retries parsing a Micropub citation in the background. 20 * 21 * @var string 22 */ 23 const ENRICH_EVENT = 'post_kinds_enrich_citation'; 24 25 /** 26 * How many times a citation is parsed after temporary failures, counting the 27 * attempt during the request. 28 * 29 * @var int 30 */ 31 const ENRICH_ATTEMPTS = 3; 32 33 /** 34 * Post meta holding the log of citation parsing outcomes for a post. 35 * 36 * @var string 37 */ 38 const LOG_META = '_kind_citation_log'; 39 40 /** 41 * Post meta holding the post's citation status: retrying or failed. It is 42 * removed when no citation is retrying or failed. 43 * 44 * @var string 45 */ 46 const STATUS_META = '_kind_citation_status'; 47 48 /** 49 * How many log entries are kept per post. 50 * 51 * @var int 52 */ 53 const LOG_LIMIT = 20; 54 55 /** 56 * Citation URLs from the current Micropub request that could not be parsed, 57 * as arrays of property, URL and WP_Error, waiting for the post ID. 58 * 59 * @var array[] 60 */ 61 private static $pending = array(); 62 63 /** 13 64 * Initialize our plugin integrations. 14 65 * 66 * @since 2.5.2 67 * 15 68 * @access public 16 69 */ 17 70 public static function init() { 18 // Set Post Kind for Micropub Inputs. 71 // Set the post kind for Micropub posts before they are inserted (Micropub 2.1.0 72 // and later), with after_micropub as a fallback. 73 add_filter( 'micropub_tax_input', array( static::class, 'micropub_tax_input' ), 10, 2 ); 19 74 add_action( 'after_micropub', array( static::class, 'micropub_set_kind' ), 9, 2 ); 20 75 add_action( 'after_micropub', array( static::class, 'post_formats' ), 11, 2 ); 21 76 add_filter( 'before_micropub', array( static::class, 'micropub_parse' ), 11 ); 77 add_action( 'after_micropub', array( static::class, 'schedule_enrichment' ), 20, 2 ); 78 add_action( self::ENRICH_EVENT, array( static::class, 'enrich_citation' ), 10, 4 ); 79 // Advertise the enabled kinds to Micropub clients (Micropub 1.4 and later). 80 add_filter( 'micropub_query', array( static::class, 'micropub_query' ), 10, 2 ); 22 81 add_filter( 'tempus_widget_post_title', array( static::class, 'tempus_widget_post_title' ), 10, 2 ); 23 82 // Override Post Type in Semantic Linkbacks. 24 83 add_filter( 'semantic_linkbacks_post_type', array( static::class, 'semantic_post_type' ), 11, 2 ); 25 84 26 // Remove the Automatic Post Generation that the Micropub Plugin Offers 27 if ( class_exists( 'Micropub_Render' ) ) { 28 if ( has_filter( 'micropub_post_content', array( 'Micropub_Render', 'generate_post_content' ) ) ) { 29 remove_filter( 'micropub_post_content', array( 'Micropub_Render', 'generate_post_content' ), 1, 2 ); 30 } 31 } elseif ( class_exists( 'Micropub_Plugin' ) ) { 32 if ( has_filter( 'micropub_post_content', array( 'Micropub_Plugin', 'generate_post_content' ) ) ) { 33 remove_filter( 'micropub_post_content', array( 'Micropub_Plugin', 'generate_post_content' ), 1, 2 ); 34 } 35 } 36 37 // Hum Compatibility Filters 85 // Post Kinds displays the response itself, so turn off Micropub's dynamic 86 // rendering of Micropub posts (Micropub 2.4.0 and later). 87 add_filter( 'micropub_dynamic_render', '__return_false' ); 88 89 // Hum Compatibility Filters. 38 90 add_action( 'hum_local_types', array( static::class, 'hum_local_types' ), 11 ); 39 91 add_action( 'hum_type_prefix', array( static::class, 'hum_type_prefix' ), 11, 2 ); 40 92 41 // ActivityPub Filters 93 // ActivityPub Filters. 42 94 add_filter( 'activitypub_post', array( static::class, 'activitypub_post' ), 12 ); 43 95 } … … 47 99 * Construct a title for the Tempus Widget link. 48 100 * 101 * @since 3.5.4 102 * 49 103 * @access public 50 104 * 51 * @param string $title The Original Title.52 * @param WP_Post $post Post object.105 * @param string $title The Original Title. 106 * @param WP_Post $post Post object. 53 107 * @return string 54 108 */ … … 58 112 59 113 /** 60 * 61 * @param array $post_array 114 * Sets the ActivityPub object type based on the post kind. 115 * 116 * @since 3.6.2 117 * 118 * @param array $post_array ActivityPub object array. 62 119 * 63 120 * @return array … … 93 150 } 94 151 152 /** 153 * Adds the kinds' shortlink type codes to the Hum plugin. 154 * 155 * @since 3.4.0 156 * 157 * @param string[] $types Local type codes. 158 * @return string[] Local type codes. 159 */ 95 160 public static function hum_local_types( $types ) { 96 161 // http://tantek.pbworks.com/w/page/21743973/Whistle#design - Some of the uses are modified based on design considerations noted. 97 $types[] = 'f'; // Favorited, Likes, etc 98 $types[] = 'e'; // Events 99 $types[] = 'g'; // Geo Checkin 100 $types[] = 'h'; // Link 101 $types[] = 'm'; // Metric 102 $types[] = 'q'; // Question 103 $types[] = 'r'; // Review 104 $types[] = 'x'; // Experience 105 $types[] = 'u'; // Status Update 162 $types[] = 'f'; // Favorited, Likes, etc. 163 $types[] = 'e'; // Events. 164 $types[] = 'g'; // Geo Checkin. 165 $types[] = 'h'; // Link. 166 $types[] = 'm'; // Metric. 167 $types[] = 'q'; // Question. 168 $types[] = 'r'; // Review. 169 $types[] = 'x'; // Experience. 170 $types[] = 'u'; // Status Update. 106 171 return $types; 107 172 } 108 173 174 /** 175 * Uses the kind's shortlink code as a post's Hum type prefix. 176 * 177 * @since 3.4.0 178 * 179 * @param string $prefix Type prefix. 180 * @param int $post_id Post ID. 181 * @return string The kind's shortlink code, or the prefix if it has none. 182 */ 109 183 public static function hum_type_prefix( $prefix, $post_id ) { 110 184 $post_type = get_post_type( $post_id ); … … 124 198 * Replaces need for replacing the entire excerpt. 125 199 * 200 * @since 2.5.2 201 * 126 202 * @access public 127 203 * … … 135 211 136 212 /** 137 * Take mf2 properties and set a post kind. 138 * Implements Post Type Discovery https://www.w3.org/TR/post-type-discovery/ 139 * 140 * @param array $input Micropub Request in JSON. 141 * @param array $wp_args Arguments passed to insert or update posts. 213 * Returns the post kind for an mf2 object, using Post Type Discovery. 214 * Implements https://www.w3.org/TR/post-type-discovery/ via Parse This. 215 * 216 * @since 4.0.0 217 * 218 * @param array $mf2 mf2 object, such as a Micropub request. 219 * @return string Registered kind slug, or an empty string if none applies. 220 */ 221 public static function discover_kind( $mf2 ) { 222 if ( ! is_array( $mf2 ) || empty( $mf2['properties'] ) || ! function_exists( '\\ParseThis\\post_type_discovery' ) ) { 223 return ''; 224 } 225 $type = \ParseThis\post_type_discovery( \ParseThis\mf2_to_jf2( $mf2 ) ); 226 return ( $type && Kind_Taxonomy::get_post_kind_info( $type ) ) ? $type : ''; 227 } 228 229 /** 230 * Sets the kind of a Micropub post in the taxonomy input, so the post is 231 * inserted with its kind rather than the default term. Everything that runs 232 * during the insert, such as the publish transition, post formats and other 233 * plugins' publish hooks, then sees the right kind. 234 * 235 * Hooked to Micropub's micropub_tax_input filter (Micropub 2.1.0 and later), 236 * which is applied when a post is created. 237 * 238 * @since 4.0.0 239 * 240 * @param array|null $tax_input Taxonomy input for the new post. 241 * @param array $input Micropub request. 242 * @return array|null Taxonomy input, with the kind added. 243 */ 244 public static function micropub_tax_input( $tax_input, $input ) { 245 $kind = self::discover_kind( $input ); 246 if ( ! $kind ) { 247 return $tax_input; 248 } 249 if ( ! is_array( $tax_input ) ) { 250 $tax_input = array(); 251 } 252 if ( empty( $tax_input['kind'] ) ) { 253 $tax_input['kind'] = array( $kind ); 254 } 255 return $tax_input; 256 } 257 258 /** 259 * Sets the kind of a Micropub post after it is created, if it does not 260 * already have it. 261 * 262 * The kind is normally set before the insert (see micropub_tax_input()). This 263 * covers Micropub versions before 2.1.0, and users who cannot assign kind 264 * terms, for whom WordPress ignores the taxonomy input. 265 * 266 * @since 2.5.2 267 * 268 * @param array $input Micropub request. 269 * @param array|null $wp_args Arguments of the created or updated post; null for queries. 142 270 */ 143 271 public static function micropub_set_kind( $input, $wp_args ) { 144 // Only continue if create or update 145 if ( ! $wp_args ) { 272 if ( empty( $wp_args['ID'] ) ) { 146 273 return; 147 274 } 148 $type = post_type_discovery( mf2_to_jf2( $input ) ); 149 if ( ! empty( $type ) ) { 150 set_post_kind( $wp_args['ID'], $type ); 151 } 152 } 153 154 /** 155 * Set our post formats. 275 $post_id = (int) $wp_args['ID']; 276 if ( isset( $input['action'] ) && 'update' === $input['action'] ) { 277 // Only recalculate when the update changed a property that decides the 278 // kind, so a kind chosen in the editor survives unrelated updates. 279 if ( ! self::update_changes_kind( $input ) ) { 280 return; 281 } 282 $kind = self::discover_kind( self::stored_mf2( $post_id ) ); 283 } else { 284 $kind = self::discover_kind( $input ); 285 } 286 if ( $kind && get_post_kind_slug( $post_id ) !== $kind ) { 287 self::set_kind_without_moving_properties( $post_id, $kind ); 288 } 289 } 290 291 /** 292 * Returns the properties that decide a post's kind: each registered kind's 293 * property, plus rsvp, which Post Type Discovery checks first. 294 * 295 * @since 4.0.0 296 * 297 * @return string[] Property names. 298 */ 299 public static function kind_properties() { 300 $properties = array( 'rsvp' ); 301 foreach ( Kind_Taxonomy::get_kind_list() as $kind ) { 302 $property = Kind_Taxonomy::get_kind_info( $kind, 'property' ); 303 if ( $property ) { 304 $properties[] = $property; 305 } 306 } 307 return array_values( array_unique( $properties ) ); 308 } 309 310 /** 311 * Whether a Micropub update request replaces, adds or deletes a property 312 * that decides the kind. 313 * 314 * @since 4.0.0 315 * 316 * @param array $input Micropub update request. 317 * @return bool 318 */ 319 public static function update_changes_kind( $input ) { 320 $changed = array(); 321 foreach ( array( 'replace', 'add' ) as $operation ) { 322 if ( isset( $input[ $operation ] ) && is_array( $input[ $operation ] ) ) { 323 $changed = array_merge( $changed, array_keys( $input[ $operation ] ) ); 324 } 325 } 326 if ( isset( $input['delete'] ) && is_array( $input['delete'] ) ) { 327 // Either a list of property names, or an object of property => values. 328 $changed = array_merge( $changed, wp_is_numeric_array( $input['delete'] ) ? $input['delete'] : array_keys( $input['delete'] ) ); 329 } 330 return (bool) array_intersect( $changed, self::kind_properties() ); 331 } 332 333 /** 334 * Builds the mf2 for a post from what is stored: the mf2_ post meta, plus the 335 * title and content, which Post Type Discovery uses to tell articles from notes. 336 * 337 * @since 4.0.0 338 * 339 * @param int $post_id Post ID. 340 * @return array mf2 object. 341 */ 342 public static function stored_mf2( $post_id ) { 343 $properties = array(); 344 foreach ( get_post_meta( $post_id ) as $key => $values ) { 345 if ( 0 !== strpos( $key, 'mf2_' ) || 'mf2_type' === $key ) { 346 continue; 347 } 348 $value = maybe_unserialize( $values[0] ); 349 if ( '' === $value || array() === $value ) { 350 continue; 351 } 352 $properties[ substr( $key, 4 ) ] = is_array( $value ) ? $value : array( $value ); 353 } 354 $post = get_post( $post_id ); 355 if ( $post && '' !== $post->post_title ) { 356 $properties['name'] = array( $post->post_title ); 357 } 358 if ( $post && '' !== $post->post_content ) { 359 $properties['content'] = array( $post->post_content ); 360 } 361 return array( 362 'type' => array( 'h-entry' ), 363 'properties' => $properties, 364 ); 365 } 366 367 /** 368 * Sets a post's kind without moving property values between properties. 369 * 370 * When the kind changes in the editor, Kind_Metabox::change_kind() moves the 371 * old kind's property to the new one (a like-of becomes an in-reply-to, for 372 * example). After a Micropub update the properties are already where the 373 * client put them, so that is suspended here. 374 * 375 * @since 4.0.0 376 * 377 * @param int $post_id Post ID. 378 * @param string $kind Kind slug. 379 */ 380 private static function set_kind_without_moving_properties( $post_id, $kind ) { 381 $moving = has_action( 'change_kind', array( 'Kind_Metabox', 'change_kind' ) ); 382 if ( false !== $moving ) { 383 remove_action( 'change_kind', array( 'Kind_Metabox', 'change_kind' ), $moving ); 384 } 385 set_post_kind( $post_id, $kind ); 386 if ( false !== $moving ) { 387 add_action( 'change_kind', array( 'Kind_Metabox', 'change_kind' ), $moving, 3 ); 388 } 389 } 390 391 /** 392 * Sets the post format of a post created or updated by Micropub to match its kind. 393 * 394 * @since 2.5.2 156 395 * 157 396 * @access public 158 397 * 159 * @param $input160 * @param $wp_args398 * @param array $input The Micropub request. 399 * @param array $wp_args Arguments passed to wp_insert_post() or wp_update_post(). 161 400 */ 162 401 public static function post_formats( $input, $wp_args ) { … … 169 408 170 409 /** 171 * Parse our micropub values. 410 * Returns the Micropub properties whose URL values are enriched into citations. 411 * 412 * @since 4.0.0 413 * 414 * @return string[] Property names. 415 */ 416 public static function citation_properties() { 417 return array( 'bookmark-of', 'like-of', 'favorite-of', 'in-reply-to', 'read-of', 'listen-of', 'watch-of' ); 418 } 419 420 /** 421 * Enriches the URLs in citation properties of a Micropub create request, or 422 * of the replace part of an update request, into h-cite objects parsed from 423 * the cited page. 424 * 425 * Follows the Micropub spec: property values are arrays, so a property whose 426 * value is not an array is left for Micropub to handle, and only string URLs 427 * inside the array are enriched. Values the client already sent as objects, 428 * such as an h-cite, are kept as sent. 429 * 430 * @since 2.7.5 172 431 * 173 432 * @access public 174 433 * 175 * @param array $input Array of inputs to parse.176 * @return mixed434 * @param array $input Micropub request, in mf2 JSON. 435 * @return array The request, with citation URLs enriched where possible. 177 436 */ 178 437 public static function micropub_parse( $input ) { 179 if ( ! $input ) { 438 self::$pending = array(); 439 // Queries (q) are left as is. 440 if ( ! is_array( $input ) || isset( $input['q'] ) ) { 180 441 return $input; 181 442 } 182 // q indicates a get query 183 if ( isset( $input['q'] ) ) { 443 if ( ! class_exists( '\\ParseThis\\Parser' ) || ! self::enrichment_enabled( $input ) ) { 184 444 return $input; 185 445 } 186 if ( ! isset( $input['properties'] ) ) { 187 return $input; 188 } 189 $parsed = array( 'bookmark-of', 'like-of', 'favorite-of', 'in-reply-to', 'read-of', 'listen-of', 'watch-of' ); 190 foreach ( $input['properties'] as $property => $value ) { 191 if ( in_array( $property, $parsed, true ) ) { 192 if ( wp_is_numeric_array( $value ) ) { 193 foreach ( $value as $i => $v ) { 194 if ( wp_http_validate_url( $v ) ) { 195 $parse = new Parse_This( $v ); 196 $fetch = $parse->fetch(); 197 if ( ! is_wp_error( $fetch ) ) { 198 $parse->parse(); 199 $jf2 = $parse->get(); 200 // Entries become citations 201 if ( 'entry' === $jf2['type'] ) { 202 $jf2['type'] = 'cite'; 203 } 204 $mf2 = jf2_to_mf2( $jf2 ); 205 $input['properties'][ $property ][ $i ] = $mf2; 206 } else { 207 error_log( wp_json_encode( $fetch ) ); // phpcs:ignore 208 } 209 } 210 } 211 } elseif ( isset( $value['url'] ) && wp_http_validate_url( $value['url'] ) ) { 212 $parse = new Parse_This( $value['url'] ); 213 $fetch = $parse - fetch(); 214 if ( ! is_wp_error( $fetch ) ) { 215 $parse->parse(); 216 $input['properties'][ $property ] = array_merge( $value, jf2_to_mf2( $parse->get() ) ); 446 // Create requests carry properties. Update requests can replace citation 447 // properties; Micropub only allows adding categories, syndication and media. 448 if ( isset( $input['properties'] ) && is_array( $input['properties'] ) ) { 449 $input['properties'] = self::enrich_properties( $input['properties'] ); 450 } 451 if ( isset( $input['action'] ) && 'update' === $input['action'] && isset( $input['replace'] ) && is_array( $input['replace'] ) ) { 452 $input['replace'] = self::enrich_properties( $input['replace'] ); 453 } 454 return $input; 455 } 456 457 /** 458 * Enriches the citation URLs in a set of Micropub properties. 459 * 460 * @since 4.0.0 461 * 462 * @param array $properties Properties, keyed by name, each an array of values. 463 * @return array The properties, with citation URLs enriched where possible. 464 */ 465 private static function enrich_properties( $properties ) { 466 foreach ( self::citation_properties() as $property ) { 467 if ( empty( $properties[ $property ] ) || ! wp_is_numeric_array( $properties[ $property ] ) ) { 468 continue; 469 } 470 foreach ( $properties[ $property ] as $i => $value ) { 471 if ( ! is_string( $value ) || ! wp_http_validate_url( $value ) ) { 472 continue; 473 } 474 // Keep the client waiting only briefly: a short timeout and no follow-up 475 // requests. Failures are logged, and retried in the background where 476 // worthwhile, once the post exists (see schedule_enrichment()). 477 $cite = self::parse_citation( $value, self::request_parse_args() ); 478 if ( is_wp_error( $cite ) ) { 479 self::report_failure( $value, $cite ); 480 self::$pending[] = array( $property, $value, $cite ); 481 continue; 482 } 483 $properties[ $property ][ $i ] = $cite; 484 } 485 } 486 return $properties; 487 } 488 489 /** 490 * Fetches and parses a URL into an mf2 citation. 491 * 492 * @since 4.0.0 493 * 494 * @access public 495 * 496 * @param string $url URL of the cited page. 497 * @param array $args { 498 * Optional. Limits for this parse. 499 * 500 * @type int $timeout HTTP timeout in seconds. Default: Parse This's own (15). 501 * @type int $max_requests Follow-up requests allowed. Default: Parse This's own (10). 502 * } 503 * @return array|WP_Error mf2 citation, or WP_Error if the page could not be fetched or parsed. 504 */ 505 public static function parse_citation( $url, $args = array() ) { 506 $timeout = isset( $args['timeout'] ) ? (int) $args['timeout'] : 0; 507 $max_requests = isset( $args['max_requests'] ) ? (int) $args['max_requests'] : null; 508 $limit_timeout = static function ( $request ) use ( $timeout ) { 509 $request['timeout'] = isset( $request['timeout'] ) ? min( (float) $request['timeout'], $timeout ) : $timeout; 510 return $request; 511 }; 512 $limit_budget = static function () use ( $max_requests ) { 513 return $max_requests; 514 }; 515 if ( $timeout > 0 ) { 516 add_filter( 'http_request_args', $limit_timeout, 99 ); 517 } 518 if ( null !== $max_requests ) { 519 add_filter( 'parse_this_max_requests', $limit_budget, 99 ); 520 } 521 try { 522 $parse = new \ParseThis\Parser( $url ); 523 $fetch = $parse->fetch(); 524 if ( is_wp_error( $fetch ) ) { 525 return $fetch; 526 } 527 // Keep nested objects in place rather than moving them to refs. 528 $parse->parse( array( 'references' => false ) ); 529 $jf2 = $parse->get(); 530 } finally { 531 remove_filter( 'http_request_args', $limit_timeout, 99 ); 532 remove_filter( 'parse_this_max_requests', $limit_budget, 99 ); 533 } 534 if ( ! is_array( $jf2 ) || empty( $jf2 ) ) { 535 return new WP_Error( 'empty', __( 'Nothing could be parsed from the URL.', 'indieweb-post-kinds' ) ); 536 } 537 // Content Parse This did not recognize is returned whole under raw. 538 unset( $jf2['raw'] ); 539 $type = isset( $jf2['type'] ) ? $jf2['type'] : ''; 540 // A page that parses as a feed (a home page, for example) is cited as the 541 // page itself, not its entries. 542 if ( 'feed' === $type ) { 543 $jf2 = array_intersect_key( $jf2, array_flip( array( 'name', 'url', 'author', 'summary', 'photo' ) ) ); 544 } 545 $jf2 = self::clean_citation( $jf2 ); 546 // Entries and feeds become citations. Other types, such as an h-event for 547 // an RSVP, are kept. 548 $jf2['type'] = in_array( $type, array( '', 'entry', 'feed' ), true ) ? 'cite' : $type; 549 return \ParseThis\jf2_to_mf2( $jf2 ); 550 } 551 552 /** 553 * Whether to enrich citation URLs in Micropub requests. 554 * 555 * @since 4.0.0 556 * 557 * @param array|null $input Micropub request, or null outside a request. 558 * @return bool 559 */ 560 public static function enrichment_enabled( $input = null ) { 561 /** 562 * Filters whether Post Kinds enriches citation URLs in Micropub requests 563 * (in-reply-to, like-of, and so on) with data parsed from the cited page. 564 * 565 * Return false to store citations exactly as the client sent them. 566 * 567 * @since 4.0.0 568 * 569 * @param bool $enabled Whether to enrich citations. Default true. 570 * @param array|null $input Micropub request, or null when a background retry runs. 571 */ 572 return (bool) apply_filters( 'post_kinds_micropub_enrich', true, $input ); 573 } 574 575 /** 576 * Returns the limits for parsing citations during a Micropub request. 577 * 578 * @since 4.0.0 579 * 580 * @return array Arguments for parse_citation(). 581 */ 582 public static function request_parse_args() { 583 /** 584 * Filters the HTTP timeout, in seconds, for parsing a citation while a 585 * Micropub client waits. Citations that time out are retried in the 586 * background. 587 * 588 * @since 4.0.0 589 * 590 * @param int $timeout Timeout in seconds. Default 5. 591 */ 592 $timeout = (int) apply_filters( 'post_kinds_micropub_parse_timeout', 5 ); 593 return array( 594 'timeout' => max( 1, $timeout ), 595 'max_requests' => 0, 596 ); 597 } 598 599 /** 600 * Classifies a parse failure to decide whether to retry it. 601 * 602 * @since 4.0.0 603 * 604 * @param WP_Error $error Parse failure. 605 * @return string One of: 606 * 'temporary' A connection problem or timeout, a server error or rate limiting. 607 * 'unavailable' The page was missing or refused (HTTP 404, 401 or 403), which 608 * may be an outage or a configuration problem on the cited site. 609 * 'permanent' Anything else, such as content that cannot be parsed. 610 */ 611 public static function failure_type( $error ) { 612 if ( 'http_request_failed' === $error->get_error_code() ) { 613 return 'temporary'; 614 } 615 $code = self::failure_response_code( $error ); 616 if ( 429 === $code || $code >= 500 ) { 617 return 'temporary'; 618 } 619 if ( in_array( $code, array( 401, 403, 404 ), true ) ) { 620 return 'unavailable'; 621 } 622 return 'permanent'; 623 } 624 625 /** 626 * Returns the HTTP status code of a parse failure, if there was one. 627 * 628 * @since 4.0.0 629 * 630 * @param WP_Error $error Parse failure. 631 * @return int HTTP status code, or 0. 632 */ 633 private static function failure_response_code( $error ) { 634 $data = $error->get_error_data(); 635 return is_array( $data ) && isset( $data['response_code'] ) ? (int) $data['response_code'] : 0; 636 } 637 638 /** 639 * Returns how long to wait before retrying a failed citation, if at all. 640 * 641 * Temporary failures are retried after a minute, then after 15 minutes, up 642 * to ENRICH_ATTEMPTS attempts in all. A missing or refused page is retried 643 * once, after some hours, in case the cited site was down or misconfigured. 644 * Other failures are not retried. 645 * 646 * @since 4.0.0 647 * 648 * @param int $post_id Post ID. 649 * @param string $url Cited URL. 650 * @param WP_Error $error The failure. 651 * @param int $attempt Which attempt failed. 652 * @return int Seconds to wait, or 0 for no retry. 653 */ 654 public static function retry_delay( $post_id, $url, $error, $attempt ) { 655 switch ( self::failure_type( $error ) ) { 656 case 'temporary': 657 if ( $attempt >= self::ENRICH_ATTEMPTS ) { 658 return 0; 659 } 660 return 1 === $attempt ? MINUTE_IN_SECONDS : 15 * MINUTE_IN_SECONDS; 661 case 'unavailable': 662 // Only once per citation. 663 foreach ( self::get_citation_log( $post_id ) as $entry ) { 664 if ( $url === $entry['url'] && 'unavailable' === $entry['type'] ) { 665 return 0; 217 666 } 218 667 } 219 } 220 } 221 return $input; 668 /** 669 * Filters how long to wait before retrying, once, a citation whose page 670 * was missing or refused (HTTP 404, 401 or 403). 671 * 672 * @since 4.0.0 673 * 674 * @param int $delay Delay in seconds. Default 6 hours. 675 * @param string $url Cited URL. 676 * @param WP_Error $error The failure. 677 */ 678 return max( 0, (int) apply_filters( 'post_kinds_micropub_unavailable_retry_delay', 6 * HOUR_IN_SECONDS, $url, $error ) ); 679 default: 680 return 0; 681 } 682 } 683 684 /** 685 * Reports a citation that could not be parsed. Only shown when WP_DEBUG is on. 686 * 687 * @since 4.0.0 688 * 689 * @param string $url Cited URL. 690 * @param WP_Error $error Parse failure. 691 */ 692 public static function report_failure( $url, $error ) { 693 wp_trigger_error( 694 __METHOD__, 695 sprintf( 'Could not parse %1$s for a Micropub citation: %2$s', esc_url( $url ), esc_html( $error->get_error_message() ) ) 696 ); 697 } 698 699 /** 700 * Logs and, where worthwhile, schedules a retry for citations that could not 701 * be parsed during a Micropub create or update request, once the post ID is known. 702 * 703 * @since 4.0.0 704 * 705 * @param array $input Micropub request. 706 * @param array|null $wp_args Arguments of the created or updated post. 707 */ 708 public static function schedule_enrichment( $input, $wp_args ) { 709 $pending = self::$pending; 710 self::$pending = array(); 711 if ( empty( $pending ) || empty( $wp_args['ID'] ) ) { 712 return; 713 } 714 foreach ( $pending as $citation ) { 715 self::handle_failure( (int) $wp_args['ID'], $citation[0], $citation[1], 1, $citation[2] ); 716 } 717 } 718 719 /** 720 * Logs a failed attempt and schedules a retry if one is worthwhile. 721 * 722 * @since 4.0.0 723 * 724 * @param int $post_id Post ID. 725 * @param string $property Micropub property, such as in-reply-to. 726 * @param string $url Cited URL. 727 * @param int $attempt Which attempt failed. 728 * @param WP_Error $error The failure. 729 */ 730 private static function handle_failure( $post_id, $property, $url, $attempt, $error ) { 731 // Work out the delay before logging, so the log holds only earlier failures. 732 $delay = self::retry_delay( $post_id, $url, $error, $attempt ); 733 $retry_at = $delay ? time() + $delay : 0; 734 self::log_outcome( $post_id, $property, $url, $attempt, $error, $retry_at ); 735 if ( $retry_at ) { 736 $args = array( $post_id, $property, $url, $attempt + 1 ); 737 if ( ! wp_next_scheduled( self::ENRICH_EVENT, $args ) ) { 738 wp_schedule_single_event( $retry_at, self::ENRICH_EVENT, $args ); 739 } 740 } 741 } 742 743 /** 744 * Parses a Micropub citation in the background and stores the result. 745 * 746 * The citation is only replaced if the post still holds the URL as it was 747 * stored, so later edits are not overwritten. 748 * 749 * @since 4.0.0 750 * 751 * @param int $post_id Post ID. 752 * @param string $property Micropub property, such as in-reply-to. 753 * @param string $url Cited URL. 754 * @param int $attempt Which attempt this is. 755 */ 756 public static function enrich_citation( $post_id, $property, $url, $attempt ) { 757 if ( ! get_post( $post_id ) || ! class_exists( '\\ParseThis\\Parser' ) || ! self::enrichment_enabled() ) { 758 return; 759 } 760 $key = 'mf2_' . $property; 761 $values = get_post_meta( $post_id, $key, true ); 762 $index = is_array( $values ) ? array_search( $url, $values, true ) : false; 763 if ( false === $index ) { 764 self::log_outcome( $post_id, $property, $url, $attempt, 'skipped', 0 ); 765 return; 766 } 767 $cite = self::parse_citation( $url ); 768 if ( is_wp_error( $cite ) ) { 769 self::report_failure( $url, $cite ); 770 self::handle_failure( $post_id, $property, $url, $attempt, $cite ); 771 return; 772 } 773 $values[ $index ] = $cite; 774 update_post_meta( $post_id, $key, $values ); 775 self::log_outcome( $post_id, $property, $url, $attempt, 'enriched', 0 ); 776 } 777 778 /** 779 * Returns the log of citation parsing outcomes for a post, oldest first. 780 * 781 * Each entry is an array with these keys: 782 * - time Unix timestamp of the attempt. 783 * - property Micropub property, such as in-reply-to. 784 * - url Cited URL. 785 * - attempt Attempt number; 1 is the attempt during the Micropub request. 786 * - result 'enriched', 'failed', or 'skipped' (the citation was edited since). 787 * - type For failures: 'temporary', 'unavailable' or 'permanent'. Otherwise empty. 788 * - code For failures: the WP_Error code. Otherwise empty. 789 * - response_code For failures: the HTTP status code, or 0. 790 * - message For failures: the error message. Otherwise empty. 791 * - retry Unix timestamp of the scheduled retry, or 0 if none. 792 * 793 * @since 4.0.0 794 * 795 * @param int $post_id Post ID. 796 * @return array[] Log entries. 797 */ 798 public static function get_citation_log( $post_id ) { 799 $log = get_post_meta( $post_id, self::LOG_META, true ); 800 return is_array( $log ) ? $log : array(); 801 } 802 803 /** 804 * Adds an outcome to a post's citation log and updates its citation status. 805 * 806 * @since 4.0.0 807 * 808 * @param int $post_id Post ID. 809 * @param string $property Micropub property. 810 * @param string $url Cited URL. 811 * @param int $attempt Attempt number. 812 * @param WP_Error|string $result The failure, or 'enriched' or 'skipped'. 813 * @param int $retry_at Timestamp of the scheduled retry, or 0. 814 */ 815 private static function log_outcome( $post_id, $property, $url, $attempt, $result, $retry_at ) { 816 $failed = is_wp_error( $result ); 817 $entry = array( 818 'time' => time(), 819 'property' => $property, 820 'url' => $url, 821 'attempt' => (int) $attempt, 822 'result' => $failed ? 'failed' : $result, 823 'type' => $failed ? self::failure_type( $result ) : '', 824 'code' => $failed ? $result->get_error_code() : '', 825 'response_code' => $failed ? self::failure_response_code( $result ) : 0, 826 'message' => $failed ? $result->get_error_message() : '', 827 'retry' => (int) $retry_at, 828 ); 829 $log = self::get_citation_log( $post_id ); 830 $log[] = $entry; 831 $log = array_slice( $log, -self::LOG_LIMIT ); 832 update_post_meta( $post_id, self::LOG_META, $log ); 833 834 // The status reflects the latest outcome for each cited URL. 835 $latest = array(); 836 foreach ( $log as $item ) { 837 $latest[ $item['url'] ] = $item; 838 } 839 $status = ''; 840 foreach ( $latest as $item ) { 841 if ( 'failed' !== $item['result'] ) { 842 continue; 843 } 844 if ( ! $item['retry'] ) { 845 $status = 'failed'; 846 break; 847 } 848 $status = 'retrying'; 849 } 850 if ( $status ) { 851 update_post_meta( $post_id, self::STATUS_META, $status ); 852 } else { 853 delete_post_meta( $post_id, self::STATUS_META ); 854 } 855 856 /** 857 * Fires after each attempt to parse a Micropub citation. 858 * 859 * @since 4.0.0 860 * 861 * @param array $entry The log entry. See Kind_Plugins::get_citation_log(). 862 * @param int $post_id Post ID. 863 */ 864 do_action( 'post_kinds_citation_enrichment', $entry, $post_id ); 865 } 866 867 /** 868 * Removes Parse This bookkeeping from parsed jf2, recursively. 869 * 870 * Keys starting with an underscore (_code, _links, _alternate, 871 * _source_format, ...), post-type and refs are not microformats properties, 872 * and would otherwise be stored in post meta and returned by Micropub's 873 * q=source. 874 * 875 * @since 4.0.0 876 * 877 * @access public 878 * 879 * @param mixed $jf2 Parsed jf2, or any value within it. 880 * @return mixed The value without bookkeeping keys. 881 */ 882 public static function clean_citation( $jf2 ) { 883 if ( ! is_array( $jf2 ) ) { 884 return $jf2; 885 } 886 foreach ( array_keys( $jf2 ) as $key ) { 887 if ( is_string( $key ) && ( 0 === strpos( $key, '_' ) || in_array( $key, array( 'post-type', 'refs' ), true ) ) ) { 888 unset( $jf2[ $key ] ); 889 } elseif ( is_array( $jf2[ $key ] ) ) { 890 $jf2[ $key ] = self::clean_citation( $jf2[ $key ] ); 891 } 892 } 893 return $jf2; 894 } 895 /** 896 * Adds the enabled kinds to Micropub's configuration query, using the 897 * post-types extension (Query for Supported Vocabulary), so clients only 898 * offer the kinds this site uses. 899 * 900 * @since 4.0.0 901 * 902 * @see https://indieweb.org/Micropub-extensions#Query_for_Supported_Vocabulary 903 * 904 * @param array|mixed $response Micropub query response. 905 * @param array $input Micropub query. 906 * @return array|mixed The response, with post-types added for q=config. 907 */ 908 public static function micropub_query( $response, $input ) { 909 if ( ! is_array( $response ) || ! isset( $input['q'] ) || 'config' !== $input['q'] ) { 910 return $response; 911 } 912 $types = isset( $response['post-types'] ) && is_array( $response['post-types'] ) ? $response['post-types'] : array(); 913 $known = wp_list_pluck( $types, 'type' ); 914 foreach ( self::micropub_post_types() as $type ) { 915 if ( ! in_array( $type['type'], $known, true ) ) { 916 $types[] = $type; 917 } 918 } 919 $response['post-types'] = $types; 920 return $response; 921 } 922 923 /** 924 * Returns the enabled kinds as Micropub post types. 925 * 926 * @since 4.0.0 927 * 928 * @return array[] Arrays with the keys type (the kind slug, which is its Post 929 * Type Discovery type) and name (the kind's singular name). 930 */ 931 public static function micropub_post_types() { 932 // The kinds offered in the editor: those enabled in settings, plus note, 933 // which is always available. 934 $kinds = Kind_Config::get_termslist(); 935 array_unshift( $kinds, 'note' ); 936 $types = array(); 937 foreach ( array_unique( $kinds ) as $kind ) { 938 $name = Kind_Taxonomy::get_kind_info( $kind, 'singular_name' ); 939 if ( $name ) { 940 $types[] = array( 941 'type' => $kind, 942 'name' => $name, 943 ); 944 } 945 } 946 /** 947 * Filters the post types advertised to Micropub clients in q=config. 948 * 949 * @since 4.0.0 950 * 951 * @param array[] $types Arrays with the keys type and name. 952 */ 953 return apply_filters( 'post_kinds_micropub_post_types', $types ); 222 954 } 223 955 } // End Class Kind_Plugins -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-post-widget.php
r3001401 r3738687 1 1 <?php 2 /** 3 * Kind_Post_Widget class. 4 * 5 * @package Post_Kinds 6 * @since 3.1.5 7 */ 2 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Widget that lists recent posts of selected kinds. 13 * 14 * @since 3.1.5 15 */ 3 16 class Kind_Post_Widget extends WP_Widget { 4 17 /** 5 18 * Register widget with WordPress. 19 * 20 * @since 3.1.5 6 21 */ 7 22 public function __construct() { 8 23 parent::__construct( 9 'Kind_Post_Widget', // Base ID 10 __( 'Kind Post Widget', 'indieweb-post-kinds' ), // Name 24 'Kind_Post_Widget', // Base ID. 25 __( 'Kind Post Widget', 'indieweb-post-kinds' ), // Name. 11 26 array( 12 27 'classname' => 'kind_post_widget', … … 20 35 * Front-end display of widget. 21 36 * 37 * @since 3.1.5 38 * 22 39 * @see WP_Widget::widget() 23 40 * … … 26 43 */ 27 44 public function widget( $args, $instance ) { 28 /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ 29 $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ); 30 31 $kind = $instance['kind'] ?? 'note'; 32 // phpcs:ignore 33 echo $args['before_widget']; 34 if ( ! empty( $instance['title'] ) ) { 35 echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore 36 } 37 $transient = get_transient( 'kind_post_widget' ); 38 if ( false === $transient ) { 39 $query = array( 40 'tax_query' => array( 41 array( 42 'taxonomy' => 'kind', 43 'field' => 'slug', 44 'terms' => $kind, 45 ), 45 $kind = $instance['kind'] ?? 'note'; 46 $number = absint( $instance['number'] ?? 5 ); 47 $posts = get_posts( 48 array( 49 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Kinds are a taxonomy; this is the indexed way to select them. 50 'tax_query' => array( 51 array( 52 'taxonomy' => 'kind', 53 'field' => 'slug', 54 'terms' => $kind, 46 55 ), 47 'numberposts' => $instance['number'] ?? 5, 48 ); 49 $posts = get_posts( $query ); 56 ), 57 'numberposts' => $number ? $number : 5, 58 'no_found_rows' => true, 59 'ignore_sticky_posts' => true, 60 ) 61 ); 62 // Like core's Recent Posts widget, show nothing when there are no posts. 63 // WP_Query caches the result in the object cache, so no transient is needed. 64 if ( empty( $posts ) ) { 65 return; 50 66 } 51 67 52 if ( 0 === count( $posts ) ) { 53 return; 68 /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ 69 $title = apply_filters( 'widget_title', $instance['title'] ?? '', $instance, $this->id_base ); 70 71 echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget wrapper markup registered by the theme. 72 if ( $title ) { 73 echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Wrapper markup is the theme's; the title is escaped by core's widget_title filter. 54 74 } 55 echo '<div id="kind-posts">'; 56 if ( 0 !== count( $posts ) ) { 57 echo '<ul>'; 58 foreach ( $posts as $post ) { 59 printf( '<li>%1$s</li>', kind_get_the_link( $post ) ); // phpcs:ignore 60 } 61 echo '</ul>'; 62 } else { 63 esc_html_e( 'No Posts Found', 'indieweb-post-kinds' ); 75 echo '<div id="kind-posts"><ul>'; 76 foreach ( $posts as $post ) { 77 printf( '<li>%1$s</li>', kind_get_the_link( $post ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- kind_get_the_link() escapes its parts. 64 78 } 65 echo '</ div>';66 echo $args['after_widget']; // phpcs:ignore 79 echo '</ul></div>'; 80 echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget wrapper markup registered by the theme. 67 81 } 68 82 69 83 /** 70 84 * Sanitize widget form values as they are saved. 85 * 86 * @since 3.1.5 71 87 * 72 88 * @see WP_Widget::update() … … 78 94 */ 79 95 public function update( $new_instance, $old_instance ) { 80 array_walk_recursive( $new_instance, 'sanitize_text_field' ); 81 return $new_instance; 96 $kind = isset( $new_instance['kind'] ) && is_string( $new_instance['kind'] ) ? sanitize_key( $new_instance['kind'] ) : ''; 97 $number = isset( $new_instance['number'] ) ? absint( $new_instance['number'] ) : 0; 98 return array( 99 'title' => isset( $new_instance['title'] ) && is_string( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '', 100 'kind' => in_array( $kind, Kind_Taxonomy::get_kind_list(), true ) ? $kind : 'note', 101 'number' => $number > 0 ? $number : 5, 102 ); 82 103 } 83 104 … … 85 106 /** 86 107 * Create the form for the Widget admin 108 * 109 * @since 3.1.5 87 110 * 88 111 * @see WP_Widget::form() … … 93 116 $instance['kind'] = ( $instance['kind'] ?? 'note' ); 94 117 ?> 95 <p><label for=" title"><?php esc_html_e( 'Title: ', 'indieweb-post-kinds' ); ?></label>96 <input type="text" size="30" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?> id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"97 value="<?php echo esc_ html( $instance['title'] ?? '' ); ?>" /></p>118 <p><label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title: ', 'indieweb-post-kinds' ); ?></label> 119 <input type="text" size="30" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" 120 value="<?php echo esc_attr( $instance['title'] ?? '' ); ?>" /></p> 98 121 <select name="<?php echo esc_attr( $this->get_field_name( 'kind' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'kind' ) ); ?>"> 99 122 <?php 100 $list = get_option( 'kind_termslist', Kind_Taxonomy::get_kind_list());123 $list = Kind_Config::get_termslist(); 101 124 $list[] = 'note'; 125 $list = array_unique( $list ); 102 126 foreach ( $list as $term ) { 103 $value = Kind_Taxonomy::get_post_kind_info( $term );104 127 printf( 105 128 '<option value="%1$s" %3$s>%2$s</option>', 106 129 esc_attr( $term ), 107 Kind_Taxonomy::get_kind_info( $term, 'singular_name' ), // phpcs:ignore108 selected( $instance['kind'], $term )130 esc_html( Kind_Taxonomy::get_kind_info( $term, 'singular_name' ) ), 131 selected( $instance['kind'], $term, false ) 109 132 ); 110 printf( '%1$s %2$s<br />', Kind_Taxonomy::get_icon( $term ), esc_html( $value->singular_name ) ); //phpcs:ignore111 133 } 112 134 ?> -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-post.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Kind Post Class3 * Kind_Post class. 4 4 * 5 * @package Post Kinds 6 * Assists in retrieving/saving properties from a Post. Replaces MF2_Post by only looking for Items on Demand instead of Parsing Them initially. 5 * @package Post_Kinds 6 * @since 3.4.0 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Reads and writes the kind properties of a post. 13 * 14 * Replaces MF2_Post by looking up each property on demand instead of parsing 15 * all of them up front. 16 * 17 * @since 3.4.0 7 18 */ 8 19 class Kind_Post { 9 20 21 /** 22 * Post ID. 23 * 24 * @since 3.4.0 25 * @var int 26 */ 10 27 public $id; 11 28 29 /** 30 * Sets up the post to read and write. 31 * 32 * @since 3.4.0 33 * 34 * @param int|WP_Post|string $post Post ID, post object, or the URL of a post or attachment. 35 */ 12 36 public function __construct( $post ) { 13 37 if ( is_numeric( $post ) ) { … … 21 45 $post = $id; 22 46 } else { 23 $id = attachment_url_to_postid( $post );47 $id = self::get_attachment_id( $post ); 24 48 $this->id = $id; 25 49 $post = $id; … … 30 54 } 31 55 32 /* 33 * Check is post is attachment. 34 * 35 * @param WP_Post $post Post Object. 36 * @return boolean True if attachment. 56 /** 57 * Checks whether a post is an attachment. 58 * 59 * @since 3.4.10 60 * 61 * @param int|WP_Post $post Post ID or post object. 62 * @return bool True if the post is an attachment. 37 63 */ 38 64 private function is_attachment( $post ) { … … 41 67 42 68 43 /* 44 * Returns WP_Post Object 45 * 46 * @return WP_Post Post Object. 47 * 69 /** 70 * Returns the post object. 71 * 72 * @since 3.4.0 73 * 74 * @return WP_Post|null The post, or null if it does not exist. 48 75 */ 49 76 public function get_post() { … … 51 78 } 52 79 53 /* 54 * Returns the Post Kind. For Attachments will return the media type. 55 * 56 * @return string Post Kind. 57 * 80 /** 81 * Returns the post kind. For an attachment, returns its media type instead. 82 * 83 * @since 3.4.0 84 * 85 * @return string|false|null The kind slug ('photo', 'video' or 'audio' for an attachment), 86 * false if a post has no kind, or null for an attachment of another type. 58 87 */ 59 88 public function get_kind() { … … 73 102 } 74 103 75 /* 76 * Get Name. 77 * 78 * @return string Return name. 104 /** 105 * Returns the post's name (title). 106 * 107 * A title that is just the post ID, or an attachment title that is just its 108 * file name, does not count as a name. 109 * 110 * @since 3.4.0 111 * 112 * @return string|false The name, or false if there is none. 79 113 */ 80 114 public function get_name() { … … 102 136 } 103 137 104 /* 105 * Get Content. 106 * 107 * @return array with HTML and Plaintext Version of Content or Summary 138 /** 139 * Returns the post's content or summary (excerpt). 140 * 141 * @since 3.4.0 142 * 143 * @param string $property Either 'content' or 'summary'. 144 * @return array|false Array with the HTML ('html') and plain text ('value') versions, 145 * or false if the property is empty or not supported. 108 146 */ 109 147 public function get_html( $property ) { … … 124 162 } 125 163 126 /* 127 * Get Permalink URL 128 * 129 * @return string $url 164 /** 165 * Returns the post's permalink, or the file URL for an attachment. 166 * 167 * @since 3.4.0 168 * 169 * @return string|false The URL, or false if there is none. 130 170 */ 131 171 public function get_url() { … … 137 177 } 138 178 139 /* 140 * Get Featured Image Permalink 141 * 142 * @return string $url 179 /** 180 * Returns the URL of the post's featured image. 181 * 182 * @since 3.4.0 183 * 184 * @return string|false The URL, or false if the post has no featured image. 143 185 */ 144 186 public function get_featured() { … … 150 192 151 193 152 /* 153 * Return datetime property as a DateTime Object. 154 * 155 * @param string $property Property You Wish to Return. 156 * @return DateTimeImmutable Published Time in Local Timezone. 157 * 194 /** 195 * Returns a date property as a date object. 196 * 197 * For posts other than attachments, 'published' and 'updated' come from the 198 * post's own dates. Everything else comes from post meta. 199 * 200 * @since 3.4.0 201 * 202 * @param string $property Property to return, such as 'published' or 'start'. 203 * @return DateTimeImmutable|false The date, or false if it is missing or not a valid date. 158 204 */ 159 205 public function get_datetime_property( $property ) { … … 175 221 $datetime = $datetime[0]; 176 222 } 177 return new DateTimeImmutable( $datetime ); 178 } 179 180 /* 181 * Returns Publication. 182 * 183 * @return string 184 * 223 return kind_safe_datetime( $datetime ); 224 } 225 226 /** 227 * Returns the publication the post appeared in. 228 * 229 * For posts other than attachments, this is the site title. 230 * 231 * @since 3.4.0 232 * 233 * @return string|null The publication, or null if an attachment has none. 185 234 */ 186 235 public function get_publication() { … … 195 244 } 196 245 246 /** 247 * Returns the post's stored duration. 248 * 249 * @since 3.4.0 250 * 251 * @return DateInterval|false The duration, or false if it is missing or not valid. 252 */ 197 253 public function get_duration() { 198 254 $duration = get_post_meta( $this->id, 'mf2_duration', true ); … … 200 256 $duration = $duration[0]; 201 257 } 202 if ( $duration ) {203 return new DateInterval( $duration );204 } 205 return false;206 }207 208 /*209 * Return Categories which are a combination of Tags and Categories.210 * 211 * @return array Array of the names of Categories and Tag names.212 * 258 return kind_safe_interval( $duration ); 259 } 260 261 /** 262 * Returns the names of the post's categories and tags, combined. 263 * 264 * 'Uncategorized' is left out. 265 * 266 * @since 3.4.0 267 * 268 * @return string[] Category and tag names. 213 269 */ 214 270 public function get_categories() { 215 271 $category = array(); 216 // Get a list of categories and extract their names 272 // Get a list of categories and extract their names. 217 273 $post_categories = get_the_terms( $this->id, 'category' ); 218 274 if ( ! empty( $post_categories ) && ! is_wp_error( $post_categories ) ) { … … 220 276 } 221 277 222 // Get a list of tags and extract their names 278 // Get a list of tags and extract their names. 223 279 $post_tags = get_the_terms( $this->id, 'post_tag' ); 224 280 if ( ! empty( $post_tags ) && ! is_wp_error( $post_tags ) ) { 225 $category = array_merge( $ this->category, wp_list_pluck( $post_tags, 'name' ) );281 $category = array_merge( $category, wp_list_pluck( $post_tags, 'name' ) ); 226 282 } 227 283 if ( in_array( 'Uncategorized', $category, true ) ) { … … 232 288 233 289 /** 234 * Is prefix in string. 235 * 236 * @param string $source The source string. 237 * @param string $prefix The prefix you wish to check for in source. 238 * @return boolean The result. 290 * Checks whether a string starts with a prefix. 291 * 292 * @since 3.4.0 293 * 294 * @param string $source The source string. 295 * @param string $prefix The prefix to look for. 296 * @return bool True if the source starts with the prefix. 239 297 */ 240 298 protected static function str_prefix( $source, $prefix ) { … … 243 301 244 302 /** 245 * Returns True if Array is Multidimensional. 246 * 247 * @param array $arr array. 248 * 249 * @return boolean result 303 * Checks whether an array is multidimensional. 304 * 305 * @since 3.4.0 306 * 307 * @param array $arr The array to check. 308 * @return bool True if any element is an array. 250 309 */ 251 310 protected static function is_multi_array( $arr ) { … … 257 316 } 258 317 318 /** 319 * Unwraps single-item arrays, recursively. 320 * 321 * @since 3.4.0 322 * 323 * @param mixed $value The value to unwrap. 324 * @param bool $discard Optional. Whether to keep only the first item of a list 325 * with more than one item. Default false. 326 * @return mixed The value, with single-item arrays replaced by their item. 327 */ 259 328 protected function single_array( $value, $discard = false ) { 260 329 if ( ! is_array( $value ) ) { … … 274 343 275 344 /** 276 * Retrieve author 277 * 278 * @return boolean|array The result or false if does not exist. 345 * Returns the post's author as an h-card. 346 * 347 * For an attachment, or a post with no WordPress author, this is the author 348 * stored in post meta. 349 * 350 * @since 3.4.0 351 * 352 * @return array|string|false The author as mf2, the stored value, or false if there is none. 279 353 */ 280 354 public function get_author() { … … 284 358 return ( $author ?? false ); 285 359 } 286 // Attachments may have been uploaded by a user but may have metadata for original author 360 // Attachments may have been uploaded by a user but may have metadata for original author. 287 361 if ( 'attachment' === get_post_type( $this->id ) ) { 288 362 $author = get_post_meta( $post->ID, 'mf2_author', true ); … … 294 368 'name' => array( get_the_author_meta( 'display_name', $post->post_author ) ), 295 369 'url' => array( get_the_author_meta( 'user_url', $post->post_author ) ? get_the_author_meta( 'user_url', $post->post_author ) : get_author_posts_url( $post->post_author ) ), 296 'photo' => array( get_avatar_url( $ this->post_author ) ),370 'photo' => array( get_avatar_url( $post->post_author ) ), 297 371 ), 298 372 ); 299 373 } 300 374 301 /* 302 * Return Attached Media IDs. 303 * 304 * @param string $type audio, video, or photo. 305 * @return array Array of Media IDs. 306 * 375 /** 376 * Returns the IDs of media attached to the post. 377 * 378 * @since 3.4.0 379 * 380 * @param string $type 'audio', 'video' or 'photo'. 381 * @return int[]|false Attachment IDs, or false if the type is not supported. 307 382 */ 308 383 public function get_attached_media( $type ) { … … 318 393 } 319 394 320 /* 321 * Return Attached Photos. 322 * 323 * Looks in both attached media and the photo property. 324 * 325 * @param boolean $content If true then return empty if there are any images in content. 326 * @return array Array of Media IDs. 327 * 395 /** 396 * Returns the post's photos. 397 * 398 * Looks in attached media, the photo property and images in the content. 399 * Returns an empty array if the post has a featured image. 400 * 401 * @since 3.4.0 402 * 403 * @param bool $content Optional. Whether to return an empty array if the content 404 * has any images. Default true. 405 * @return int[]|false Attachment IDs, or false if there are none. 328 406 */ 329 407 public function get_photo( $content = true ) { 408 if ( ! $this->get_post() ) { 409 return false; 410 } 330 411 // Check if the post itself is an image attachment. 331 412 if ( wp_attachment_is( 'image', $this->id ) ) { … … 333 414 } 334 415 416 // Recorded when the post is saved; see Kind_Media_Metadata::save_post(). 335 417 $content_ids = get_post_meta( $this->id, '_content_img_ids', true ); 336 337 if ( false === $content_ids ) {338 // Check for a gallery first.339 $gallery = get_post_gallery( $this->id, false );340 if ( $gallery & array_key_exists( 'ids', $gallery ) ) {341 $content_ids = explode( ',', $gallery['ids'] );342 }343 $content_ids = array();344 $post = $this->get_post();345 $shortcode = false;346 $shortcodes = apply_filter( 'kind_photo_shortcode_exclude', array( 'vr', '360' ) );347 if ( $post->post_content ) {348 foreach ( $shortcodes as $code ) {349 if ( has_shortcode( $post->post_content, $code ) ) {350 $shortcode = true;351 }352 }353 if ( $shortcode ) {354 $content_ids = array();355 } else {356 $content_ids = array_merge( $content_ids, Kind_Media_Metadata::get_img_from_content( $post->post_content ) );357 }358 update_post_meta( $this->id, '_content_img_ids', $content_ids );359 }360 }361 418 362 419 // If there are photos in the content then end here if this is true. … … 393 450 } 394 451 395 /* 396 * Return Attached Audio. 397 * 398 * Looks in both attached media and the audio property. 399 * 400 * @param boolean $content If true then return empty if there are any audio files in content. 401 * @return array Array of Media IDs or URLs. 402 * 452 /** 453 * Returns the post's audio. 454 * 455 * Looks in attached media, the audio property and audio in the content. 456 * 457 * @since 3.4.0 458 * 459 * @param bool $content Optional. Whether to return an empty array if the content 460 * has any audio. Default true. 461 * @return array|false Attachment IDs, external URLs if no attachments were found, 462 * or false if there is no audio. 403 463 */ 404 464 public function get_audio( $content = true ) { … … 408 468 } 409 469 470 // Recorded when the post is saved; see Kind_Media_Metadata::save_post(). 410 471 $content_ids = get_post_meta( $this->id, '_content_audio_ids', true ); 411 412 if ( false === $content_ids ) {413 $post = get_post();414 if ( $post->post_content ) {415 $content_ids = Kind_Media_Metadata::get_audio_from_content( $post->post_content );416 update_post_meta( $this->id, '_content_audio_ids', $content_ids );417 }418 }419 472 420 473 // If there are ids in the content then end here if this is true. … … 431 484 $audio_ids = is_array( $audios ) ? $this->get_attachments_from_urls( $audios ) : array(); 432 485 433 // If there are ids found return them 486 // If there are ids found return them. 434 487 if ( ! empty( $audio_ids ) || ! empty( $att_ids ) || ! empty( $content_ids ) ) { 435 return array_unique( array_merge( $att_ids, $ this->get_attachments_from_urls( $audios ), $content_ids ) );488 return array_unique( array_merge( $att_ids, $audio_ids, $content_ids ) ); 436 489 } 437 490 … … 444 497 } 445 498 446 /* 447 * Return Attached Video. 448 * 449 * Looks in both attached media and the audio property. 450 * 451 * @param boolean $content If true then return empty if there are any video files in content. 452 * @return array Array of Media IDs. 453 * 499 /** 500 * Returns the post's videos. 501 * 502 * Looks in attached media, the video property and videos in the content. 503 * 504 * @since 3.4.0 505 * 506 * @param bool $content Optional. Whether to return an empty array if the content 507 * has any videos. Default true. 508 * @return int[]|false Attachment IDs, or false if there are none. 454 509 */ 455 510 public function get_video( $content = true ) { … … 458 513 return array( $this->id ); 459 514 } 515 // Recorded when the post is saved; see Kind_Media_Metadata::save_post(). 460 516 $content_ids = get_post_meta( $this->id, '_content_video_ids', true ); 461 462 if ( false === $content_ids ) {463 $post = get_post();464 if ( $post->post_content ) {465 $content_ids = Kind_Media_Metadata::get_video_from_content( $post->post_content );466 update_post_meta( $this->id, '_content_video_ids', $content_ids );467 }468 }469 517 470 518 // If there are ids in the content then end here if this is true. … … 488 536 } 489 537 538 /** 539 * Look up the attachment ID for a URL, with caching. 540 * 541 * Wraps attachment_url_to_postid(), which runs an uncached query, in the 542 * object cache. The key includes the posts last_changed value, so adding, 543 * changing or deleting any post or attachment invalidates it. Without a 544 * persistent object cache this still avoids repeat lookups in a request. 545 * 546 * @since 4.0.0 547 * 548 * @param string $url Media URL. 549 * @return int Attachment ID, or 0 if there is none. 550 */ 551 public static function get_attachment_id( $url ) { 552 if ( ! is_string( $url ) || '' === $url ) { 553 return 0; 554 } 555 $key = 'attachment_id:' . md5( $url ) . ':' . wp_cache_get_last_changed( 'posts' ); 556 $id = wp_cache_get( $key, 'post-kinds' ); 557 if ( false === $id ) { 558 $id = attachment_url_to_postid( $url ); 559 wp_cache_set( $key, $id, 'post-kinds' ); 560 } 561 return (int) $id; 562 } 563 564 /** 565 * Looks up the attachment IDs for media URLs. 566 * 567 * @since 3.4.0 568 * 569 * @param string|array $urls A URL, or a list of URLs, attachment IDs or arrays with a 'url' key. 570 * @return int[] Attachment IDs found. URLs with no attachment are left out. 571 */ 490 572 public function get_attachments_from_urls( $urls ) { 491 573 if ( is_string( $urls ) ) { 492 $attachment = attachment_url_to_postid( $urls );574 $attachment = self::get_attachment_id( $urls ); 493 575 if ( $attachment ) { 494 576 return array( $attachment ); … … 502 584 if ( is_array( $url ) ) { 503 585 if ( isset( $url['url'] ) ) { 504 $att_ids[] = attachment_url_to_postid( $url['url'] );586 $att_ids[] = self::get_attachment_id( $url['url'] ); 505 587 } 506 588 } elseif ( is_numeric( $url ) ) { 507 589 $att_ids[] = $url; 508 590 } else { 509 $att_ids[] = attachment_url_to_postid( $url );591 $att_ids[] = self::get_attachment_id( $url ); 510 592 } 511 593 } … … 515 597 516 598 /** 517 * Retrieve value 518 * 519 * @param string $key The key to retrieve. 520 * @param boolean $single Whether to return a a single value or array if there is only one value. 521 * @return mixed The result or false if does not exist. 599 * Returns a property of the post. 600 * 601 * @since 3.4.0 602 * 603 * @param string $key The property to return. 604 * @param bool $single Optional. Whether to unwrap a single value from its array. Default true. 605 * @return mixed The value, or false if the key is empty. 522 606 */ 523 607 public function get( $key, $single = true ) { … … 559 643 } 560 644 645 /** 646 * Returns the post's citation, or one property of it. 647 * 648 * For an attachment, the citation is built from the attachment itself. 649 * Citations stored as jf2 are returned as mf2. 650 * 651 * @since 3.4.0 652 * 653 * @param string|null $key Optional. Citation property to return. Default null, for the whole citation. 654 * @return mixed The citation as mf2 or a string, the property, or false if there is none. 655 */ 561 656 public function get_cite( $key = null ) { 562 657 if ( 'attachment' === get_post_type( $this->id ) ) { … … 569 664 $duration = date_interval_to_iso8601( $duration ); 570 665 } 571 $cite = jf2_to_mf2(666 $cite = \ParseThis\jf2_to_mf2( 572 667 array_filter( 573 668 array( … … 604 699 $cite = $cite[0]; 605 700 } 606 $this->delete( 'cite' ); 607 } 608 } 609 610 // If this is formatted as JF2 try to convert it to MF2 and update. 611 if ( is_array( $cite ) && ! wp_is_numeric_array( $cite ) ) { 612 $cite['type'] = 'cite'; 613 $cite = jf2_to_mf2( $cite ); 614 $property = Kind_Taxonomy::get_kind_info( $this->get_kind(), 'property' ); 615 $this->set( $property, $cite ); 701 } 702 703 // Citations stored as jf2 are returned as mf2. 704 if ( is_array( $cite ) && ! wp_is_numeric_array( $cite ) ) { 705 if ( ! isset( $cite['properties'] ) ) { 706 if ( empty( $cite['type'] ) ) { 707 $cite['type'] = $this->get_cite_type(); 708 } 709 $cite = \ParseThis\jf2_to_mf2( $cite ); 710 } elseif ( empty( $cite['type'] ) || ! is_array( $cite['type'] ) ) { 711 // Reads before 4.0.0 stored the type as 'cite'. 712 $cite['type'] = array( 'h-' . $this->get_cite_type() ); 713 } 714 } 616 715 } 617 716 … … 649 748 } 650 749 651 /* Returns a normalized cite with all possible parameters present to reduce isset checks and to ensure everything is formatted correctly 652 * For Display and Metabox purposes only. 750 /** 751 * Returns the jf2 type of the post's citation, based on its kind. 752 * 753 * Matches the type Kind_Metabox::save_post() stores: a checkin cites a 754 * place (card), eat and drink cite food, and every other kind a citation. 755 * 756 * @since 4.0.0 757 * 758 * @return string The jf2 type: 'card', 'food' or 'cite'. 759 */ 760 public function get_cite_type() { 761 $kind = $this->get_kind(); 762 if ( 'checkin' === $kind ) { 763 return 'card'; 764 } 765 if ( in_array( $kind, array( 'drink', 'eat' ), true ) ) { 766 return 'food'; 767 } 768 return 'cite'; 769 } 770 771 /** 772 * Returns a citation as jf2 with every key present and formatted for display. 773 * 774 * Missing keys are set to empty strings, which saves isset checks. For 775 * display and the metabox only. 776 * 777 * 'authors' lists every author as a card with name, url and photo. 'author' 778 * is the single author, or for several authors one card whose fields hold 779 * each author's value in turn, separated by semicolons, as the metabox 780 * shows them. 781 * 782 * @since 3.4.9 783 * @since 4.0.0 Added 'authors'. 'author' joins several authors' photos too. 784 * 785 * @param array|string|false $cite The citation, as mf2, jf2 or a URL or name. 786 * @return array The normalized citation. 653 787 */ 654 788 public function normalize_cite( $cite ) { 655 // Ensures that an empty string is always present in the cite 789 // Ensures that an empty string is always present in the cite. 656 790 $author_defaults = array( 657 791 'type' => 'card', … … 670 804 'summary' => '', 671 805 'author' => $author_defaults, 806 'authors' => array(), 672 807 'category' => '', 673 808 ); … … 680 815 $cite = $cite[0]; 681 816 } 682 if ( Parse_This_MF2_Utils::is_microformat( $cite ) ) {683 $cite = mf2_to_jf2( $cite );817 if ( \ParseThis\MF2_Utils::is_microformat( $cite ) ) { 818 $cite = \ParseThis\mf2_to_jf2( $cite ); 684 819 } 685 820 … … 698 833 } 699 834 700 if ( wp_is_numeric_array( $cite['author'] ) ) { 701 if ( 1 === count( $cite['author'] ) ) { 702 $cite['author'] = array_pop( $cite['author'] ); 703 } 704 } 705 706 if ( is_string( $cite['author'] ) ) { 707 $cite['author'] = wp_http_validate_url( $cite['author'] ) ? array( 'url' => $cite['author'] ) : array( 'name' => $cite['author'] ); 708 } 709 710 $cite['author'] = wp_parse_args( $cite['author'], $author_defaults ); 835 $cite['authors'] = self::normalize_authors( $cite['author'] ); 836 if ( 1 === count( $cite['authors'] ) ) { 837 $cite['author'] = $cite['authors'][0]; 838 } else { 839 $cite['author'] = $author_defaults; 840 foreach ( array( 'name', 'url', 'photo' ) as $key ) { 841 $values = wp_list_pluck( $cite['authors'], $key ); 842 if ( array_filter( $values ) ) { 843 $cite['author'][ $key ] = implode( '; ', $values ); 844 } 845 } 846 } 711 847 712 848 if ( is_array( $cite['publication'] ) ) { 713 849 $cite['publication'] = $cite['publication']['name']; 714 }715 716 if ( is_array( $cite['author']['name'] ) ) {717 $cite['author']['name'] = implode( ';', $cite['author']['name'] );718 }719 if ( is_array( $cite['author']['url'] ) ) {720 $cite['author']['url'] = implode( ';', $cite['author']['url'] );721 850 } 722 851 … … 733 862 } 734 863 864 865 /** 866 * Returns a citation's authors as a list of cards. 867 * 868 * Accepts one author or a list, each as a jf2 or mf2 h-card, a URL or a 869 * name. Before 4.0.0 the metabox saved several authors as one card whose 870 * name, url and photo were lists; the values are paired up by position. 871 * 872 * @since 4.0.0 873 * 874 * @param mixed $author The author or authors. 875 * @return array[] Cards with string type, name, url and photo, leaving out empty ones. 876 */ 877 public static function normalize_authors( $author ) { 878 if ( wp_is_numeric_array( $author ) ) { 879 $authors = array(); 880 foreach ( $author as $item ) { 881 $authors = array_merge( $authors, self::normalize_authors( $item ) ); 882 } 883 return $authors; 884 } 885 if ( is_string( $author ) ) { 886 $author = wp_http_validate_url( $author ) ? array( 'url' => $author ) : array( 'name' => $author ); 887 } 888 if ( ! is_array( $author ) ) { 889 return array(); 890 } 891 if ( \ParseThis\MF2_Utils::is_microformat( $author ) ) { 892 $author = \ParseThis\mf2_to_jf2( $author ); 893 } 894 895 $fields = array(); 896 foreach ( array( 'name', 'url', 'photo' ) as $key ) { 897 $value = isset( $author[ $key ] ) ? $author[ $key ] : ''; 898 if ( is_array( $value ) && isset( $value['value'] ) ) { 899 // A jf2 photo with alt text. 900 $value = $value['value']; 901 } 902 $fields[ $key ] = array_values( array_filter( (array) $value, 'is_scalar' ) ); 903 } 904 905 $authors = array(); 906 $count = max( array_map( 'count', $fields ) ); 907 for ( $i = 0; $i < $count; $i++ ) { 908 $card = array( 'type' => 'card' ); 909 foreach ( $fields as $key => $values ) { 910 $card[ $key ] = isset( $values[ $i ] ) ? trim( (string) $values[ $i ] ) : ''; 911 } 912 if ( '' !== $card['name'] . $card['url'] . $card['photo'] ) { 913 $authors[] = $card; 914 } 915 } 916 return $authors; 917 } 918 /** 919 * Stores a date property. 920 * 921 * For posts other than attachments, 'published' and 'updated' set the 922 * post's own dates. Everything else is stored in post meta. 923 * 924 * @since 3.4.0 925 * 926 * @param string $key Property to set, such as 'published' or 'start'. 927 * @param mixed $value A date, in any form kind_safe_datetime() accepts. 928 * @return int|bool|WP_Error The result of updating the post or its meta, 929 * or false if the value is not a valid date. 930 */ 735 931 public function set_datetime_property( $key, $value ) { 736 932 // In an attachment the post date properties reflect when the item was uploaded not when the piece was created. 737 if ( ! $value instanceof DateTime ) { 738 $value = new DateTime( $value ); 739 } 933 // A mutable copy, as the timezone is changed below. 934 $value = kind_safe_datetime( $value, null, false ); 740 935 if ( ! $value ) { 741 936 return false; … … 755 950 } 756 951 952 /** 953 * Stores the post's duration as an ISO 8601 duration. 954 * 955 * @since 3.4.0 956 * 957 * @param mixed $value A duration, in any form kind_safe_interval() accepts. 958 * @return int|bool The result of updating the meta, or false if the value is not a valid duration. 959 */ 757 960 public function set_duration( $value ) { 758 if ( ! $value instanceof DateInterval ) { 759 $value = new DateInterval( $value ); 760 } 961 $value = kind_safe_interval( $value ); 761 962 if ( ! $value ) { 762 963 return false; … … 769 970 770 971 /** 771 * Set author 772 * 773 * @param array $value Author microformat. 774 * @return boolean|WP_Error 972 * Stores the author of an attachment. 973 * 974 * Other posts use their WordPress author, so nothing is stored for them. 975 * 976 * @since 3.4.0 977 * 978 * @param array $value Author, as mf2. 979 * @return int|bool|null The result of updating the meta, or null if the post is not an attachment. 775 980 */ 776 981 public function set_author( $value ) { 777 // Attachments may have been uploaded by a user but may have metadata for original author 982 // Attachments may have been uploaded by a user but may have metadata for original author. 778 983 if ( 'attachment' === get_post_type( $this->id ) ) { 779 984 return update_post_meta( $this->id, 'mf2_author', $value ); … … 781 986 } 782 987 988 /** 989 * Stores a property, or several. 990 * 991 * @since 3.4.0 992 * 993 * @param string|array $key Property to set, or an array of property => value pairs. 994 * @param mixed $value Optional. The value. Default null. 995 * @return mixed The result of storing the value. True when setting an array, 996 * and null if the key or value is empty. 997 */ 783 998 public function set( $key, $value = null ) { 784 999 if ( is_array( $key ) ) { … … 804 1019 case 'featured': 805 1020 if ( wp_http_validate_url( $value ) ) { 806 $featured = attachment_url_to_postid( $value );1021 $featured = self::get_attachment_id( $value ); 807 1022 if ( $featured ) { 808 1023 $value = $featured; … … 832 1047 return wp_update_post( $args, true ); 833 1048 case 'audio': 834 /* All media is handled identically. 835 */ 1049 // All media is handled identically. 836 1050 case 'video': 837 /* All Media is handled identically. 838 */ 1051 // All media is handled identically. 839 1052 case 'photo': 840 if ( Parse_This_MF2::is_microformat( $value ) ) {841 $url = Parse_This_MF2::get_plaintext( $value, 'url' );842 $id = attachment_url_to_postid( $url );1053 if ( \ParseThis\MF2_Utils::is_microformat( $value ) ) { 1054 $url = \ParseThis\MF2_Utils::get_plaintext( $value, 'url' ); 1055 $id = self::get_attachment_id( $url ); 843 1056 if ( $id ) { 844 $value = mf2_to_jf2( $value );1057 $value = \ParseThis\mf2_to_jf2( $value ); 845 1058 unset( $value['type'] ); 846 1059 $attachment = new Kind_Post( $id ); … … 852 1065 return update_post_meta( $this->id, 'mf2_' . $key, array( $url ) ); 853 1066 } 854 /* If it is not a microformat handle as default. 855 */ 1067 // If it is not a microformat handle as default. 856 1068 default: 857 1069 return update_post_meta( $this->id, 'mf2_' . $key, $value ); … … 859 1071 } 860 1072 1073 /** 1074 * Deletes a property stored in post meta. 1075 * 1076 * @since 3.4.0 1077 * 1078 * @param string $key Property to delete. 1079 * @return bool True on success, false on failure. 1080 */ 861 1081 public function delete( $key ) { 862 1082 return delete_post_meta( $this->id, 'mf2_' . $key ); -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-taxonomy.php
r3011061 r3738687 1 1 <?php 2 3 2 /** 4 * Post Kind Taxonomy Class3 * Kind_Taxonomy class. 5 4 * 6 * Registers the taxonomy and sets its behavior. 5 * @package Post_Kinds 6 * @since 2.0.2 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Registers the kind taxonomy and sets its behavior. 7 13 * 8 * @ package Post Kinds14 * @since 2.0.2 9 15 */ 10 16 final class Kind_Taxonomy { 11 private static $kinds = array(); // Store a Post_Kind class which is a definition of a specific kind 12 17 /** 18 * Registered kinds, keyed by slug. 19 * 20 * @since 3.1.0 21 * @var Post_Kind[] 22 */ 23 private static $kinds = array(); 24 25 /** 26 * Whether the built-in kinds have been registered. 27 * 28 * @since 4.0.0 29 * @var bool 30 */ 31 private static $builtins_loaded = false; 32 33 /** 34 * Loads the built-in kinds and adds the taxonomy's hooks. 35 * 36 * @since 2.0.2 37 */ 13 38 public static function init() { 14 $wp_version = get_bloginfo( 'version' ); 15 require_once plugin_dir_path( __FILE__ ) . '/register-kinds.php'; 39 self::load_kinds(); 16 40 17 41 // Add the Correct Archive Title to Kind Archives. 18 if ( version_compare( $wp_version, '5.5', '>' ) ) { 19 add_filter( 'get_the_archive_title', array( self::class, 'kind_archive_title' ), 10 ); 20 } else { 21 add_filter( 'get_the_archive_title', array( self::class, 'kind_archive_title' ), 10, 3 ); 22 } 42 add_filter( 'get_the_archive_title', array( self::class, 'kind_archive_title' ), 10 ); 23 43 24 44 add_filter( 'get_the_archive_title_prefix', array( self::class, 'kind_archive_prefix' ), 10 ); … … 26 46 add_filter( 'document_title_parts', array( self::class, 'document_title_parts' ), 10 ); 27 47 48 // Keep the taxonomy's default term in step with the Default Kind setting. 49 add_action( 'add_option_kind_default', array( self::class, 'sync_default_term' ) ); 50 add_action( 'update_option_kind_default', array( self::class, 'sync_default_term' ) ); 51 28 52 // Add Kind Permalinks. 29 53 add_filter( 'post_link', array( self::class, 'kind_permalink' ), 10, 3 ); 30 54 add_filter( 'post_type_link', array( self::class, 'kind_permalink' ), 10, 3 ); 31 55 32 // Query Variable to Exclude Kinds from Feed 56 // Query Variable to Exclude Kinds from Feed. 33 57 add_filter( 'query_vars', array( self::class, 'query_vars' ) ); 34 58 add_action( 'pre_get_posts', array( self::class, 'kind_filter_query' ) ); … … 37 61 add_action( 'pre_get_posts', array( self::class, 'kind_firehose_query' ), 99 ); 38 62 39 // Add Dropdown 63 // Add Dropdown. 40 64 add_action( 'restrict_manage_posts', array( self::class, 'kind_dropdown' ), 10, 2 ); 41 65 … … 43 67 add_filter( 'webmention_links', array( self::class, 'webmention_links' ), 11, 2 ); 44 68 45 // Add Links to Enclosures if Appropriate 69 // Add Links to Enclosures if Appropriate. 46 70 add_filter( 'enclosure_links', array( self::class, 'enclosure_links' ), 11, 2 ); 47 71 … … 51 75 // Trigger Webmention on Change in Post Status. 52 76 add_filter( 'transition_post_status', array( self::class, 'transition' ), 10, 3 ); 53 // On Post Save Set Post Format 77 // On Post Save Set Post Format. 54 78 add_action( 'save_post', array( self::class, 'post_formats' ), 99, 3 ); 55 79 56 // Create hook triggered by change of kind 80 // Create hook triggered by change of kind. 57 81 add_action( 'set_object_terms', array( self::class, 'set_object_terms' ), 10, 6 ); 58 82 … … 69 93 } 70 94 71 /** Template Redirect 72 * 95 /** 96 * Uses the kind photos template for photo archives, if the theme has one. 97 * 98 * @since 3.4.14 99 * 100 * @param string $template Path of the template to include. 101 * @return string Path of the template to include. 73 102 */ 74 103 public static function template_include( $template ) { 75 104 global $wp_query; 76 105 77 // Only use this template for archive pages 106 // Only use this template for archive pages. 78 107 if ( is_singular() ) { 79 108 return $template; … … 92 121 /** 93 122 * Register the Route. 123 * 124 * @since 3.3.4 94 125 */ 95 126 public static function register_routes() { … … 107 138 ), 108 139 ), 140 // For editor tools: only users who can write posts need it. 109 141 'permission_callback' => function () { 110 return current_user_can( ' read' );142 return current_user_can( 'edit_posts' ); 111 143 }, 112 144 ), … … 115 147 } 116 148 149 /** 150 * Returns a kind's registration info for the post-kinds/1.0/fields route. 151 * 152 * @since 3.3.4 153 * 154 * @param WP_REST_Request $request Request, with the kind slug (or all) in kind. 155 * @return Post_Kind|Post_Kind[]|WP_Error The kind, all kinds, or an error for an unknown kind. 156 */ 117 157 public static function read( $request ) { 118 158 $kind = $request->get_param( 'kind' ); 119 return self::get_kind_info( $kind, 'all' ); 159 $info = self::get_kind_info( $kind, 'all' ); 160 if ( false === $info ) { 161 return new WP_Error( 'rest_post_kind_not_found', __( 'Unknown post kind.', 'indieweb-post-kinds' ), array( 'status' => 404 ) ); 162 } 163 return $info; 120 164 } 121 165 … … 123 167 /** 124 168 * Add our query variables to query_vars list. 169 * 170 * @since 3.2.4 125 171 * 126 172 * @access public … … 140 186 * Filter the query for our post kinds. 141 187 * 142 * @access public 143 * 144 * @param $query 188 * @since 3.4.14 189 * 190 * @access public 191 * 192 * @param WP_Query $query The query. 193 * @return WP_Query The query. 145 194 */ 146 195 public static function kind_photo_filter( $query ) { 147 // check if the user is requesting an admin page 196 // check if the user is requesting an admin page. 148 197 if ( is_admin() || ! $query->is_main_query() ) { 149 198 return $query; 150 199 } 151 200 $photos = get_query_var( 'kind_photos' ); 152 // Return if not set 201 // Return if not set. 153 202 if ( $photos ) { 154 203 $query->set( 'posts_per_page', 30 ); … … 156 205 $query->is_comment_feed = false; 157 206 $query->is_home = false; 207 // Set for posts with images in their content or a photo property; see Kind_Media_Metadata::update_photo_flag(). 158 208 $query->set( 159 209 'meta_query', 160 210 array( 161 'relation' => 'OR',162 211 array( 163 'key' => '_content_img_ids', 164 'compare' => 'EXISTS', 165 ), 166 array( 167 'key' => 'mf2_photo', 212 'key' => '_kind_has_photo', 168 213 'compare' => 'EXISTS', 169 214 ), … … 177 222 * Filter the query for our post kinds. 178 223 * 179 * @access public 180 * 181 * @param $query 224 * @since 3.2.4 225 * 226 * @access public 227 * 228 * @param WP_Query $query The query. 229 * @return WP_Query The query. 182 230 */ 183 231 public static function kind_filter_query( $query ) { 184 // check if the user is requesting an admin page 232 // check if the user is requesting an admin page. 185 233 if ( is_admin() || ! $query->is_main_query() ) { 186 234 return $query; 187 235 } 188 236 $exclude = get_query_var( 'exclude' ); 189 // Return if both are not set 237 // Return if both are not set. 190 238 if ( ! taxonomy_exists( $exclude ) ) { 191 239 return $query; … … 214 262 * Filter the main query for post kinds. 215 263 * 216 * @access public 217 * 218 * @param $query 264 * @since 3.6.0 265 * 266 * @access public 267 * 268 * @param WP_Query $query The query. 269 * @return WP_Query The query. 219 270 */ 220 271 public static function kind_firehose_query( $query ) { 221 272 222 // check if the user is requesting an admin page 273 // check if the user is requesting an admin page. 223 274 if ( is_admin() || ! $query->is_main_query() ) { 224 275 return $query; … … 246 297 } 247 298 248 $query->set( 249 'tax_query', 299 $tax_query = array( 250 300 array( 251 'relation' => 'OR', 252 array( 253 'taxonomy' => 'kind', 254 'field' => 'slug', 255 'terms' => $firehose, 256 'operator' => 'IN', 257 ), 258 array( 259 'taxonomy' => 'kind', 260 'operator' => 'NOT EXISTS', 261 ), 262 ) 263 ); 301 'taxonomy' => 'kind', 302 'field' => 'slug', 303 'terms' => $firehose, 304 'operator' => 'IN', 305 ), 306 ); 307 // Posts without a kind are shown too. The upgrade routine gives them one, 308 // so the costly NOT EXISTS clause is only needed until it completes. 309 if ( Kind_Upgrade::is_pending() ) { 310 $tax_query['relation'] = 'OR'; 311 $tax_query[] = array( 312 'taxonomy' => 'kind', 313 'operator' => 'NOT EXISTS', 314 ); 315 } 316 $query->set( 'tax_query', $tax_query ); 264 317 return $query; 265 318 } … … 268 321 * Allows for some pre-defined aliases 269 322 * 270 * @access public 271 * 272 * @param $query 323 * @since 3.4.19 324 * 325 * @access public 326 * 327 * @param WP_Query $query The query. 328 * @return WP_Query The query. 273 329 */ 274 330 public static function kind_alias_filter( $query ) { … … 292 348 /** 293 349 * Register our REST API endpoint for post kinds. 350 * 351 * @since 3.1.0 294 352 * 295 353 * @access public … … 300 358 'kind', 301 359 array( 302 'get_callback' => array( self::class, 'get_ post_kind_slug' ),360 'get_callback' => array( self::class, 'get_rest_post_kind' ), 303 361 'update_callback' => array( self::class, 'set_rest_post_kind' ), 304 362 'schema' => array( 305 'kind' => __( 'Post Kind', 'indieweb-post-kinds' ), 306 'type' => 'string', 363 'description' => __( 'The post kind, or false if the post has none.', 'indieweb-post-kinds' ), 364 // False is only returned, never accepted: the enum allows kind slugs alone. 365 'type' => array( 'string', 'boolean' ), 366 'enum' => self::get_kind_list(), 367 'context' => array( 'view', 'edit' ), 307 368 ), 308 369 ) … … 312 373 /** 313 374 * Filter template hierarchy to include our template files. 375 * 376 * @since 3.1.2 314 377 * 315 378 * @access public … … 332 395 * Generate a sample permalink for a post. 333 396 * 397 * @since 3.2.2 398 * 334 399 * @access public 335 400 * … … 348 413 if ( is_numeric( $permalink[1] ) && $post_id === (int) $permalink[1] ) { 349 414 $excerpt = self::get_excerpt( $post ); 350 $excerpt = sanitize_title( mb_strimwidth( wp_strip_all_tags( $excerpt ), 0, 40 ) ); // phpcs:ignore415 $excerpt = sanitize_title( mb_strimwidth( wp_strip_all_tags( $excerpt ), 0, 40 ) ); 351 416 if ( ! empty( $excerpt ) ) { 352 417 $permalink[1] = wp_unique_post_slug( $excerpt, $post_id, $post->post_status, $post->post_type, $post->post_parent ); … … 359 424 * Generate a post title. 360 425 * 361 * @access public 362 * 363 * @param int!WP_Post $post Post ID or Post Object. 364 * @return string 426 * @since 3.3.3 427 * 428 * @access public 429 * 430 * @param int|WP_Post $post Post ID or post object. 431 * @param int $length Optional. Maximum length of the title. Default 40. 432 * @return string|null The title, or null if the post does not exist. 365 433 */ 366 434 public static function generate_title( $post, $length = 40 ) { … … 374 442 $kind_post = new Kind_Post( $post ); 375 443 $cite = $kind_post->get_cite(); 376 if ( Parse_This_MF2::is_microformat( $cite ) ) { 444 // Citations are stored as a list; use the first. 445 if ( wp_is_numeric_array( $cite ) && 1 === count( $cite ) ) { 446 $cite = $cite[0]; 447 } 448 if ( class_exists( '\\ParseThis\\MF2_Utils' ) && \ParseThis\MF2_Utils::is_microformat( $cite ) ) { 377 449 if ( array_key_exists( 'name', $cite['properties'] ) ) { 378 450 $excerpt = $cite['properties']['name']; … … 383 455 } 384 456 } 385 return mb_strimwidth( $excerpt, 0, $length, '...' ); // phpcs:ignore457 return mb_strimwidth( $excerpt, 0, $length, '...' ); 386 458 } 387 459 … … 389 461 * Filter the post title. Add a symbol at the end if generated title. 390 462 * 391 * @access public 392 * 393 * @param string $title Current post title 394 * @param int $post_id Post ID 463 * @since 3.0.9 464 * 465 * @access public 466 * 467 * @param string $title Current post title. 468 * @param int $post_id Post ID. 395 469 * @return string 396 470 */ … … 406 480 $title = sprintf( '[%1$s] %2$s', $post_kind, self::generate_title( $post_id, 60 ) ); 407 481 } 482 } elseif ( empty( $title ) && is_comment_feed() ) { 483 // Comment feed items read "Comment on <title> by <author>"; name untitled posts. 484 $title = self::generate_title( $post_id, 60 ); 408 485 } 409 486 return $title; … … 413 490 * Filter the single post title. 414 491 * 415 * @access public 416 * 417 * @param string $title Current post title 418 * @param WP_Post $post Post object 492 * @since 3.3.3 493 * 494 * @access public 495 * 496 * @param string $title Current post title. 497 * @param WP_Post $post Post object. 419 498 * @return string 420 499 */ … … 438 517 * Filter the post excerpt. 439 518 * 519 * @since 3.2.0 520 * 440 521 * @access public 441 522 * … … 456 537 * Set our post object terms. 457 538 * 539 * @since 3.0.8 540 * 458 541 * @access public 459 542 * … … 461 544 * @param array $terms Array of object terms. 462 545 * @param array $tt_ids Array of term taxonomy IDs. 463 * @param string $taxonomy Taxonomy slug 546 * @param string $taxonomy Taxonomy slug. 464 547 * @param bool $append If terms should be appended or overwrite. 465 548 * @param array $old_tt_ids Array of original trem taxonomy IDs. … … 475 558 $new_term = $new_term instanceof WP_Term ? $new_term->slug : ''; 476 559 if ( $old_term !== $new_term ) { 477 // Trigger a hook on a changed kind identifying old and new so actions can be performed 560 /** 561 * Fires when a post's kind changes. 562 * 563 * @since 3.0.8 564 * 565 * @param int $object_id Post ID. 566 * @param string $old_term Slug of the previous kind, or an empty string if there was none. 567 * @param string $new_term Slug of the new kind, or an empty string if the kind was removed. 568 */ 478 569 do_action( 'change_kind', $object_id, $old_term, $new_term ); 479 570 } … … 483 574 /** 484 575 * To Be Run on Plugin Activation. 576 */ 577 /** 578 * Registers the built-in kinds, once. 579 * 580 * Called from init() and on activation. A plugin is activated after 581 * plugins_loaded and init have run for the request, so neither the kinds 582 * nor the register_post_kind() function (kind-functions.php) would 583 * otherwise be loaded when the kind terms are created. 584 * 585 * @since 4.0.0 586 */ 587 public static function load_kinds() { 588 if ( self::$builtins_loaded ) { 589 return; 590 } 591 self::$builtins_loaded = true; 592 if ( ! function_exists( 'register_post_kind' ) ) { 593 require_once plugin_dir_path( __FILE__ ) . 'kind-functions.php'; 594 } 595 require plugin_dir_path( __FILE__ ) . 'register-kinds.php'; 596 } 597 598 /** 599 * Creates the kind terms on activation. 600 * 601 * Stops with an error if the IndieWeb Taxonomy plugin, which Post Kinds 602 * replaces, is active. 603 * 604 * @since 2.0.2 485 605 */ 486 606 public static function activate_kinds() { 487 607 if ( function_exists( 'iwt_plugin_notice' ) ) { 488 deactivate_plugins( plugin_basename( __FILE__ ) ); 489 wp_die( 'You have Indieweb Taxonomy activated. Post Kinds replaces this plugin. Please disable Taxonomy before activating' ); 490 } 608 deactivate_plugins( plugin_basename( dirname( __DIR__ ) . '/indieweb-post-kinds.php' ) ); 609 wp_die( esc_html__( 'Post Kinds replaces the IndieWeb Taxonomy plugin. Deactivate IndieWeb Taxonomy, then activate Post Kinds.', 'indieweb-post-kinds' ) ); 610 } 611 self::load_kinds(); 491 612 self::register(); 492 613 self::kind_defaultterms(); … … 494 615 } 495 616 617 /** 618 * Returns the rewrite regex for the pagination part of a URL. 619 * 620 * @since 3.5.0 621 * 622 * @return string The regex, such as 'page/?([0-9]{1,})'. 623 */ 496 624 public static function get_pagination_regex() { 497 625 global $wp_rewrite; … … 499 627 } 500 628 629 /** 630 * Returns the rewrite regex for the feed part of a URL. 631 * 632 * @since 3.5.0 633 * 634 * @param bool $with_base Optional. Whether to start with the feed base ('feed/'). Default true. 635 * @return string The regex, such as 'feed/(feed|rdf|rss|rss2|atom)'. 636 */ 501 637 public static function get_feed_regex( $with_base = true ) { 502 638 global $wp_rewrite; 503 // Build a regex to match the feed section of URLs, something like (feed|atom|rss|rss2)/? 639 // Build a regex to match the feed section of URLs, something like (feed|atom|rss|rss2)/?. 504 640 $feedregex2 = ''; 505 641 foreach ( (array) $wp_rewrite->feeds as $feed_name ) { … … 511 647 } 512 648 649 /** 650 * Joins regex parts into a rewrite rule pattern. 651 * 652 * @since 3.5.0 653 * 654 * @param string[] $elements Parts of the pattern, in order. 655 * @return string The parts joined by slashes and ending in '?$', or an empty string if there are none. 656 */ 513 657 public static function generate_permastruct( $elements ) { 514 658 if ( empty( $elements ) || ! is_array( $elements ) ) { … … 520 664 521 665 /** 666 * Returns the start of kind archive URLs, as in /kind/like/. 667 * 668 * Set as the Kind base on Settings > Permalinks. 669 * 670 * @since 4.0.0 671 * 672 * @return string The base. Default 'kind'. 673 */ 674 public static function get_kind_base() { 675 $base = get_option( 'kind_base', '' ); 676 $base = is_string( $base ) && '' !== $base ? $base : 'kind'; 677 /** 678 * Filters the start of kind archive URLs, as in /kind/like/. 679 * 680 * The Kind base on Settings > Permalinks sets it. Flush rewrite rules after 681 * changing this. 682 * 683 * @since 4.0.0 684 * 685 * @param string $base The base. Default the Kind base setting, or 'kind'. 686 */ 687 return (string) apply_filters( 'kind_base_slug', $base ); 688 } 689 690 /** 522 691 * Register the custom taxonomy for kinds. 692 * 693 * @since 2.0.2 523 694 */ 524 695 public static function register() { … … 540 711 'add_or_remove_items' => _x( 'Add or remove kinds', 'add or remove items', 'indieweb-post-kinds' ), 541 712 'choose_from_most_used' => _x( 'Choose from the most used kinds', 'choose most used', 'indieweb-post-kinds' ), 542 'not found' => _x( 'No kinds found', 'no kinds found', 'indieweb-post-kinds' ),713 'not_found' => _x( 'No kinds found', 'no kinds found', 'indieweb-post-kinds' ), 543 714 'no_terms' => _x( 'No kinds', 'no kinds', 'indieweb-post-kinds' ), 544 715 'back_to_items' => __( 'Back to Kinds', 'indieweb-post-kinds' ), … … 546 717 'item_link_description' => __( 'A link to a kind', 'indieweb-post-kinds' ), 547 718 ); 719 720 // The slug is used as the name too: core finds the default term by name, and a translated name would not match. 721 $default_kind = get_option( 'kind_default', 'note' ); 548 722 549 723 $args = array( … … 560 734 'show_admin_column' => true, 561 735 'meta_box_cb' => array( self::class, 'select_metabox' ), 562 'rewrite' => true,736 'rewrite' => array( 'slug' => self::get_kind_base() ), 563 737 'query_var' => true, 564 738 'default_term' => array( 565 'name' => __( 'Article', 'indieweb-post-kinds' ),566 'slug' => 'article',739 'name' => $default_kind, 740 'slug' => $default_kind, 567 741 ), 568 742 569 743 ); 570 744 register_taxonomy( 'kind', array( 'post' ), $args ); 571 add_permastruct( 'kind_date', 'kind/%kind%/%year%/%monthnum%/' ); 572 745 add_permastruct( 'kind_date', self::get_kind_base() . '/%kind%/%year%/%monthnum%/' ); 746 747 /** 748 * Filters the taxonomies that get kind archive rewrite rules. 749 * 750 * Each taxonomy gets rules for /kind/{kind}/{taxonomy}/{term}/, where kind is the Kind base. Rewrite rules 751 * are flushed when the plugin is activated or updated; flush them yourself 752 * (Settings > Permalinks > Save) after changing this. 753 * 754 * @since 3.4.15 755 * 756 * @param string[] $taxonomies Taxonomy names. Default 'post_tag', 'category' and 'series'. 757 */ 573 758 $kind_rewrite_taxonomies = apply_filters( 'kind_rewrite_taxonomies', array( 'post_tag', 'category', 'series' ) ); 574 759 575 760 // For each kind, support filtering by other taxonomies. 576 761 foreach ( $kind_rewrite_taxonomies as $taxonomy ) { 577 add_permastruct( 'kind_' . $taxonomy, 'kind/%kind%/' . $taxonomy . '/%' . $taxonomy . '%' ); 578 } 579 762 add_permastruct( 'kind_' . $taxonomy, self::get_kind_base() . '/%kind%/' . $taxonomy . '/%' . $taxonomy . '%' ); 763 } 764 765 /** 766 * Filters the URL slug for archives that exclude kinds, as in /exclude/kind/{kinds}/. 767 * 768 * Flush rewrite rules after changing this. 769 * 770 * @since 3.4.15 771 * 772 * @param string $slug Slug. Default 'exclude'. 773 */ 580 774 $kind_exclude_slug = apply_filters( 'kind_exclude_slug', 'exclude' ); 581 775 … … 584 778 add_permastruct( 'kind_excludes', $kind_exclude_slug . '/%kind_exclude%/%kind_exclude_terms%' ); 585 779 780 /** 781 * Filters the URL slug for the archive of posts with photos. 782 * 783 * Flush rewrite rules after changing this. 784 * 785 * @since 3.4.15 786 * 787 * @param string $slug Slug. Default 'photos'. 788 */ 586 789 $kind_photos_slug = apply_filters( 'kind_photos_slug', 'photos' ); 790 /** 791 * Filters the URL slug for the firehose feed, which includes every kind. 792 * 793 * Flush rewrite rules after changing this. 794 * 795 * @since 3.6.0 796 * 797 * @param string $slug Slug. Default 'firehose'. 798 */ 587 799 $kind_firehose_slug = apply_filters( 'kind_firehose_slug', 'firehose' ); 588 800 589 801 $year_regex = '([0-9]{4})'; 590 $month_regex = '( 0-9]{2})';802 $month_regex = '([0-9]{2})'; 591 803 $day_regex = '([0-9]{1,})'; 592 804 $pagination_regex = self::get_pagination_regex(); 593 805 $feed_regex = self::get_feed_regex(); 594 806 $feed_regex2 = self::get_feed_regex( false ); 595 $tax_regex = '([^/]*)'; 596 $term_regex = '([^/]*)'; 597 598 // Year Archives for Photos 807 808 // Year Archives for Photos. 599 809 add_rewrite_rule( 600 810 self::generate_permastruct( array( $kind_photos_slug, $year_regex, $pagination_regex ) ), … … 608 818 ); 609 819 610 // Year and Month Archive for Photos 820 // Year and Month Archive for Photos. 611 821 add_rewrite_rule( 612 822 self::generate_permastruct( array( $kind_photos_slug, $year_regex, $month_regex, $pagination_regex ) ), … … 620 830 ); 621 831 622 // Year and Month And Day Archive for Photos 832 // Year and Month And Day Archive for Photos. 623 833 add_rewrite_rule( 624 834 self::generate_permastruct( array( $kind_photos_slug, $year_regex, $month_regex, $day_regex, $pagination_regex ) ), … … 632 842 ); 633 843 634 // Month And Day Archive for Photos 844 // Month And Day Archive for Photos. 635 845 add_rewrite_rule( 636 846 self::generate_permastruct( array( $kind_photos_slug, $month_regex, $day_regex, $pagination_regex ) ), … … 698 908 699 909 /** 910 * Points the kind taxonomy's default term at the Default Kind setting. 911 * 912 * Core sets the default term when the taxonomy is registered; this applies a 913 * change to the setting within the same request. 914 * 915 * @since 4.0.0 916 */ 917 public static function sync_default_term() { 918 $term = get_term_by( 'slug', get_option( 'kind_default', 'note' ), 'kind' ); 919 if ( $term ) { 920 update_option( 'default_term_kind', $term->term_id ); 921 } 922 } 923 924 /** 700 925 * Sets up Default Terms for Kind Taxonomy. 926 * 927 * Creates a term for each registered kind that has none, and updates the 928 * name and description of terms that differ from the kind's, for example 929 * after a translation changes. Terms that already match are not written. 930 * 931 * @since 2.0.2 701 932 */ 702 933 public static function kind_defaultterms() { 703 $terms = self::get_kind_list(); 704 foreach ( $terms as $term ) { 705 if ( ! get_term_by( 'slug', $term, 'kind' ) ) { 934 $existing = get_terms( 935 array( 936 'taxonomy' => 'kind', 937 'hide_empty' => false, 938 ) 939 ); 940 $existing = is_wp_error( $existing ) ? array() : array_column( $existing, null, 'slug' ); 941 foreach ( self::get_kind_list() as $term ) { 942 if ( ! isset( $existing[ $term ] ) ) { 706 943 self::create_post_kind( $term ); 707 } else { 944 continue; 945 } 946 $kind = self::get_post_kind_info( $term ); 947 if ( $kind && ( $existing[ $term ]->name !== $kind->singular_name || $existing[ $term ]->description !== $kind->description ) ) { 708 948 self::update_post_kind( $term ); 709 949 } … … 714 954 * Update our post kind if the term already exists. 715 955 * 956 * @since 3.1.0 957 * 716 958 * @access private 717 959 * 718 * @param $term960 * @param string $term Post kind slug. 719 961 */ 720 962 private static function update_post_kind( $term ) { … … 740 982 * Create our post kind term. 741 983 * 984 * @since 3.1.0 985 * 742 986 * @access private 743 987 * 744 * @param $term988 * @param string $term Post kind slug. 745 989 */ 746 990 private static function create_post_kind( $term ) { 747 991 $kind = self::get_post_kind_info( $term ); 748 992 if ( $kind ) { 993 // The first argument is the term name; wp_insert_term() has no 'name' argument. 749 994 wp_insert_term( 750 $kind->s lug,995 $kind->singular_name, 751 996 'kind', 752 997 array( 753 'name' => $kind->singular_name,754 998 'description' => $kind->description, 755 999 'slug' => $term, … … 762 1006 * Filter our post kind permalink. 763 1007 * 1008 * @since 2.0.2 1009 * 764 1010 * @access public 765 1011 * … … 773 1019 return $permalink; } 774 1020 775 // Get post 1021 // Get post. 776 1022 $post = get_post( $post_id ); 777 1023 if ( ! $post ) { 778 1024 return $permalink; } 779 1025 780 // Get taxonomy terms 781 $terms = wp_get_object_terms( $post->ID, 'kind' );782 if ( ! is_wp_error( $terms ) && ! empty( $terms ) && is_object( $terms[0] ) ) {1026 // Get taxonomy terms; get_the_terms() uses the term cache the main query primes. 1027 $terms = get_the_terms( $post, 'kind' ); 1028 if ( is_array( $terms ) && ! empty( $terms ) && is_object( $terms[0] ) ) { 783 1029 $taxonomy_slug = $terms[0]->slug; 784 1030 } else { … … 789 1035 /** 790 1036 * Fetch the current post kind terms from the current query. 1037 * 1038 * @since 3.0.3 791 1039 * 792 1040 * @access public … … 812 1060 * Prefix Introduced in WP5.5 813 1061 * 1062 * @since 3.5.0 1063 * 814 1064 * @access public 815 1065 * … … 831 1081 * Original Title and Prefix introduced in WP5.5. 832 1082 * 1083 * @since 2.0.2 1084 * 833 1085 * @access public 834 1086 * … … 839 1091 */ 840 1092 public static function kind_archive_title( $title, $original_title = null, $prefix = null ) { 841 $return = array(); 842 $wp_version = get_bloginfo( 'version' ); 843 1093 $return = array(); 1094 1095 /** 1096 * This filter is documented in wp-includes/general-template.php 1097 */ 844 1098 $prefix = apply_filters( 'get_the_archive_title_prefix', $prefix ); 845 1099 … … 884 1138 /** 885 1139 * Filters the post kind archive description. 1140 * 1141 * @since 2.5.2 886 1142 * 887 1143 * @access public … … 911 1167 * Sets Post Format for Post Kind. 912 1168 * 913 * @param int $post_id Post ID 914 * @param WP_Post $post Post Object 915 * @param boolean $update, 1169 * @since 2.5.0 1170 * 1171 * @param int $post_id Post ID. 1172 * @param WP_Post $post Post object. 1173 * @param bool $update Whether this is an existing post being updated. 916 1174 */ 917 1175 public static function post_formats( $post_id, $post, $update ) { … … 925 1183 * Fiters the `<title>` tag parts for the post kind. 926 1184 * 1185 * @since 3.1.3 1186 * 927 1187 * @access public 928 1188 * … … 932 1192 public static function document_title_parts( $parts ) { 933 1193 if ( is_tax() || is_category() || is_tag() ) { 934 $terms = self::get_terms_from_query(); 1194 $terms = self::get_terms_from_query(); 1195 $return = array(); 935 1196 foreach ( $terms as $term ) { 936 1197 if ( 'kind' === $term->taxonomy ) { … … 950 1211 * Callback for the taxonomy meta box for our post kinds taxonomy. 951 1212 * 1213 * @since 2.0.2 1214 * 952 1215 * @access public 953 1216 * … … 955 1218 */ 956 1219 public static function select_metabox( $post ) { 957 $include = get_option( 'kind_termslist' ); 958 $include = array_merge( $include, array( 'note', 'reply', 'article' ) ); 959 // If Simple Location is Enabled, include the check-in type 960 // Filter Kinds 1220 $include = array_merge( Kind_Config::get_termslist(), array( 'note', 'reply', 'article' ) ); 1221 /** 1222 * Filters the kinds offered in the editor and listed by the Kind Menu widget. 1223 * 1224 * By default these are the kinds selected in the settings, plus note, reply and article. 1225 * 1226 * @since 2.0.2 1227 * 1228 * @param string[] $include Kind slugs. 1229 */ 961 1230 $include = array_unique( apply_filters( 'kind_include', $include ) ); 962 // Note cannot be removed or disabled without hacking the code 1231 // Note cannot be removed or disabled without hacking the code. 963 1232 if ( ! in_array( 'note', $include, true ) ) { 964 1233 $include[] = 'note'; 965 1234 } 966 if ( isset( $_GET['kind'] ) ) { 967 $default = get_term_by( 'slug', $_GET['kind'], 'kind' ); 1235 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Preselects the kind on the new post screen; nothing is saved. 1236 $requested = ( isset( $_GET['kind'] ) && is_string( $_GET['kind'] ) ) ? sanitize_key( wp_unslash( $_GET['kind'] ) ) : ''; 1237 if ( '' !== $requested ) { 1238 $default = get_term_by( 'slug', $requested, 'kind' ); 968 1239 } elseif ( 'publish' === get_post_status( $post ) ) { 969 // On existing published posts without a kind fall back on article which most closely mimics the behavior of an unclassified post 1240 // On existing published posts without a kind fall back on article which most closely mimics the behavior of an unclassified post. 970 1241 $default = get_term_by( 'slug', 'article', 'kind' ); 971 1242 } else { … … 1003 1274 * Register our post kinds. 1004 1275 * 1276 * @since 3.1.0 1277 * 1005 1278 * @access public 1006 1279 * 1007 1280 * @param string $slug Post kind slug. 1008 * @param array $args Post kind arguments { 1009 * @param string $singular_name Name for one instance of the kind. 1010 * @param string $name General name for the kind plural. 1011 * @param string $verb The string for the verb or action (liked this). 1012 * @param string $property Microformats 2 property, superseded by properties 1013 * @param array $properties Properties is an array outlining the fields for the particular kind. See Kind Fields class for more details 1014 * @param string $format Post Format that maps to this. 1015 * @param string $description Description of the Kind 1016 * @param url $description-url Link to more information 1017 * @param string $title Should this kind have an explicit title. 1018 * @param boolean $show Show in Settings. 1281 * @param array $args { 1282 * Post kind arguments. Arguments that are not listed are kept and can 1283 * be read as properties of the Post_Kind. 1284 * 1285 * @type string $singular_name Name for one instance of the kind. Default the slug. 1286 * @type string $name General name for the kind, plural. Default the slug. 1287 * @type string $verb The verb or action, such as 'Liked'. Default the slug. 1288 * @type string $property Microformats 2 property, such as 'like-of'. Default empty. 1289 * @type array $properties The fields for the kind. See Kind_Fields. 1290 * @type string $format Post format that maps to this kind. Default 'standard'. 1291 * @type string $icon Icon name. Default the slug. 1292 * @type string $description Description of the kind. 1293 * @type string $description_url Link to more information. 1294 * @type string $shortlink Shortlink code for the kind. 1295 * @type bool $title Whether the kind should have an explicit title. 1296 * @type bool $show Whether to show the kind in Settings. Default false. 1019 1297 * } 1020 * @return bool 1298 * @return bool True if the kind was registered, false if it already exists. 1021 1299 */ 1022 1300 public static function register_post_kind( $slug, $args ) { 1023 1301 $kind = new Post_Kind( $slug, $args ); 1024 // Do not allow reregistering existing kinds 1302 // Do not allow reregistering existing kinds. 1025 1303 if ( isset( self::$kinds[ $slug ] ) ) { 1026 1304 return false; … … 1032 1310 /** 1033 1311 * Retrieve info on a provided post kind. 1312 * 1313 * @since 3.1.0 1034 1314 * 1035 1315 * @access public … … 1045 1325 } 1046 1326 1047 // Enable a hidden post kind1048 1049 1327 /** 1050 1328 * Enables a hidden post kind. 1329 * 1330 * @since 3.1.0 1051 1331 * 1052 1332 * @access public … … 1067 1347 * Retrieve list of registered post kinds. 1068 1348 * 1349 * @since 3.1.0 1350 * 1069 1351 * @access public 1070 1352 * @return array … … 1077 1359 * Returns all translated strings. 1078 1360 * 1361 * @since 2.5.2 1362 * 1079 1363 * @param string $kind Post Kind to return. 1080 * @param string $property The individual property 1364 * @param string $property The individual property. 1081 1365 * @return string|array Return kind-property. If either is set to all, return all. 1082 1366 */ … … 1095 1379 return $k; 1096 1380 } 1097 if ( ! array_key_exists( $property, get_object_vars( $k )) ) {1381 if ( ! $k->has( $property ) ) { 1098 1382 return false; 1099 1383 } … … 1103 1387 /** 1104 1388 * Add available webmention links. 1389 * 1390 * @since 2.3.5 1105 1391 * 1106 1392 * @access public … … 1125 1411 /** 1126 1412 * Add available enclosure links. 1413 * 1414 * @since 3.0.4 1127 1415 * 1128 1416 * @access public … … 1150 1438 * Generate a dropdown of our post kinds for the post list table view. 1151 1439 * 1440 * @since 2.5.2 1441 * 1152 1442 * @access public 1153 1443 * @param string $post_type Current post type being listed. … … 1157 1447 if ( 'post' === $post_type ) { 1158 1448 $taxonomy = 'kind'; 1159 $selected = isset( $_GET[ $taxonomy ] ) ? $_GET[ $taxonomy ] : ''; 1449 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- A list-table filter; nothing is saved. 1450 $selected = ( isset( $_GET[ $taxonomy ] ) && is_string( $_GET[ $taxonomy ] ) ) ? sanitize_key( wp_unslash( $_GET[ $taxonomy ] ) ) : ''; 1160 1451 $kind_taxonomy = get_taxonomy( $taxonomy ); 1161 1452 wp_dropdown_categories( … … 1179 1470 * Set our post kind terms upon publish transition status. 1180 1471 * 1181 * @access public 1182 * @param int $post_id Current post ID 1472 * @since 2.0.2 1473 * 1474 * @access public 1475 * 1476 * @param int $post_id Current post ID. 1183 1477 * @param WP_Post|null $post Current post object. 1184 1478 */ … … 1187 1481 return; 1188 1482 } 1189 $ count= wp_get_post_terms( $post_id, 'kind' );1190 if ( is_countable( $count ) && $count <= 0) {1483 $terms = wp_get_post_terms( $post_id, 'kind' ); 1484 if ( ! is_wp_error( $terms ) && empty( $terms ) ) { 1191 1485 set_post_kind( $post_id, get_option( 'kind_default' ) ); 1192 1486 } … … 1195 1489 /** 1196 1490 * Maybe set our post kind terms upon post status transition. 1491 * 1492 * @since 2.0.2 1197 1493 * 1198 1494 * @access public … … 1211 1507 * Filter the classes on a post's markup. 1212 1508 * 1509 * @since 2.0.2 1510 * 1213 1511 * @access public 1214 1512 * … … 1227 1525 * Returns a pretty, translated version of a post kind slug. 1228 1526 * 1527 * @since 2.1.1 1528 * 1229 1529 * @param string $slug A post format slug. 1230 1530 * @return string The translated post format name. … … 1232 1532 public static function get_post_kind_string( $slug ) { 1233 1533 $string = self::get_kind_info( $slug, 'singular_name' ); 1234 return $s lug ? $slug : '';1534 return $string ? $string : ''; 1235 1535 } 1236 1536 1237 1537 /** 1238 1538 * Returns a link to a post kind index. 1539 * 1540 * @since 2.1.1 1239 1541 * 1240 1542 * @param string $kind The post kind slug. … … 1252 1554 * Retrieve a total count statistic for a given post kind. 1253 1555 * 1556 * @since 3.1.3 1557 * 1254 1558 * @access public 1255 1559 * … … 1267 1571 /** 1268 1572 * Returns the post kind slug for the current post. 1573 * 1574 * @since 2.1.1 1269 1575 * 1270 1576 * @access public … … 1288 1594 * Returns the post kind name for the current post. 1289 1595 * 1290 * @access public 1291 * 1292 * @param WP_Post|int|null $post 1293 * @return array|bool|string 1596 * @since 2.1.1 1597 * 1598 * @access public 1599 * 1600 * @param WP_Post|int|null $post Optional. Post ID or post object. Default the current post. 1601 * @return string|false The kind's translated name, or false if the post has no kind. 1294 1602 */ 1295 1603 public static function get_post_kind( $post = null ) { … … 1306 1614 * Check if a post has any of the given kinds, or any kind. 1307 1615 * 1616 * @since 2.1.1 1617 * 1308 1618 * @uses has_term() 1309 1619 * … … 1325 1635 * Assign a kind to a post. 1326 1636 * 1637 * @since 2.1.1 1638 * 1327 1639 * @param int|WP_Post $post The post for which to assign a kind. 1328 * @param string $kind A kind to assign. Using an empty string or array will default to article. 1329 * @return mixed WP_Error on error. Array of affected term IDs on success. 1640 * @param string $kind Optional. A registered kind's slug. Default 'article'. 1641 * @return array|false|WP_Error Array of affected term IDs on success. WP_Error if the post 1642 * or the kind is not valid, or false on failure. 1330 1643 */ 1331 1644 public static function set_post_kind( $post, $kind = 'article' ) { … … 1341 1654 1342 1655 /** 1343 * Update callback for REST API endpoint. 1344 * 1345 * @access public 1346 * 1347 * @param string $kind Post kind being processed. 1348 * @param $post_array 1349 */ 1350 public static function set_rest_post_kind( $kind, $post_array ) { 1351 if ( isset( $post_array['id'] ) ) { 1352 self::set_post_kind( $post_array['id'], $kind ); 1353 } 1656 * Update callback for the kind REST field. 1657 * 1658 * @since 3.1.0 1659 * 1660 * @access public 1661 * 1662 * @param string $kind Post kind to set. 1663 * @param WP_Post|array $post Post being updated. 1664 * @return true|WP_Error True on success, WP_Error if the kind could not be set. 1665 */ 1666 public static function set_rest_post_kind( $kind, $post ) { 1667 // WordPress passes the post being updated as a WP_Post object. 1668 $post_id = $post instanceof WP_Post ? $post->ID : ( is_array( $post ) && isset( $post['id'] ) ? (int) $post['id'] : 0 ); 1669 if ( ! $post_id ) { 1670 return new WP_Error( 'rest_invalid_post', __( 'Invalid post.', 'indieweb-post-kinds' ), array( 'status' => 400 ) ); 1671 } 1672 if ( ! current_user_can( 'edit_post', $post_id ) ) { 1673 return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to set the kind of this post.', 'indieweb-post-kinds' ), array( 'status' => rest_authorization_required_code() ) ); 1674 } 1675 $result = self::set_post_kind( $post_id, $kind ); 1676 if ( is_wp_error( $result ) ) { 1677 $result->add_data( array( 'status' => 400 ) ); 1678 return $result; 1679 } 1680 return true; 1681 } 1682 1683 /** 1684 * Get callback for the kind REST field. 1685 * 1686 * @since 4.0.0 1687 * 1688 * @access public 1689 * 1690 * @param array $post Prepared post data, as an array with an id key. 1691 * @return string|false The post's kind slug, or false if it has none. 1692 */ 1693 public static function get_rest_post_kind( $post ) { 1694 // The prepared post is an array; passing it to get_post() would cast it to 1. 1695 $post_id = is_array( $post ) && isset( $post['id'] ) ? (int) $post['id'] : 0; 1696 return $post_id ? self::get_post_kind_slug( $post_id ) : false; 1354 1697 } 1355 1698 … … 1357 1700 * Whether or not to display the before kind content. 1358 1701 * 1702 * @since 2.6.6 1703 * 1359 1704 * @access public 1360 1705 * @return mixed|void 1361 1706 */ 1362 1707 public static function before_kind() { 1708 /** 1709 * Filters whether to show the kind's icon or name before the response. 1710 * 1711 * Return false to hide it everywhere and remove the display option from the settings page. 1712 * 1713 * @since 2.6.6 1714 * 1715 * @param bool $display Whether to show it. Default true. 1716 */ 1363 1717 return apply_filters( 'kind_icon_display', true ); 1364 1718 } … … 1367 1721 * Display before Kind - either icon text or no display. 1368 1722 * 1723 * @since 2.6.6 1724 * 1369 1725 * @param string $kind The slug for the kind of the current post. 1370 * @param string $display Override display ;1726 * @param string $display Override display. 1371 1727 * @return string Marked up kind information. 1372 1728 */ … … 1378 1734 $display = get_option( 'kind_display' ); 1379 1735 } 1380 $text = '<span class="kind-display-text">' . self::get_kind_info( $kind, 'verb') . '</span> ';1736 $text = '<span class="kind-display-text">' . esc_html( self::get_kind_info( $kind, 'verb' ) ) . '</span> '; 1381 1737 $icon = self::get_icon( $kind ); 1382 // Hide Icon in Feed View 1738 // Hide Icon in Feed View. 1383 1739 if ( 'text' !== $display && is_feed() ) { 1384 1740 $icon = ''; … … 1399 1755 * Retrieve the icon SVG for a provided post kind. 1400 1756 * 1757 * @since 2.4.0 1758 * 1401 1759 * @access public 1402 1760 * … … 1406 1764 */ 1407 1765 public static function get_icon( $kind, $echo = false ) { 1766 // Icons are shown per post, per widget item and per settings row; read each file once. 1767 static $svgs = array(); 1768 $kind = sanitize_key( $kind ); 1769 if ( ! isset( $svgs[ $kind ] ) ) { 1770 $svg = sprintf( '%1$ssvgs/%2$s.svg', plugin_dir_path( __DIR__ ), $kind ); 1771 $svgs[ $kind ] = file_exists( $svg ) ? (string) file_get_contents( $svg ) : ''; 1772 // A default size for places without the plugin's stylesheet, such as feed readers. CSS overrides it. 1773 $svgs[ $kind ] = preg_replace( '/^<svg\b/', '<svg width="1em" height="1em"', $svgs[ $kind ] ); 1774 } 1775 if ( '' === $svgs[ $kind ] ) { 1776 return ''; 1777 } 1408 1778 $name = self::get_kind_info( $kind, 'singular_name' ); 1409 $svg = sprintf( '%1$ssvgs/%2$s.svg', plugin_dir_path( __DIR__ ), $kind ); 1410 $attributes = apply_filters( 'post_kinds_icon_attributes', 'style="display: inline-block; max-height: 1rem; margin-right: 0.5rem"' ); 1411 if ( file_exists( $svg ) ) { 1412 $return = sprintf( '<span class="svg-icon svg-%1$s" aria-label="%2$s" title="%2$s" %3$s><span aria-hidden="true">%4$s</span></span>', esc_attr( $kind ), esc_attr( $name ), $attributes, file_get_contents( $svg ) ); 1413 } else { 1414 return ''; 1415 } 1779 /** 1780 * Filters the HTML attributes added to the span that wraps a kind icon. 1781 * 1782 * The string is output as-is, so it must be safe, escaped markup. 1783 * 1784 * @since 3.5.8 1785 * @since 4.0.0 The default is empty; the stylesheet sizes the icon. 1786 * 1787 * @param string $attributes HTML attributes. Default empty. 1788 */ 1789 $attributes = (string) apply_filters( 'post_kinds_icon_attributes', '' ); 1790 $attributes = '' === $attributes ? '' : ' ' . $attributes; 1791 $return = sprintf( '<span class="svg-icon svg-%1$s" aria-label="%2$s" title="%2$s"%3$s><span aria-hidden="true">%4$s</span></span>', esc_attr( $kind ), esc_attr( $name ), $attributes, $svgs[ $kind ] ); 1416 1792 if ( $echo ) { 1417 echo $return; // phpcs:ignore 1793 echo $return; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The plugin's own SVG icon; the kind and name are escaped, and the attributes come from the post_kinds_icon_attributes filter. 1418 1794 } 1419 1795 return $return; -
indieweb-post-kinds/tags/4.0.0/includes/class-kind-view.php
r3001401 r3738687 1 1 <?php 2 2 /** 3 * Post Kind View Class3 * Kind_View class. 4 4 * 5 * Includes Helper Functions to Set Up Display Behavior and Allows Calling of View Templates 5 * @package Post_Kinds 6 * @since 2.0.2 6 7 */ 7 8 8 // The Kind_View class sets up the kind display behavior for kinds 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Sets up how kinds are displayed, and locates and renders the view templates. 13 * 14 * @since 2.0.2 15 */ 9 16 class Kind_View { 17 /** 18 * Adds the hooks that display kind markup. 19 * 20 * @since 2.0.2 21 */ 10 22 public static function init() { 11 23 24 /** 25 * Filters whether to add response markup to post content, excerpts and feeds automatically. 26 * 27 * Return false to place it in the theme instead, for example with 28 * Kind_View::get_display(). 29 * 30 * @since 3.6.0 31 * 32 * @param bool $display Whether to add it. Default true. 33 */ 12 34 if ( apply_filters( 'kind_content_display', true ) ) { 13 35 add_filter( 'the_content', array( static::class, 'content_response' ), 9 ); … … 22 44 /** 23 45 * Filters the attachment image attributes for image post kinds. 46 * 47 * @since 2.6.1 24 48 * 25 49 * @access public … … 55 79 } 56 80 57 /* 58 * Function will locate the correct template and return it. 59 * 60 * @param string $slug Post Kind Slug. 61 * @param string $name Post Kind Term Name. 62 * @return string|null File if located or null if unable to find. 81 /** 82 * Finds the view file for a kind, in the theme or the plugin. 83 * 84 * @since 3.4.0 85 * 86 * @param string $slug View slug, such as 'kind'. 87 * @param string $name Kind slug, such as 'like'. 88 * @return string|null Path of the view file, an empty string if no kind is given, 89 * or null if no view file exists. 63 90 */ 64 91 public static function locate_view( $slug, $name ) { … … 67 94 return ''; 68 95 } 69 $templates[] = "{$slug}-{$name}.php"; 70 $templates[] = "{$slug}.php"; 96 $templates = array( "{$slug}-{$name}.php", "{$slug}.php" ); 97 /** 98 * Filters the folders searched for view files before the plugin's own views folder. 99 * 100 * @since 3.3.0 101 * 102 * @param string[] $paths Folder paths, each with a trailing slash. Default the theme's kind_views folder. 103 */ 71 104 $look = apply_filters( 'kind_view_paths', array( get_theme_file_path( 'kind_views/' ) ) ); 72 105 $look[] = plugin_dir_path( __DIR__ ) . 'views/'; … … 94 127 * Function will return the output. 95 128 * 129 * @since 2.4.0 130 * 96 131 * @access public 97 132 * 98 133 * @param string $slug Post kind slug. 99 134 * @param string $name Post kind term name. 100 * @param array $args Optional Arguments135 * @param array $args Optional. Arguments for the view. 'post_id' is the post's ID. 101 136 * @return string 102 137 */ … … 124 159 } 125 160 126 $author = self::get_hcard( $cite['author'] ); 161 // Every author's h-card, as a list such as "Ann, Bob and Cat". 162 $author = array_filter( array_map( array( static::class, 'get_hcard' ), $cite['authors'] ) ); 163 $author = empty( $author ) ? false : wp_sprintf( '%l', $author ); 127 164 128 165 $url = $cite['url']; … … 140 177 * Return the post kind display. 141 178 * 179 * @since 2.1.1 180 * 142 181 * @access public 143 182 * … … 151 190 if ( 'post' === get_post_type( $post_id ) ) { 152 191 $kind = get_post_kind_slug( $post_id ); 153 $content = self::get_view_part( 'kind', $kind ); 192 $content = self::get_view_part( 'kind', $kind, array( 'post_id' => $post_id ) ); 193 /** 194 * Filters the response markup for a post. 195 * 196 * @since 2.0.2 197 * 198 * @param string $content Rendered view markup. 199 * @param int $post_id Post ID. 200 */ 154 201 return apply_filters( 'kind_response_display', $content, $post_id ); 155 202 } … … 159 206 * Echo the output of get_display. 160 207 * 208 * @since 2.1.1 209 * 161 210 * @access public 162 211 * @param int|null $post_id Post ID. 163 212 */ 164 213 public static function display( $post_id = null ) { 165 echo self::get_display( $post_id ); // phpcs:ignore 214 echo self::get_display( $post_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The view escapes its output as it is built. 166 215 } 167 216 168 217 /** 169 218 * Output the post kind content to the post content. 219 * 220 * @since 2.0.2 170 221 * 171 222 * @access public … … 183 234 } 184 235 global $wp_current_filter; 185 // Don't allow to be added to the_content more than once (prevent infinite loops) 236 // Don't allow to be added to the_content more than once (prevent infinite loops). 186 237 $done = false; 187 238 foreach ( $wp_current_filter as $filter ) { … … 203 254 * Output the post kind content to the feed item content. 204 255 * 256 * @since 3.0.5 257 * 205 258 * @access public 206 259 * … … 219 272 * Output the post kind content to the content if the `jsonfeed` plugin is active. 220 273 * 274 * @since 3.0.5 275 * 221 276 * @access public 222 277 * … … 228 283 229 284 $kind_post = new Kind_Post( $post ); 230 $kind = $kind_post->get_kind(); 231 $type = Kind_Taxonomy::get_kind_info( $kind, 'property' ); 232 $cite = mf2_to_jf2( $kind_post->get_cite() ); 285 $cite = \ParseThis\mf2_to_jf2( $kind_post->get_cite() ); 233 286 234 287 if ( is_string( $cite ) ) { … … 248 301 * Append the post kind display to excerpts. 249 302 * 303 * @since 2.3.3 304 * 250 305 * @access public 251 306 * … … 270 325 * This function will remove the www.prefix if it is part of the URL. 271 326 * 327 * @since 2.3.7 328 * 272 329 * @access public 273 330 * … … 283 340 * Converts an array of attributes and output them as a string. 284 341 * 342 * @since 2.1.1 343 * 285 344 * @access public 286 345 * … … 301 360 /** 302 361 * Converts a URL into a complete `<a>` link with link text. 362 * 363 * @since 2.1.1 303 364 * 304 365 * @access public … … 316 377 $atr = self::get_attributes( $atr ); 317 378 } 318 $return = '<a ' . $atr . ' href="' . $url . '">' . $name. '</a>';379 $return = '<a ' . $atr . ' href="' . esc_url( $url ) . '">' . esc_html( $name ) . '</a>'; 319 380 return $return; 320 381 } … … 323 384 * Create formatted HTML output for a field. 324 385 * 325 * @access public 326 * 327 * @param string $field Content to put in the markup 386 * @since 2.2.0 387 * 388 * @access public 389 * 390 * @param string $field Content to put in the markup. 328 391 * @param string $attr Attributes to add to the tag markup. 329 392 * @param string $type HTML tag type to create. Default span. … … 333 396 public static function get_formatted( $field, $attr, $type = 'span' ) { 334 397 if ( ! isset( $field ) ) { 335 return $string; 336 } 398 return ''; 399 } 400 $type = tag_escape( $type ); 337 401 $string = '<' . $type . $attr . '>' . $field . '</' . $type . '>'; 338 402 return $string; … … 341 405 /** 342 406 * Return post kind-wrapped oEmbed content for a provided URL. 407 * 408 * @since 2.1.1 343 409 * 344 410 * @access public … … 363 429 'dailymotion.com', 364 430 'flickr.com', 365 'imgur.com', 431 'imgur.com', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent -- A domain whose links are embedded with oEmbed, not a remote asset. 366 432 'issuu.com', 367 433 'kickstarter.com', … … 385 451 'youtube.com', 386 452 ); 453 /** 454 * Filters the domains whose cited URLs are embedded with oEmbed. 455 * 456 * @since 3.3.6 457 * 458 * @param string[] $approvelist Domain names. 459 */ 387 460 $approvelist = apply_filters( 'post_kind_embed_approvelist', $approvelist ); 388 461 if ( ! in_array( $host, $approvelist, true ) ) { 389 462 return ''; 390 463 } 391 if ( isset( $GLOBALS['wp_embed'] ) ) { 392 $embed = $GLOBALS['wp_embed']->autoembed( $url ); 393 } 464 if ( ! isset( $GLOBALS['wp_embed'] ) ) { 465 return ''; 466 } 467 $embed = $GLOBALS['wp_embed']->autoembed( $url ); 394 468 if ( 0 === strcmp( $embed, $url ) ) { 395 469 $embed = ''; 396 470 } else { 397 $embed = sprintf( '<div class="kind-embed">%1$s<a class="u-url" href="%2$s"></a></div>', $embed, $url ); 471 // The embed is the oEmbed markup WordPress generated. 472 $embed = sprintf( '<div class="kind-embed">%1$s<a class="u-url" href="%2$s"></a></div>', $embed, esc_url( $url ) ); 398 473 } 399 474 return $embed; … … 402 477 /** 403 478 * Returns an array of domains with the post type terminologies 479 * 480 * @since 2.1.1 404 481 * 405 482 * @access public … … 423 500 $domain = self::extract_domain_name( $url ); 424 501 if ( array_key_exists( $domain, $strings ) ) { 502 /** 503 * Filters the phrase for what a known site's URL points to, such as 'a video' for YouTube. 504 * 505 * It runs only for the sites listed above; other URLs get "a post". 506 * 507 * @since 2.0.2 508 * 509 * @param string $string Translated phrase. 510 */ 425 511 return apply_filters( 'kind_post_type_string', $strings[ $domain ] ); 426 512 } else { … … 431 517 /** 432 518 * Retrieve/Generate the h-card. 519 * 520 * @since 2.1.1 433 521 * 434 522 * @param mixed $author The author to generate Accepts an array or optionally other info. … … 451 539 452 540 /** 453 * Filter for alternate retrieval types 454 * 455 * This could be using WordPress's gravatar system, retrieval by pure URL, etc. 456 * 457 * @param string|boolean $author Defaults to false, but may return string. 458 * @param mixed $author Data on the author, type optional. Defaults to array. 459 * @param array $args Arguments passed to get_hcard. 541 * Filters the author data before it is turned into an h-card. 542 * 543 * Use it to look up author details another way, for example from a URL 544 * or Gravatar. 545 * 546 * @since 2.3.0 547 * 548 * @param mixed $author Author data, usually an array with name, url and photo. 549 * @param array $args Arguments passed to Kind_View::get_hcard(). 460 550 */ 461 551 $author = apply_filters( 'get_hcard_data', $author, $args ); … … 472 562 473 563 /** 474 * Filter for alternate presentation 475 * 476 * @param string|boolean $card Defaults to false, but may return string. 477 * @param mixed $author Data on the author, type optional. Defaults to array. 478 * @param array $args Arguments passed to get_hcard. 564 * Filters the h-card markup for an author. 565 * 566 * Return non-empty markup to replace the plugin's own h-card. The markup is 567 * output as-is, so it must be escaped. 568 * 569 * @since 2.3.0 570 * 571 * @param string $card H-card markup. Default empty, which uses the plugin's markup. 572 * @param array $author Author data, with name, url and photo. 573 * @param array $args Arguments passed to Kind_View::get_hcard(). 479 574 */ 480 575 $card = apply_filters( 'get_hcard', '', $author, $args ); … … 482 577 return $card; 483 578 } 484 // Temporarily drop multi-data on display485 if ( ! empty( $author['url'] ) && is_array( $author['url'] )) {486 $ author['url'] = $author['url'][0];487 }488 489 if ( is_array( $author['name'] ) ) { 490 $author['name'] = $author['name'][0];491 }492 493 if ( empty( $author['name']) && ! empty( $author['url'] ) ) {579 // One card is one author: use the first of any list. Kind_Post::normalize_authors() splits lists into cards. 580 foreach ( array( 'name', 'url', 'photo' ) as $key ) { 581 $value = isset( $author[ $key ] ) ? $author[ $key ] : ''; 582 $author[ $key ] = is_array( $value ) ? (string) reset( $value ) : (string) $value; 583 } 584 585 // Escape everything here: author data often comes from other sites. 586 $author['url'] = esc_url( $author['url'] ); 587 $author['photo'] = esc_url( $author['photo'] ); 588 if ( '' === trim( wp_strip_all_tags( $author['name'] ) ) && ! empty( $author['url'] ) ) { 494 589 $author['name'] = __( 'an author', 'indieweb-post-kinds' ); 495 590 } 591 $author['name'] = esc_html( wp_strip_all_tags( $author['name'] ) ); 592 $args['width'] = absint( $args['width'] ); 593 $args['height'] = absint( $args['height'] ); 496 594 497 595 // If no filter generated the card, generate the card. … … 502 600 } 503 601 if ( empty( $author['url'] ) ) { 504 return sprintf( '<img src="%1 s" class="h-card u-photo p-author" alt="%2s" width=%3s height=%4s/>', $author['photo'], $author['name'], $args['width'], $args['height'] );602 return sprintf( '<img src="%1$s" class="h-card u-photo p-author" alt="%2$s" width="%3$d" height="%4$d" />', $author['photo'], $author['name'], $args['width'], $args['height'] ); 505 603 } else { 506 return sprintf( '<a class="h-card p-author" href="%1 s"><img class="u-photo" src="%2s" alt="%3s" width=%4s height=%5s/></a>', $author['url'], $author['photo'], $author['name'], $args['width'], $args['height'] );604 return sprintf( '<a class="h-card p-author" href="%1$s"><img class="u-photo" src="%2$s" alt="%3$s" width="%4$d" height="%5$d" /></a>', $author['url'], $author['photo'], $author['name'], $args['width'], $args['height'] ); 507 605 } 508 606 break; 509 607 case 'name': 510 return sprintf( '<span class="h-card p-author">%1 s</span>', $author['name'] );608 return sprintf( '<span class="h-card p-author">%1$s</span>', $author['name'] ); 511 609 case 'both': 512 610 if ( ! empty( $author['photo'] ) ) { 513 611 if ( empty( $author['url'] ) ) { 514 return sprintf( '<span class="h-card p-author"><img src="%1 s" class="u-photo" alt="%2s" width=%3s height=%4s />%5s</span>', $author['photo'], $author['name'], $args['width'], $args['height'], $author['name'] );612 return sprintf( '<span class="h-card p-author"><img src="%1$s" class="u-photo" alt="%2$s" width="%3$d" height="%4$d" />%5$s</span>', $author['photo'], $author['name'], $args['width'], $args['height'], $author['name'] ); 515 613 } else { 516 return sprintf( '<a href="%1 s" class="h-card p-author"><img class="u-photo" src="%2s" alt="%3s" width=%4s height=%5s />%6s</a>', $author['url'], $author['photo'], $author['name'], $args['width'], $args['height'], $author['name'] );614 return sprintf( '<a href="%1$s" class="h-card p-author"><img class="u-photo" src="%2$s" alt="%3$s" width="%4$d" height="%5$d" />%6$s</a>', $author['url'], $author['photo'], $author['name'], $args['width'], $args['height'], $author['name'] ); 517 615 } 616 } elseif ( ! empty( $author['url'] ) ) { 617 return sprintf( '<a href="%1$s" class="h-card p-author">%2$s</a>', $author['url'], $author['name'] ); 518 618 } else { 519 return sprintf( '<span class="h-card p-author">%1 s</span>', $author['name'] );619 return sprintf( '<span class="h-card p-author">%1$s</span>', $author['name'] ); 520 620 } 521 621 break; … … 529 629 * Retrieve a title for a given citation. 530 630 * 631 * @since 2.3.0 632 * 531 633 * @access public 532 634 * … … 538 640 return false; 539 641 } 540 if ( empty( $cite['url'] ) ) { 541 if ( ! isset( $cite['name'] ) ) { 642 // FIXME: Temporary Fix for array functionality. 643 $url = isset( $cite['url'] ) ? $cite['url'] : ''; 644 $url = esc_url( is_array( $url ) ? (string) reset( $url ) : (string) $url ); 645 $name = isset( $cite['name'] ) ? $cite['name'] : null; 646 $name = is_array( $name ) ? (string) reset( $name ) : $name; 647 if ( empty( $url ) ) { 648 if ( null === $name ) { 542 649 return ''; 543 650 } 544 return sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 545 } 546 // FIXME: Temporary Fix for array functionality 547 if ( is_array( $cite['url'] ) ) { 548 $cite['url'] = $cite['url'][0]; 549 } 550 if ( ! array_key_exists( 'name', $cite ) ) { 551 // $cite['name'] = self::get_post_type_string( $cite['url'] ); 552 $cite['name'] = $cite['url']; 553 } 554 return sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $cite['url'], $cite['name'] ); 651 return sprintf( '<span class="p-name">%1$s</span>', esc_html( wp_strip_all_tags( (string) $name ) ) ); 652 } 653 if ( null === $name || '' === $name ) { 654 $name = $url; 655 } 656 return sprintf( '<a href="%1$s" class="p-name u-url">%2$s</a>', $url, esc_html( wp_strip_all_tags( (string) $name ) ) ); 555 657 } 556 658 557 659 /** 558 660 * Retrieve site name for given citation. 661 * 662 * @since 2.3.0 559 663 * 560 664 * @access public … … 570 674 return false; 571 675 } 572 return sprintf( '<span class="p-publication">%1s</span>', $cite['publication'] ); 573 } 574 575 /** 576 * Returns a requested Rating item. 577 * 578 * The returned value will be a printf-ready translated string. 579 * 580 * @access 581 * 582 * @param int $rating Rating Value. 583 * @return mixed|string 676 $publication = is_array( $cite['publication'] ) ? (string) reset( $cite['publication'] ) : (string) $cite['publication']; 677 return sprintf( '<span class="p-publication">%1$s</span>', esc_html( wp_strip_all_tags( $publication ) ) ); 678 } 679 680 /** 681 * Returns the citation's summary as escaped plain text. 682 * 683 * Summaries are plain text: tags are stripped, and the text is marked up as 684 * p-summary. Line breaks are kept. 685 * 686 * @since 4.0.0 687 * 688 * @access public 689 * 690 * @param array $cite Normalized citation (see Kind_Post::normalize_cite()). 691 * @return string The summary markup, or an empty string if there is none. 692 */ 693 public static function get_summary( $cite ) { 694 if ( ! is_array( $cite ) || empty( $cite['summary'] ) ) { 695 return ''; 696 } 697 $summary = $cite['summary']; 698 if ( is_array( $summary ) ) { 699 foreach ( array( 'value', 'text', 'html' ) as $key ) { 700 if ( isset( $summary[ $key ] ) ) { 701 $summary = $summary[ $key ]; 702 break; 703 } 704 } 705 $summary = is_array( $summary ) ? (string) reset( $summary ) : $summary; 706 } 707 $summary = trim( wp_strip_all_tags( (string) $summary ) ); 708 if ( '' === $summary ) { 709 return ''; 710 } 711 return sprintf( '<blockquote class="p-summary">%1$s</blockquote>', nl2br( esc_html( $summary ) ) ); 712 } 713 714 /** 715 * Returns a rating as a row of stars. 716 * 717 * @since 3.7.0 718 * 719 * @access public 720 * 721 * @param int|string $rating Rating value. Values above 10 show 10 stars. 722 * @return string Plain text: one star per point, or an empty string. 584 723 */ 585 724 public static function rating_text( $rating ) { 586 if ( ! $rating || 0 === $rating ) { 587 return ''; 588 } 589 590 $ret = ''; 591 for ( $i = $rating; $i > 0; $i-- ) { 592 $ret .= '⭐'; 593 } 594 595 return $ret; 596 } 597 598 /** 599 * Returns a requested RSVP option item. 600 * 601 * The returned value will be a printf-ready translated string. 602 * 603 * @access 604 * 605 * @param string $type RSVP type to return. 606 * @return mixed|string 607 */ 608 public static function rsvp_text( $type ) { 609 if ( ! $type ) { 610 return ''; 611 } 725 $rating = is_numeric( $rating ) ? (int) $rating : 0; 726 if ( $rating <= 0 ) { 727 return ''; 728 } 729 return str_repeat( '⭐', min( $rating, 10 ) ); 730 } 731 732 /** 733 * Returns the text for an RSVP. 734 * 735 * Called with only a type, it returns a printf-ready string whose 736 * %1$s and %2$s are the event URL and name, as before 4.0.0; the caller 737 * must escape both. With a URL or name, it returns the escaped sentence 738 * with the event linked. 739 * 740 * @since 2.6.1 741 * 742 * @access public 743 * 744 * @param string $type RSVP value: yes, no, maybe, interested or remote. 745 * @param string|null $url Optional. Event URL. 746 * @param string|null $name Optional. Event name. Defaults to the URL. 747 * @return string The RSVP text, or an empty string for an unknown type. 748 */ 749 public static function rsvp_text( $type, $url = null, $name = null ) { 612 750 $rsvp = array( 613 /* translators: URL for link to event and name ofevent */614 'yes' => __( 'Attending <a href="%1$s" class="u-in-reply-to">%2$s</a>', 'indieweb-post-kinds' ),615 /* translators: URL for link to event and name ofevent */616 'maybe' => __( 'Might be attending <a href="%1$s" class="u-in-reply-to">%2$s</a>', 'indieweb-post-kinds' ),617 /* translators: URL for link to event and name ofevent */618 'no' => __( 'Unable to Attend <a href="%1$s" class="u-in-reply-to">%2$s</a>', 'indieweb-post-kinds' ),619 /* translators: URL for link to event and name ofevent */620 'interested' => __( 'Interested in Attending <a href="%1$s" class=u-in-reply-to">%2$s</a>', 'indieweb-post-kinds' ),621 /* translators: URL for link to event and name ofevent */622 'remote' => __( 'Attending <a href="%1$s" class="u-in-reply-to">%2$s</a>remotely', 'indieweb-post-kinds' ),751 /* translators: %s: Link to the event */ 752 'yes' => __( 'Attending %s', 'indieweb-post-kinds' ), 753 /* translators: %s: Link to the event */ 754 'maybe' => __( 'Might be attending %s', 'indieweb-post-kinds' ), 755 /* translators: %s: Link to the event */ 756 'no' => __( 'Unable to Attend %s', 'indieweb-post-kinds' ), 757 /* translators: %s: Link to the event */ 758 'interested' => __( 'Interested in Attending %s', 'indieweb-post-kinds' ), 759 /* translators: %s: Link to the event */ 760 'remote' => __( 'Attending %s remotely', 'indieweb-post-kinds' ), 623 761 ); 624 return $rsvp[ $type ]; 762 if ( ! is_string( $type ) || ! array_key_exists( $type, $rsvp ) ) { 763 return ''; 764 } 765 if ( null === $url && null === $name ) { 766 return sprintf( esc_html( $rsvp[ $type ] ), '<a href="%1$s" class="u-in-reply-to">%2$s</a>' ); 767 } 768 $url = esc_url( (string) $url ); 769 $name = ( null === $name || '' === $name ) ? $url : $name; 770 $name = esc_html( wp_strip_all_tags( (string) $name ) ); 771 if ( '' === $url ) { 772 $link = sprintf( '<span class="p-in-reply-to">%1$s</span>', $name ); 773 } else { 774 $link = sprintf( '<a href="%1$s" class="u-in-reply-to">%2$s</a>', $url, $name ); 775 } 776 return sprintf( esc_html( $rsvp[ $type ] ), $link ); 625 777 } 626 778 627 779 /** 628 780 * Returns a requested read status option item. 781 * 782 * @since 3.0.7 629 783 * 630 784 * @access public … … 642 796 'finished' => __( 'Finished Reading: ', 'indieweb-post-kinds' ), 643 797 ); 644 return $read[ $type ];798 return ( is_string( $type ) && isset( $read[ $type ] ) ) ? $read[ $type ] : ''; 645 799 } 646 800 … … 648 802 * Return a string for a requested duration. 649 803 * 650 * @access public 651 * 652 * @param string|Dateinterval $interval Duration to display. 653 * @return string 654 * @throws Exception 804 * @since 2.6.1 805 * @since 4.0.0 Returns escaped markup. 806 * 807 * @access public 808 * 809 * @param string|DateInterval $interval Duration to display. 810 * @return string The duration as an escaped dt-duration time element, or an empty string if it is not a valid duration. 655 811 */ 656 812 public static function display_duration( $interval ) { 657 if ( ! $interval instanceof DateInterval ) { 658 $interval = new DateInterval( $interval ); 659 } 813 $interval = kind_safe_interval( $interval ); 660 814 if ( ! $interval ) { 661 815 return ''; … … 695 849 } 696 850 697 return sprintf( '<time class="dt-duration" datetime="%1$s">%2$s</time>', date_interval_to_iso8601( $interval ), implode( ' ', $duration) );851 return sprintf( '<time class="dt-duration" datetime="%1$s">%2$s</time>', esc_attr( date_interval_to_iso8601( $interval ) ), esc_html( implode( ' ', $duration ) ) ); 698 852 } 699 853 } // End Class -
indieweb-post-kinds/tags/4.0.0/includes/class-mf2-post.php
r2973430 r3738687 1 1 <?php 2 /** 3 * MF2_Post class. 4 * 5 * @package Post_Kinds 6 * @since 3.1.0 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 2 11 /** 3 12 * MF2 Post Class 4 13 * 5 * @package Post Kinds 6 * Assists in retrieving/saving microformats 2 properties from a post 14 * Assists in retrieving/saving microformats 2 properties from a post. 15 * 16 * @since 3.1.0 17 * @deprecated 4.0.0 Use Kind_Post. MF2_Post will be removed in a future major version. 7 18 */ 8 19 class MF2_Post implements ArrayAccess { 20 /** 21 * Post ID. 22 * 23 * @since 3.1.0 24 * @var int 25 */ 9 26 public $uid; 27 28 /** 29 * ID of the post author. 30 * 31 * @since 3.1.0 32 * @var int|string 33 */ 10 34 public $post_author; 35 36 /** 37 * Post type. 38 * 39 * @since 3.3.0 40 * @var string 41 */ 11 42 public $post_type; 43 44 /** 45 * Author, as an h-card. 46 * 47 * @since 3.1.0 48 * @var array|string|false 49 */ 12 50 public $author; 51 52 /** 53 * Publication the post appeared in. 54 * 55 * @since 3.1.0 56 * @var string 57 */ 13 58 public $publication; 59 60 /** 61 * Published date, in DATE_W3C format. 62 * 63 * @since 3.1.0 64 * @var string 65 */ 14 66 public $published; 67 68 /** 69 * Updated date, in DATE_W3C format. 70 * 71 * @since 3.1.0 72 * @var string 73 */ 15 74 public $updated; 75 76 /** 77 * Content, with 'html' and 'value' versions. 78 * 79 * @since 3.1.0 80 * @var array 81 */ 16 82 public $content; 83 84 /** 85 * Summary (excerpt). 86 * 87 * @since 3.1.0 88 * @var string 89 */ 17 90 public $summary; 91 92 /** 93 * ID of the parent post. 94 * 95 * @since 3.1.0 96 * @var int 97 */ 18 98 public $post_parent; 99 100 /** 101 * Post kind slug, or media type for an attachment. 102 * 103 * @since 3.1.0 104 * @var string|false|null 105 */ 19 106 public $kind; 107 108 /** 109 * Permalink, or file URL for an attachment. 110 * 111 * @since 3.1.0 112 * @var string|false 113 */ 20 114 public $url; 115 116 /** 117 * Post title. 118 * 119 * @since 3.1.0 120 * @var string 121 */ 21 122 public $name; 123 124 /** 125 * Category and tag names. 126 * 127 * @since 3.1.0 128 * @var string[] 129 */ 22 130 public $category = array(); 131 132 /** 133 * URL of the featured image. 134 * 135 * @since 3.1.0 136 * @var string 137 */ 23 138 public $featured; 139 140 /** 141 * Properties stored in mf2_ prefixed post meta, without the prefix. 142 * 143 * @since 3.1.0 144 * @var array 145 */ 24 146 private $mf2 = array(); 25 147 148 /** 149 * Reads a post's properties. 150 * 151 * @since 3.1.0 152 * @deprecated 4.0.0 Use Kind_Post. 153 * 154 * @param int|WP_Post|string $post Post ID, post object, or the URL of a post or attachment. 155 */ 26 156 public function __construct( $post ) { 157 _deprecated_function( __METHOD__, '4.0.0', 'Kind_Post' ); 27 158 if ( is_numeric( $post ) ) { 28 159 $this->uid = (int) $post; … … 75 206 } 76 207 208 /** 209 * Returns the published date. 210 * 211 * For an attachment, this is the date stored in post meta. 212 * 213 * @since 3.3.0 214 * 215 * @return string|false The date, or false if there is none. 216 */ 77 217 public function get_published() { 78 218 if ( 'attachment' === $this->post_type ) { 79 return $this-> meta['published'] ?? '';219 return $this->get_single( $this->mf2['published'] ?? '' ); 80 220 } 81 221 return get_the_date( DATE_W3C, $this->uid ); 82 222 } 83 223 224 /** 225 * Returns the updated date. 226 * 227 * For an attachment, this is the date stored in post meta. 228 * 229 * @since 3.3.0 230 * 231 * @return string|false The date, or false if there is none. 232 */ 84 233 public function get_updated() { 85 234 if ( 'attachment' === $this->post_type ) { 86 return $this-> meta['updated'] ?? '';235 return $this->get_single( $this->mf2['updated'] ?? '' ); 87 236 } 88 237 return get_the_modified_date( DATE_W3C, $this->uid ); 89 238 } 90 239 240 /** 241 * Returns the publication the post appeared in. 242 * 243 * For posts other than attachments, this is the site title. 244 * 245 * @since 3.3.0 246 * 247 * @return string The publication, or an empty string if an attachment has none. 248 */ 91 249 public function get_publication() { 92 250 if ( 'attachment' !== $this->post_type ) { 93 251 return get_bloginfo( 'title' ); 94 252 } 95 return $this->meta['publication'] ?? ''; 96 } 97 253 return $this->get_single( $this->mf2['publication'] ?? '' ); 254 } 255 256 /** 257 * Whether a property exists, for ArrayAccess. 258 * 259 * @since 3.2.0 260 * 261 * @param string $offset Property name. 262 * @return bool 263 */ 264 #[\ReturnTypeWillChange] 98 265 public function offsetExists( $offset ) { 99 266 $vars = get_object_vars( $this ); … … 104 271 } 105 272 273 /** 274 * Returns a property, for ArrayAccess. 275 * 276 * @since 3.2.0 277 * 278 * @param string $offset Property name. 279 * @return mixed The value, or null if it does not exist. 280 */ 281 #[\ReturnTypeWillChange] 106 282 public function offsetGet( $offset ) { 107 283 $vars = get_object_vars( $this ); … … 115 291 } 116 292 293 /** 294 * Sets a property, for ArrayAccess. 295 * 296 * @since 3.2.0 297 * 298 * @param string $offset Property name. 299 * @param mixed $value The value. 300 */ 301 #[\ReturnTypeWillChange] 117 302 public function offsetSet( $offset, $value ) { 118 303 $this->set( $offset, $value ); 119 304 } 120 305 306 /** 307 * Deletes a property stored in post meta, for ArrayAccess. 308 * 309 * @since 3.2.0 310 * 311 * @param string $offset Property name. 312 */ 313 #[\ReturnTypeWillChange] 121 314 public function offsetUnset( $offset ) { 122 315 $this->delete( $offset ); 123 316 } 124 317 318 /** 319 * Returns the names of a post's categories and tags. 320 * 321 * 'Uncategorized' is left out. 322 * 323 * @since 3.2.0 324 * 325 * @param int $post_id Post ID. 326 * @return string[] Category and tag names. 327 */ 125 328 public function get_categories( $post_id ) { 126 329 $category = array(); 127 // Get a list of categories and extract their names 330 // Get a list of categories and extract their names. 128 331 $post_categories = get_the_terms( $post_id, 'category' ); 129 332 if ( ! empty( $post_categories ) && ! is_wp_error( $post_categories ) ) { … … 131 334 } 132 335 133 // Get a list of tags and extract their names 336 // Get a list of tags and extract their names. 134 337 $post_tags = get_the_terms( $post_id, 'post_tag' ); 135 338 if ( ! empty( $post_tags ) && ! is_wp_error( $post_tags ) ) { … … 142 345 } 143 346 347 /** 348 * Returns the post kind, or the media type of an attachment. 349 * 350 * @since 3.1.0 351 * 352 * @return string|false|null The kind slug, or null for an attachment that is not image, video or audio. 353 */ 144 354 private function get_post_kind() { 145 355 if ( is_attachment( $this->uid ) ) { … … 166 376 } 167 377 378 /** 379 * Returns the post object. 380 * 381 * @since 3.1.0 382 * 383 * @return WP_Post|null The post, or null if it does not exist. 384 */ 168 385 public function get_post() { 169 386 return get_post( $this->uid ); … … 172 389 /** 173 390 * Is prefix in string. 391 * 392 * @since 3.1.0 174 393 * 175 394 * @param string $source The source string. … … 183 402 /** 184 403 * Returns True if Array is Multidimensional. 404 * 405 * @since 3.1.0 185 406 * 186 407 * @param array $arr array. … … 196 417 } 197 418 419 /** 420 * Sanitizes HTML with the allowed tags from the plugin settings. 421 * 422 * @since 3.1.0 423 * 424 * @param mixed $value The value. Anything but a string is returned unchanged. 425 * @return mixed The sanitized value. 426 */ 198 427 public static function sanitize_content( $value ) { 199 428 if ( ! is_string( $value ) ) { … … 207 436 } 208 437 438 /** 439 * Sanitizes a URL or text, recursively. 440 * 441 * @since 3.1.0 442 * 443 * @param string|array $value The value. 444 * @return string|array The sanitized value. 445 */ 209 446 public function sanitize_text( $value ) { 210 447 if ( is_array( $value ) ) { … … 222 459 * Retrieve author 223 460 * 461 * @since 3.1.0 462 * 224 463 * @return boolean|array The result or false if does not exist. 225 464 */ 226 465 public function get_author() { 227 466 if ( ! $this->post_author ) { 228 return $this-> meta['author'] ?? false;229 } 230 // Attachments may have been uploaded by a user but may have metadata for original author 467 return $this->get_single( $this->mf2['author'] ?? false ); 468 } 469 // Attachments may have been uploaded by a user but may have metadata for original author. 231 470 if ( 'attachment' === $this->post_type ) { 232 return $this-> meta['author'] ?? '';471 return $this->get_single( $this->mf2['author'] ?? '' ); 233 472 } 234 473 return array( … … 243 482 244 483 /** 245 * Sets an array with only the mf2 prefixed meta. 246 * 484 * Returns the properties stored in mf2_ prefixed post meta, without the prefix. 485 * 486 * @since 3.1.0 247 487 */ 248 488 private function get_mf2meta() { … … 251 491 return array(); 252 492 } 253 if ( isset( $meta['response'] ) ) { 254 $response = maybe_unserialize( $meta['response'] ); 255 // Retrieve from the old response array and store in new location. 256 if ( ! empty( $response ) ) { 493 // Very old versions stored the citation in the response meta. Read it as 494 // mf2_cite when there is no newer citation; the upgrade routine moves it. 495 if ( isset( $meta['response'] ) && ! isset( $meta['mf2_cite'] ) ) { 496 $response = maybe_unserialize( $meta['response'][0] ); 497 if ( is_array( $response ) ) { 257 498 $new = array(); 258 // Convert to new format and update.259 499 if ( ! empty( $response['title'] ) ) { 260 500 $new['name'] = $response['title']; … … 270 510 } 271 511 if ( ! empty( $response['author'] ) ) { 272 $new['card'] = array(); 273 $new['card']['name'] = $response['author']; 512 $new['card'] = array( 'name' => $response['author'] ); 274 513 if ( ! empty( $response['icon'] ) ) { 275 514 $new['card']['photo'] = $response['icon']; 276 515 } 277 516 } 278 $new = array_unique( $new ); 279 $new['card'] = array_unique( $new['card'] ); 280 if ( isset( $new ) ) { 281 update_post_meta( $this->uid, 'mf2_cite', $new ); 282 delete_post_meta( $this->uid, 'response' ); 283 $meta['cite'] = $new; 517 if ( $new ) { 518 $meta['mf2_cite'] = array( $new ); 284 519 } 285 520 } … … 291 526 unset( $meta[ $key ] ); 292 527 $key = str_replace( 'mf2_', '', $key ); 293 // Do not save microput prefixed instructions 528 // Do not save microput prefixed instructions. 294 529 if ( self::str_prefix( $key, 'mp-' ) ) { 295 530 continue; … … 312 547 * Map Properties Based on Post Type 313 548 * 549 * @since 3.3.0 314 550 */ 315 551 private function get_post_type_properties() { … … 325 561 * Retrieve value 326 562 * 327 * @param string $key The key to retrieve. 328 * @param boolean $single Whether to return a a single value or array if there is only one value. 563 * @since 3.1.0 564 * 565 * @param string|null $key Optional. The key to retrieve. Default null, for every property. 566 * @param bool $single Optional. Whether to unwrap a single value from its array. Default true. 329 567 * @return boolean|string|array The result or false if does not exist. 330 568 */ … … 374 612 } 375 613 614 /** 615 * Whether the post has a property. 616 * 617 * @since 3.1.0 618 * 619 * @param string $key Property name. 620 * @return bool 621 */ 376 622 public function has_key( $key ) { 377 623 $keys = array_merge( get_object_vars( $this ), $this->mf2 ); … … 379 625 } 380 626 627 /** 628 * Unwraps single-item arrays, recursively. 629 * 630 * @since 3.1.0 631 * 632 * @param mixed $value The value to unwrap. 633 * @param bool $discard Optional. Whether to keep only the first item of a list 634 * with more than one item. Default false. 635 * @return mixed The value, with single-item arrays replaced by their item. 636 */ 381 637 private function single_array( $value, $discard = false ) { 382 638 if ( ! is_array( $value ) ) { … … 395 651 } 396 652 653 /** 654 * Stores a property, or several. 655 * 656 * @since 3.1.0 657 * 658 * @param string|array $key Property name, an array of property => value pairs, or an mf2 item. 659 * @param mixed $value Optional. The value. Default null. 660 * @return mixed The result of storing the value, or false or null if nothing was stored. 661 */ 397 662 public function set( $key, $value = null ) { 398 663 if ( ! $key ) { … … 542 807 } 543 808 809 /** 810 * Deletes a property stored in post meta. 811 * 812 * @since 3.1.0 813 * 814 * @param string $key Property name. 815 * @return bool True on success, false on failure. 816 */ 544 817 public function delete( $key ) { 545 818 return delete_post_meta( $this->uid, 'mf2_' . $key ); 546 819 } 547 820 821 /** 822 * Converts mf2 to jf2. 823 * 824 * @since 3.1.0 825 * 826 * @param array $cite The mf2 item. 827 * @return array The jf2 item. 828 */ 548 829 public function mf2_to_jf2( $cite ) { 549 830 return mf2_to_jf2( $cite ); 550 831 } 551 832 833 /** 834 * Returns the first item of an array. 835 * 836 * @since 3.1.0 837 * 838 * @param mixed $value The value. 839 * @return mixed The first item, or the value if it is not an array. 840 */ 552 841 public function get_single( $value ) { 553 842 if ( is_array( $value ) ) { … … 557 846 } 558 847 848 /** 849 * Converts jf2 to mf2. 850 * 851 * @since 3.1.0 852 * 853 * @param array $item The jf2 item. 854 * @param string $type Optional. jf2 type to use if the item has none. Default 'cite'. 855 * @return array The mf2 item. 856 */ 559 857 public function jf2_to_mf2( $item, $type = 'cite' ) { 560 858 if ( is_array( $item ) && isset( $item['type'] ) && ! isset( $item['properties'] ) ) { … … 565 863 } 566 864 567 // Retrieve the right property to use for the link preview based on the kind. 568 // It will return an array of properties or false if it cannot find what it needs. 569 // Also will update old posts with new settings 865 /** 866 * Returns a property for the link preview, as jf2. 867 * 868 * Updates a property stored in the format used before 2.7.0. 869 * 870 * @since 3.1.0 871 * 872 * @param string $property Property name. 873 * @return array|false The property, or false if there is none. 874 */ 570 875 public function fetch( $property ) { 571 876 572 // If the property is not set then exit 877 // If the property is not set then exit. 573 878 if ( ! $property || ! $this->has_key( $property ) ) { 574 879 return false; … … 578 883 $return = array_shift( $return ); 579 884 } 580 // If it is in fact a string it is the pre 2.7.0 format and should be updated 885 // If it is in fact a string it is the pre 2.7.0 format and should be updated. 581 886 if ( is_string( $return ) ) { 582 887 if ( $this->has_key( 'cite' ) ) { … … 596 901 } 597 902 903 /** 904 * Returns the IDs of media attached to a post. 905 * 906 * @since 3.1.0 907 * 908 * @param string $type MIME type, such as 'image'. 909 * @param int|WP_Post $post Post ID or post object. 910 * @return int[] Attachment IDs. 911 */ 598 912 public function get_attached_media( $type, $post ) { 599 913 $posts = get_attached_media( $type, $post ); … … 601 915 } 602 916 917 /** 918 * Returns the post's audio. 919 * 920 * @since 3.1.0 921 * 922 * @return int[]|false Attachment IDs, or false if there are none. 923 */ 603 924 public function get_audios() { 604 925 // Check if the post itself if an audio attachment. … … 615 936 } 616 937 938 /** 939 * Returns the post's videos. 940 * 941 * @since 3.1.0 942 * 943 * @return int[]|false Attachment IDs, or false if there are none. 944 */ 617 945 public function get_videos() { 618 946 // Check if the post itself if an audio attachment. … … 629 957 } 630 958 959 /** 960 * Returns the post's images. 961 * 962 * @since 3.1.0 963 * 964 * @param bool $content_allow Optional. Whether to return images in the content. Default false. 965 * @return int[]|false Attachment IDs, or false if there are none. 966 */ 631 967 public function get_images( $content_allow = false ) { 632 968 // Check if the post itself is an image attachment. … … 636 972 $post_content = $this->content['html'] ?? ''; 637 973 if ( $post_content ) { 974 // Recorded when the post is saved; see Kind_Media_Metadata::save_post(). 638 975 $att_ids = get_post_meta( $this->uid, '_content_img_ids', true ); 639 if ( false === $att_ids ) {640 $att_ids = Kind_Media_Metadata::get_img_from_content( $post->post_content );641 update_post_meta( $this->uid, '_content_img_ids', $att_ids );642 }643 976 if ( $att_ids ) { 644 977 return $content_allow ? $att_ids : array(); … … 648 981 } 649 982 } 650 // If there is a featured image return only that. Otherwise return all images 983 // If there is a featured image return only that. Otherwise return all images. 651 984 $featured = get_post_thumbnail_id( $this->uid ); 652 985 if ( $featured ) { … … 655 988 $att_ids = $this->get_attached_media( 'image', $this->uid ); 656 989 $photos = $this->get( 'photo', false ); 657 if ( is_array( $photos ) ) { 658 if ( ! wp_is_numeric_array( $photos ) ) { 659 $photos = array( $photos ); 660 } 661 662 $photos = $this->sideload_images( $photos ); 663 $this->set( 'photo', $photos ); 990 if ( is_array( $photos ) && ! wp_is_numeric_array( $photos ) ) { 991 $photos = array( $photos ); 664 992 } 665 993 $att_ids = array_merge( $att_ids, $this->get_attachments_from_urls( $photos ) ); … … 670 998 } 671 999 672 private function media_sideload_image( $url, $post_id, $description = null ) { 673 // To prevent sideloading the same image multiple times check for the original URL which will now be stored 674 $ids = get_posts( 675 array( 676 'post_type' => 'attachment', 677 'suppress_filters' => false, 678 'nopaging' => true, 679 'meta_key' => 'mf2_url', 680 'meta_value' => $url, 681 'fields' => 'ids', 682 ) 683 ); 684 if ( ! empty( $ids ) ) { 685 return $ids[0]; 686 } 687 $id = media_sideload_image( $url, $post_id, $description, 'id' ); 688 if ( $id ) { 689 update_post_meta( $id, 'mf2_url', $url ); 690 } 691 return $id; 692 } 693 694 695 private function sideload_images( $photos ) { 696 require_once ABSPATH . 'wp-admin/includes/media.php'; 697 require_once ABSPATH . 'wp-admin/includes/file.php'; 698 require_once ABSPATH . 'wp-admin/includes/image.php'; 699 foreach ( $photos as $key => $value ) { 700 if ( is_string( $value ) ) { 701 if ( ! wp_http_validate_url( $value ) ) { 702 continue; 703 } elseif ( ! attachment_url_to_postid( $value ) ) { 704 $id = self::media_sideload_image( $value, $this->uid ); 705 if ( $id ) { 706 $photos[ $key ] = wp_get_attachment_url( $id ); 707 } 708 } 709 } 710 // Attempt to normalize old data 711 if ( is_array( $value ) ) { 712 $value = mf2_to_jf2( $value ); 713 $id = attachment_url_to_postid( $value['url'] ); 714 if ( ! $id ) { 715 $id = self::media_sideload_image( $value['url'], $this->uid ); 716 if ( $id ) { 717 $value['url'] = wp_get_attachment_url( $id ); 718 } 719 } 720 $args = array( 721 'ID' => $id, 722 'post_title' => $value['name'] ?? '', 723 'post_excerpt' => $value['summary'] ?? '', 724 ); 725 $args = array_filter( $args ); 726 wp_update_post( $args ); 727 unset( $value['name'] ); 728 unset( $value['summary'] ); 729 foreach ( $value as $k => $v ) { 730 update_post_meta( $id, 'mf2_' . $k, $v ); 731 } 732 $photos[ $key ] = $value['url']; 733 } 734 } 735 return $photos; 736 } 737 1000 /** 1001 * Looks up the attachment IDs for media URLs. 1002 * 1003 * @since 3.1.0 1004 * 1005 * @param string|array $urls A URL, or a list of URLs or arrays with a 'url' key. 1006 * @return int[] Attachment IDs found. 1007 */ 738 1008 public function get_attachments_from_urls( $urls ) { 739 1009 if ( is_string( $urls ) ) { -
indieweb-post-kinds/tags/4.0.0/includes/class-post-kind.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Post Kind Class3 * Post_Kind class. 4 4 * 5 * @package Post Kind6 * Used to define a Post Kind object5 * @package Post_Kinds 6 * @since 3.1.0 7 7 */ 8 8 9 final class Post_Kind { 10 public $id; // Term ID 11 public $slug; // Kind Slug 12 public $name; // Name of Kind - Plural 13 public $singular_name; // Name of Kind - Singular 14 public $verb; // The string for the verb or action (liked this) 15 public $format; // Post Format that Maps to This 16 public $icon; // Icon 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Defines a single post kind, as registered with register_post_kind(). 13 * 14 * @since 3.1.0 15 */ 16 final class Post_Kind implements JsonSerializable { 17 /** 18 * Term ID. 19 * 20 * @since 3.1.0 21 * @var int 22 */ 23 public $id; 24 25 /** 26 * Kind slug. 27 * 28 * @since 3.1.0 29 * @var string 30 */ 31 public $slug; 32 33 /** 34 * General name for the kind, plural. 35 * 36 * @since 3.1.0 37 * @var string 38 */ 39 public $name; 40 41 /** 42 * Name for one instance of the kind. 43 * 44 * @since 3.1.0 45 * @var string 46 */ 47 public $singular_name; 48 49 /** 50 * The verb or action, such as 'Liked'. 51 * 52 * @since 3.1.0 53 * @var string 54 */ 55 public $verb; 56 57 /** 58 * Post format that maps to this kind. 59 * 60 * @since 3.1.0 61 * @var string 62 */ 63 public $format; 64 65 /** 66 * Icon name. 67 * 68 * @since 3.1.0 69 * @var string 70 */ 71 public $icon; 72 73 /** 74 * Description of the kind. 75 * 76 * @since 3.1.0 77 * @var string 78 */ 17 79 public $description; 80 81 /** 82 * Link to more information about the kind. 83 * 84 * @since 3.1.0 85 * @var string 86 */ 18 87 public $description_url; 19 public $title; // Should this Kind Have an Explicit Title 20 public $show; // Show in Settings 21 public $property; // Primary Property 22 public $properties; // Array of Properties 23 public $shortlink; // Shortlink Coding per http://tantek.pbworks.com/w/page/21743973/Whistle#design 24 88 89 /** 90 * Whether the kind should have an explicit title. 91 * 92 * @since 3.1.0 93 * @var bool 94 */ 95 public $title; 96 97 /** 98 * Whether to show the kind in Settings. 99 * 100 * @since 3.1.0 101 * @var bool 102 */ 103 public $show; 104 105 /** 106 * Primary microformats 2 property, such as 'like-of'. 107 * 108 * @since 3.1.0 109 * @var string 110 */ 111 public $property; 112 113 /** 114 * The fields for the kind. See Kind_Fields. 115 * 116 * @since 3.1.0 117 * @var array 118 */ 119 public $properties; 120 121 /** 122 * Shortlink code, per http://tantek.pbworks.com/w/page/21743973/Whistle#design. 123 * 124 * @since 3.4.0 125 * @var string 126 */ 127 public $shortlink; 128 129 /** 130 * Arguments given at registration that are not declared properties, such as 131 * those a custom kind adds. Kept here rather than as dynamic properties, 132 * which are deprecated in PHP 8.2, and still readable as $kind->name. 133 * 134 * @since 4.0.0 135 * @var array 136 */ 137 private $extra = array(); 138 139 /** 140 * Defines a kind. 141 * 142 * @since 3.1.0 143 * 144 * @param string $slug Kind slug. 145 * @param array $args Optional. Kind arguments. See Kind_Taxonomy::register_post_kind(). 146 */ 25 147 public function __construct( $slug, $args = array() ) { 26 148 $this->slug = $slug; … … 28 150 } 29 151 152 /** 153 * Sets the kind's properties from its arguments, filling in defaults. 154 * 155 * Arguments that are not declared properties are kept as extra arguments. 156 * 157 * @since 3.1.0 158 * 159 * @param array $args Kind arguments. See Kind_Taxonomy::register_post_kind(). 160 */ 30 161 public function set_props( $args ) { 31 162 $defaults = array( … … 35 166 'format' => 'standard', 36 167 'icon' => $this->slug, 37 'show' => 'false',168 'show' => false, 38 169 'property' => '', 39 170 ); 40 171 $args = wp_parse_args( $args, $defaults ); 41 172 foreach ( $args as $property_name => $property_value ) { 42 $this->$property_name = $property_value; 173 if ( 'extra' !== $property_name && property_exists( $this, $property_name ) ) { 174 $this->$property_name = $property_value; 175 } else { 176 $this->extra[ $property_name ] = $property_value; 177 } 43 178 } 44 179 } 180 181 /** 182 * Reads an extra argument given at registration. 183 * 184 * @since 4.0.0 185 * 186 * @param string $name Argument name. 187 * @return mixed The value, or null if it was not given. 188 */ 189 public function __get( $name ) { 190 return isset( $this->extra[ $name ] ) ? $this->extra[ $name ] : null; 191 } 192 193 /** 194 * Whether an extra argument was given at registration. 195 * 196 * @since 4.0.0 197 * 198 * @param string $name Argument name. 199 * @return bool 200 */ 201 public function __isset( $name ) { 202 return isset( $this->extra[ $name ] ); 203 } 204 205 /** 206 * Sets an extra argument, without creating a dynamic property. 207 * 208 * @since 4.0.0 209 * 210 * @param string $name Argument name. 211 * @param mixed $value Value. 212 */ 213 public function __set( $name, $value ) { 214 $this->extra[ $name ] = $value; 215 } 216 217 /** 218 * Whether the kind has a declared property or an extra argument of this name. 219 * 220 * @since 4.0.0 221 * 222 * @param string $name Property or argument name. 223 * @return bool 224 */ 225 public function has( $name ) { 226 return ( 'extra' !== $name && property_exists( $this, $name ) ) || array_key_exists( $name, $this->extra ); 227 } 228 229 /** 230 * Returns the kind's declared properties and extra arguments, for JSON. 231 * 232 * @since 4.0.0 233 * 234 * @return array 235 */ 236 #[\ReturnTypeWillChange] 237 public function jsonSerialize() { 238 $vars = get_object_vars( $this ); 239 unset( $vars['extra'] ); 240 return array_merge( $this->extra, $vars ); 241 } 45 242 } -
indieweb-post-kinds/tags/4.0.0/includes/compat.php
r2973430 r3738687 1 1 <?php 2 3 // get_feed_build_date introduced in WordPress 5.2 4 if ( ! function_exists( 'get_feed_build_date' ) ) { 5 6 function get_feed_build_date( $format ) { 7 global $wp_query; 8 9 if ( empty( $wp_query ) || ! $wp_query->have_posts() ) { 10 // Fallback to last time any post was modified or published. 11 return get_lastpostmodified( 'GMT' ); 12 } 13 14 // Extract the post modified times from the posts. 15 $modified_times = wp_list_pluck( $wp_query->posts, 'post_modified_gmt' ); 16 17 // If this is a comment feed, check those objects too. 18 if ( $wp_query->is_comment_feed() && $wp_query->comment_count ) { 19 // Extract the comment modified times from the comments. 20 $comment_times = wp_list_pluck( $wp_query->comments, 'comment_date_gmt' ); 21 22 // Add the comment times to the post times for comparison. 23 $modified_times = array_merge( $modified_times, $comment_times ); 24 } 25 26 // Determine the maximum modified time. 27 $max_modified_time = max( 28 array_map( 29 function ( $time ) use ( $format ) { 30 return mysql2date( $format, $time, false ); 31 }, 32 $modified_times 33 ) 34 ); 35 36 /** 37 * Filters the date the last post or comment in the query was modified. 38 * 39 * @since 5.2.0 40 * 41 * @param string $max_modified_time Date the last post or comment was modified in the query. 42 * @param string $format The date format requested in get_feed_build_date. 43 */ 44 return apply_filters( 'get_feed_build_date', $max_modified_time, $format ); 2 /** 3 * Polyfills for functions added to WordPress or PHP after the plugin's minimums 4 * (WordPress 6.2, PHP 7.4). 5 * 6 * Each polyfill is a copy of the core function, guarded by function_exists(), 7 * and notes the version that introduced it so it can be removed once the 8 * minimum catches up. ClassicPress may already provide some of them. 9 * 10 * @package Post_Kinds 11 */ 12 13 defined( 'ABSPATH' ) || exit; 14 15 // wp_get_admin_notice() was introduced in WordPress 6.4. 16 if ( ! function_exists( 'wp_get_admin_notice' ) ) { 17 /** 18 * Creates and returns the markup for an admin notice. 19 * 20 * @since 6.4.0 21 * 22 * @param string $message The message. 23 * @param array $args { 24 * Optional. An array of arguments for the admin notice. Default empty array. 25 * 26 * @type string $type Optional. The type of admin notice. 27 * For example, 'error', 'success', 'warning', 'info'. 28 * Default empty string. 29 * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. 30 * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. 31 * @type string[] $additional_classes Optional. A string array of class names. Default empty array. 32 * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. 33 * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. 34 * } 35 * @return string The markup for an admin notice. 36 */ 37 function wp_get_admin_notice( $message, $args = array() ) { 38 $defaults = array( 39 'type' => '', 40 'dismissible' => false, 41 'id' => '', 42 'additional_classes' => array(), 43 'attributes' => array(), 44 'paragraph_wrap' => true, 45 ); 46 47 $args = wp_parse_args( $args, $defaults ); 48 49 /** 50 * Filters the arguments for an admin notice. 51 * 52 * @since 6.4.0 53 * 54 * @param array $args The arguments for the admin notice. 55 * @param string $message The message for the admin notice. 56 */ 57 $args = apply_filters( 'wp_admin_notice_args', $args, $message ); 58 $id = ''; 59 $classes = 'notice'; 60 $attributes = ''; 61 62 if ( is_string( $args['id'] ) ) { 63 $trimmed_id = trim( $args['id'] ); 64 65 if ( '' !== $trimmed_id ) { 66 $id = 'id="' . $trimmed_id . '" '; 67 } 68 } 69 70 if ( is_string( $args['type'] ) ) { 71 $type = trim( $args['type'] ); 72 73 if ( str_contains( $type, ' ' ) ) { 74 _doing_it_wrong( 75 __FUNCTION__, 76 sprintf( 77 /* translators: %s: The "type" key. */ 78 esc_html__( 'The %s key must be a string without spaces.', 'default' ), 79 '<code>type</code>' 80 ), 81 '6.4.0' 82 ); 83 } 84 85 if ( '' !== $type ) { 86 $classes .= ' notice-' . $type; 87 } 88 } 89 90 if ( true === $args['dismissible'] ) { 91 $classes .= ' is-dismissible'; 92 } 93 94 if ( is_array( $args['additional_classes'] ) && ! empty( $args['additional_classes'] ) ) { 95 $classes .= ' ' . implode( ' ', $args['additional_classes'] ); 96 } 97 98 if ( is_array( $args['attributes'] ) && ! empty( $args['attributes'] ) ) { 99 $attributes = ''; 100 foreach ( $args['attributes'] as $attr => $val ) { 101 if ( is_bool( $val ) ) { 102 $attributes .= $val ? ' ' . $attr : ''; 103 } elseif ( is_int( $attr ) ) { 104 $attributes .= ' ' . esc_attr( trim( $val ) ); 105 } elseif ( $val ) { 106 $attributes .= ' ' . $attr . '="' . esc_attr( trim( $val ) ) . '"'; 107 } 108 } 109 } 110 111 if ( false !== $args['paragraph_wrap'] ) { 112 $message = "<p>$message</p>"; 113 } 114 115 $markup = sprintf( '<div %1$sclass="%2$s"%3$s>%4$s</div>', $id, $classes, $attributes, $message ); 116 117 /** 118 * Filters the markup for an admin notice. 119 * 120 * @since 6.4.0 121 * 122 * @param string $markup The HTML markup for the admin notice. 123 * @param string $message The message for the admin notice. 124 * @param array $args The arguments for the admin notice. 125 */ 126 return apply_filters( 'wp_admin_notice_markup', $markup, $message, $args ); 45 127 } 46 128 } 47 129 48 // Backcompat for wp_filter_content_tags introduced in 5.5. 49 if ( ! function_exists( 'wp_filter_content_tags' ) ) { 50 function wp_filter_content_tags( $content ) { 51 return wp_make_content_images_responsive( $content ); // phpcs:ignore 130 // wp_admin_notice() was introduced in WordPress 6.4. 131 if ( ! function_exists( 'wp_admin_notice' ) ) { 132 /** 133 * Outputs an admin notice. 134 * 135 * @since 6.4.0 136 * 137 * @param string $message The message to output. 138 * @param array $args { 139 * Optional. An array of arguments for the admin notice. Default empty array. 140 * 141 * @type string $type Optional. The type of admin notice. 142 * For example, 'error', 'success', 'warning', 'info'. 143 * Default empty string. 144 * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. 145 * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. 146 * @type string[] $additional_classes Optional. A string array of class names. Default empty array. 147 * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. 148 * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. 149 * } 150 */ 151 function wp_admin_notice( $message, $args = array() ) { 152 /** 153 * Fires before an admin notice is output. 154 * 155 * @since 6.4.0 156 * 157 * @param string $message The message for the admin notice. 158 * @param array $args The arguments for the admin notice. 159 */ 160 do_action( 'wp_admin_notice', $message, $args ); 161 162 echo wp_kses_post( wp_get_admin_notice( $message, $args ) ); 52 163 } 53 164 } 165 166 // wp_trigger_error() was introduced in WordPress 6.4 (ClassicPress 2.2.0). 167 if ( ! function_exists( 'wp_trigger_error' ) ) { 168 /** 169 * Generates a user-level error/warning/notice/deprecation message. 170 * 171 * Generates the message when `WP_DEBUG` is true. 172 * 173 * @since 6.4.0 174 * 175 * @param string $function_name The function that triggered the error. 176 * @param string $message The message explaining the error. 177 * The message can contain allowed HTML 'a' (with href), 'code', 178 * 'br', 'em', and 'strong' tags and http or https protocols. 179 * If it contains other HTML tags or protocols, the message should be escaped 180 * before passing to this function to avoid being stripped {@see wp_kses()}. 181 * @param int $error_level Optional. The designated error type for this error. 182 * Only works with E_USER family of constants. Default E_USER_NOTICE. 183 * 184 * @throws WP_Exception If the error level is E_USER_ERROR. Exception where WP_Exception does not exist. 185 */ 186 function wp_trigger_error( $function_name, $message, $error_level = E_USER_NOTICE ) { 187 188 // Bail out if WP_DEBUG is not turned on. 189 if ( ! WP_DEBUG ) { 190 return; 191 } 192 193 /** 194 * Fires when the given function triggers a user-level error/warning/notice/deprecation message. 195 * 196 * Can be used for debug backtracking. 197 * 198 * @since 6.4.0 199 * 200 * @param string $function_name The function that was called. 201 * @param string $message A message explaining what has been done incorrectly. 202 * @param int $error_level The designated error type for this error. 203 */ 204 do_action( 'wp_trigger_error_run', $function_name, $message, $error_level ); 205 206 if ( ! empty( $function_name ) ) { 207 $message = sprintf( '%s(): %s', $function_name, $message ); 208 } 209 210 $message = wp_kses( 211 $message, 212 array( 213 'a' => array( 'href' => true ), 214 'br' => array(), 215 'code' => array(), 216 'em' => array(), 217 'strong' => array(), 218 ), 219 array( 'http', 'https' ) 220 ); 221 222 if ( E_USER_ERROR === $error_level ) { 223 // WP_Exception was also introduced in WordPress 6.4. 224 $exception = class_exists( 'WP_Exception' ) ? 'WP_Exception' : 'Exception'; 225 throw new $exception( $message ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped 226 } 227 228 trigger_error( $message, $error_level ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error, WordPress.Security.EscapeOutput.OutputNotEscaped 229 } 230 } -
indieweb-post-kinds/tags/4.0.0/includes/kind-functions.php
r3014138 r3738687 1 1 <?php 2 2 /** 3 * Kind Functions3 * Global functions for registering, reading and displaying kinds. 4 4 * 5 * Global Scoped Functions for Handling Kinds. 6 * 7 * @package Post Kinds 5 * @package Post_Kinds 6 * @since 2.0.2 8 7 */ 9 8 10 /** 11 * 12 */ 13 function register_post_kind( $slug, $args ) { 14 Kind_Taxonomy::register_post_kind( $slug, $args ); 15 } 16 17 function set_post_kind_visibility( $slug, $show = true ) { 18 Kind_Taxonomy::set_post_kind_visibility( $slug, $show ); 19 } 20 21 /** 22 * Retrieves an array of post kind slugs. 23 * 24 * @return array The array of post kind slugs. 25 */ 26 function get_post_kind_slugs() { 27 return Kind_Taxonomy::get_post_kind_slugs(); 28 } 29 30 /** 31 * Returns a pretty, translated version of a post kind slug 32 * 33 * @param string $slug A post format slug. 34 * @return string The translated post format name. 35 */ 36 function get_post_kind_string( $slug ) { 37 return Kind_Taxonomy::get_post_kind_string( $slug ); 38 } 39 40 /** 41 * Returns a link to a post kind index. 42 * 43 * @param string $kind The post kind slug. 44 * @return string The post kind term link. 45 */ 46 function get_post_kind_link( $kind ) { 47 return Kind_Taxonomy::get_post_kind_link( $kind ); 48 } 49 50 /** 51 * Returns the post kind slug for the current post. 52 * 53 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post. 54 * @return string The post kind slug. 55 */ 56 function get_post_kind_slug( $post = null ) { 57 return Kind_Taxonomy::get_post_kind_slug( $post ); 58 } 59 60 /** 61 * Returns the post kind name for the current post. 62 * 63 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post. 64 * @return string The post kind name. 65 */ 66 function get_post_kind( $post = null ) { 67 return Kind_Taxonomy::get_post_kind( $post ); 68 } 69 70 /** 71 * Check if a post has any of the given kinds, or any kind. 72 * 73 * @uses has_term() 74 * 75 * @param string|array $kinds Optional. The kind to check. 76 * @param object|int $post Optional. The post to check. If not supplied, defaults to the current post if used in the loop. 77 * @return bool True if the post has any of the given kinds (or any kind, if no kind specified), false otherwise. 78 */ 79 function has_post_kind( $kinds = array(), $post = null ) { 80 return Kind_Taxonomy::has_post_kind( $kinds, $post ); 81 } 82 83 /** 84 * Assign a kind to a post 85 * 86 * @param int|object $post The post for which to assign a kind. 87 * @param string $kind A kind to assign. Using an empty string or array will default to note. 88 * @return mixed WP_Error on error. Array of affected term IDs on success. 89 */ 90 function set_post_kind( $post, $kind ) { 91 return Kind_Taxonomy::set_post_kind( $post, $kind ); 92 } 93 94 /** 95 * Return the Displayed Response for a Specific Kind 96 * 97 * @param $slug 98 * @param $name 99 * @param $args 100 * @return string 101 */ 102 103 function get_kind_view_part( $slug, $name = null, $args = null ) { 104 Kind_View::get_view_part( $slug, $name, $args ); 105 } 106 107 function kind_display( $post_id = null ) { 108 echo Kind_View::get_display( $post_id ); // phpcs:ignore 109 } 110 111 function kind_flatten_array( $a ) { 112 if ( ! is_array( $a ) ) { 9 defined( 'ABSPATH' ) || exit; 10 11 if ( ! function_exists( 'register_post_kind' ) ) { 12 /** 13 * Registers a post kind. 14 * 15 * @since 3.1.0 16 * 17 * @param string $slug Post kind slug. 18 * @param array $args Post kind arguments. See Kind_Taxonomy::register_post_kind(). 19 */ 20 function register_post_kind( $slug, $args ) { 21 Kind_Taxonomy::register_post_kind( $slug, $args ); 22 } 23 } 24 25 if ( ! function_exists( 'set_post_kind_visibility' ) ) { 26 /** 27 * Shows or hides a post kind in Settings. 28 * 29 * @since 3.1.0 30 * 31 * @param string $slug Post kind slug. 32 * @param bool $show Optional. Whether to show the kind. Default true. 33 */ 34 function set_post_kind_visibility( $slug, $show = true ) { 35 Kind_Taxonomy::set_post_kind_visibility( $slug, $show ); 36 } 37 } 38 39 if ( ! function_exists( 'get_post_kind_slugs' ) ) { 40 /** 41 * Retrieves an array of post kind slugs. 42 * 43 * @since 0.30 44 * 45 * @return array The array of post kind slugs. 46 */ 47 function get_post_kind_slugs() { 48 return Kind_Taxonomy::get_kind_list(); 49 } 50 } 51 52 if ( ! function_exists( 'get_post_kind_string' ) ) { 53 /** 54 * Returns a pretty, translated version of a post kind slug. 55 * 56 * @since 0.30 57 * 58 * @param string $slug A post kind slug. 59 * @return string The translated post kind name, or an empty string if the kind is not registered. 60 */ 61 function get_post_kind_string( $slug ) { 62 return Kind_Taxonomy::get_post_kind_string( $slug ); 63 } 64 } 65 66 if ( ! function_exists( 'get_post_kind_link' ) ) { 67 /** 68 * Returns a link to a post kind index. 69 * 70 * @since 0.30 71 * 72 * @param string $kind The post kind slug. 73 * @return string|WP_Error|false The post kind term link, a WP_Error from get_term_link(), 74 * or false if the kind has no term. 75 */ 76 function get_post_kind_link( $kind ) { 77 return Kind_Taxonomy::get_post_kind_link( $kind ); 78 } 79 } 80 81 if ( ! function_exists( 'get_post_kind_slug' ) ) { 82 /** 83 * Returns the post kind slug for the current post. 84 * 85 * @since 0.30 86 * 87 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post. 88 * @return string|false The post kind slug, or false if the post has no kind. 89 */ 90 function get_post_kind_slug( $post = null ) { 91 return Kind_Taxonomy::get_post_kind_slug( $post ); 92 } 93 } 94 95 if ( ! function_exists( 'get_post_kind' ) ) { 96 /** 97 * Returns the post kind name for the current post. 98 * 99 * @since 0.30 100 * 101 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post. 102 * @return string|false The translated post kind name, or false if the post has no kind. 103 */ 104 function get_post_kind( $post = null ) { 105 return Kind_Taxonomy::get_post_kind( $post ); 106 } 107 } 108 109 if ( ! function_exists( 'has_post_kind' ) ) { 110 /** 111 * Check if a post has any of the given kinds, or any kind. 112 * 113 * @since 0.30 114 * 115 * @uses has_term() 116 * 117 * @param string|array $kinds Optional. The kind to check. 118 * @param WP_Post|int $post Optional. The post to check. If not supplied, defaults to the current post if used in the loop. 119 * @return bool True if the post has any of the given kinds (or any kind, if no kind specified), false otherwise. 120 */ 121 function has_post_kind( $kinds = array(), $post = null ) { 122 return Kind_Taxonomy::has_post_kind( $kinds, $post ); 123 } 124 } 125 126 if ( ! function_exists( 'set_post_kind' ) ) { 127 /** 128 * Assign a kind to a post. 129 * 130 * @since 0.30 131 * 132 * @param int|WP_Post $post The post for which to assign a kind. 133 * @param string $kind A registered kind's slug. 134 * @return array|false|WP_Error Array of affected term IDs on success. WP_Error if the post 135 * or the kind is not valid, or false on failure. 136 */ 137 function set_post_kind( $post, $kind ) { 138 return Kind_Taxonomy::set_post_kind( $post, $kind ); 139 } 140 } 141 142 if ( ! function_exists( 'get_kind_view_part' ) ) { 143 /** 144 * Returns the rendered view for a kind. 145 * 146 * @since 2.5.0 147 * 148 * @param string $slug View slug, such as 'kind'. 149 * @param string $name Optional. Kind slug, such as 'like'. Default null. 150 * @param array|null $args Optional. Arguments for the view. See Kind_View::get_view_part(). Default null. 151 * @return string The view's markup. 152 */ 153 function get_kind_view_part( $slug, $name = null, $args = null ) { 154 return Kind_View::get_view_part( $slug, $name, $args ); 155 } 156 } 157 158 if ( ! function_exists( 'kind_display' ) ) { 159 /** 160 * Outputs the kind markup for a post. 161 * 162 * For themes that place the markup themselves instead of adding it to the content. 163 * 164 * @since 2.6.1 165 * 166 * @param int|null $post_id Optional. Post ID. Default the current post. 167 */ 168 function kind_display( $post_id = null ) { 169 echo Kind_View::get_display( $post_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The view escapes its output as it is built. 170 } 171 } 172 173 if ( ! function_exists( 'kind_flatten_array' ) ) { 174 /** 175 * Removes empty values from an array and unwraps single-item lists, recursively. 176 * 177 * @since 2.7.5 178 * 179 * @param mixed $a The value to flatten. 180 * @return mixed The flattened value. An empty string if nothing is left. 181 */ 182 function kind_flatten_array( $a ) { 183 if ( ! is_array( $a ) ) { 184 return $a; 185 } 186 if ( wp_is_numeric_array( $a ) ) { 187 $a = array_values( array_filter( array_map( 'kind_flatten_array', $a ) ) ); 188 } else { 189 $a = array_filter( $a ); 190 } 191 if ( empty( $a ) ) { 192 return ''; 193 } 194 if ( 1 === count( $a ) && isset( $a[0] ) ) { 195 return $a[0]; 196 } 113 197 return $a; 114 198 } 115 if ( wp_is_numeric_array( $a ) ) { 116 $array = array_map( 'kind_flatten_array', $a ); 117 } 118 $array = array_filter( $a ); 119 if ( 1 === count( $a ) ) { 120 return $a[0]; 121 } 122 } 123 124 // Return any sort of src urls in content 125 function kind_src_url_in_content( $content ) { 126 if ( ! $content ) { 199 } 200 201 if ( ! function_exists( 'kind_src_url_in_content' ) ) { 202 /** 203 * Returns every src attribute value in some markup. 204 * 205 * @since 3.0.0 206 * 207 * @param string $content The markup. 208 * @return string[]|int The src URLs, or 0 if there are none. 209 */ 210 function kind_src_url_in_content( $content ) { 211 if ( ! $content ) { 212 return 0; 213 } 214 if ( preg_match_all( '@src="([^"]+)"@', $content, $output ) ) { 215 return array_pop( $output ); 216 } 127 217 return 0; 128 218 } 129 if ( preg_match_all( '@src="([^"]+)"@', $content, $output ) ) { 130 return array_pop( $output ); 131 } 132 return 0; 133 } 134 135 /** 136 * Get a Marketed Up Link to the Post. 137 * 138 * @param WP_Post|null Post to Display. 139 * @param string|array Classes to add to link 140 * @param string|array Classes to add to the date 141 * @return string Marked up link to a post. 142 **/ 143 144 function kind_get_the_link( $post = null, $cls = null, $date_cls = null ) { 145 $post = get_post( $post ); 146 $kind = get_post_kind_slug( $post ); 147 148 if ( is_array( $cls ) ) { 149 $cls = implode( ' ', $cls ); 150 } 151 152 if ( is_array( $date_cls ) ) { 153 $date_cls = implode( ' ', $date_cls ); 154 } 155 156 $time_string = '<time class="%3$s" datetime="%1$s">%2$s</time>'; 157 $time_string = sprintf( 158 $time_string, 159 esc_attr( get_the_date( DATE_W3C, $post ) ), 160 get_the_date( '', $post ), 161 $date_cls 162 ); 163 return sprintf( '<a class="%4$s" href="%2$s">%1$s</a> - %3$s', kind_get_the_title( $post, $kind ), get_the_permalink( $post ), $time_string, esc_attr( $cls ) ); 164 } 165 166 167 /** 168 * Get a Generated Title for a Post as Most Post Kinds do Not Have an Explicit Title. 169 * 170 * @param WP_Post|null Post to Display. 171 * @param array $args Arguments. 172 * @return string Marked up link to a post. 173 **/ 174 function kind_get_the_title( $post = null, $args = array() ) { 175 $defaults = array( 176 'photo_size' => array( 32, 32 ), 177 ); 178 179 $args = wp_parse_args( $args, $defaults ); 180 181 $post = get_post( $post ); 182 $kind = get_post_kind_slug( $post ); 183 $title = get_the_title( $post ); 184 $before = Kind_Taxonomy::get_before_kind( $kind ); 185 $content = ''; 186 $kind_post = new Kind_Post( $post ); 187 188 if ( ! empty( $title ) ) { 189 return $title; 190 } 191 192 if ( in_array( $kind, array( 'audio', 'video', 'photo' ), true ) ) { 193 switch ( $kind ) { 194 case 'photo': 195 $photos = $kind_post->get_photo(); 196 $before = wp_get_attachment_image( 197 $photos[0], 198 $args['photo_size'], 199 false, 200 array( 201 'class' => 'kind-photo-thumbnail', 202 ) 203 ); 204 } 205 } elseif ( ! in_array( $kind, array( 'note', 'article' ), true ) ) { 206 $cite = $kind_post->get_cite( 'name' ); 207 if ( false === $cite ) { 208 $content = Kind_View::get_post_type_string( $kind_post->get_cite( 'url' ) ); 219 } 220 221 if ( ! function_exists( 'kind_get_the_link' ) ) { 222 /** 223 * Returns a marked up link to a post, followed by its date. 224 * 225 * @since 3.5.9 226 * 227 * @param WP_Post|int|null $post Optional. Post to display. Default the current post. 228 * @param string|array|null $cls Optional. Classes to add to the link. Default null. 229 * @param string|array|null $date_cls Optional. Classes to add to the date. Default null. 230 * @return string Marked up link to a post. 231 */ 232 function kind_get_the_link( $post = null, $cls = null, $date_cls = null ) { 233 $post = get_post( $post ); 234 $kind = get_post_kind_slug( $post ); 235 236 if ( is_array( $cls ) ) { 237 $cls = implode( ' ', $cls ); 238 } 239 240 if ( is_array( $date_cls ) ) { 241 $date_cls = implode( ' ', $date_cls ); 242 } 243 244 $time_string = '<time class="%3$s" datetime="%1$s">%2$s</time>'; 245 $time_string = sprintf( 246 $time_string, 247 esc_attr( get_the_date( DATE_W3C, $post ) ), 248 esc_html( get_the_date( '', $post ) ), 249 esc_attr( $date_cls ) 250 ); 251 return sprintf( '<a class="%4$s" href="%2$s">%1$s</a> - %3$s', kind_get_the_title( $post, $kind ), esc_url( get_the_permalink( $post ) ), $time_string, esc_attr( $cls ) ); 252 } 253 } 254 255 if ( ! function_exists( 'kind_get_the_title' ) ) { 256 /** 257 * Returns a post's title, generating one if the post has none. 258 * 259 * Most post kinds do not have an explicit title. 260 * 261 * @since 3.5.9 262 * 263 * @param WP_Post|int|null $post Optional. Post to display. Default the current post. 264 * @param array $args { 265 * Optional. Arguments. 266 * 267 * @type int[]|string $photo_size Image size for a photo post's thumbnail. Default array( 32, 32 ). 268 * } 269 * @return string The title, or the kind's icon or name and generated text. 270 */ 271 function kind_get_the_title( $post = null, $args = array() ) { 272 $defaults = array( 273 'photo_size' => array( 32, 32 ), 274 ); 275 276 $args = wp_parse_args( $args, $defaults ); 277 278 $post = get_post( $post ); 279 $kind = get_post_kind_slug( $post ); 280 $title = get_the_title( $post ); 281 $before = Kind_Taxonomy::get_before_kind( $kind ); 282 $content = ''; 283 $kind_post = new Kind_Post( $post ); 284 285 if ( ! empty( $title ) ) { 286 return $title; 287 } 288 289 if ( in_array( $kind, array( 'audio', 'video', 'photo' ), true ) ) { 290 switch ( $kind ) { 291 case 'photo': 292 $photos = $kind_post->get_photo(); 293 $before = wp_get_attachment_image( 294 $photos[0], 295 $args['photo_size'], 296 false, 297 array( 298 'class' => 'kind-photo-thumbnail', 299 ) 300 ); 301 } 302 } elseif ( ! in_array( $kind, array( 'note', 'article' ), true ) ) { 303 $cite = $kind_post->get_cite( 'name' ); 304 if ( false === $cite ) { 305 $content = Kind_View::get_post_type_string( $kind_post->get_cite( 'url' ) ); 306 } else { 307 $content = $cite; 308 } 209 309 } else { 210 $content = $cite; 211 } 212 } else { 213 $content = $post->post_excerpt; 214 // If no excerpt use content 215 if ( ! $content ) { 216 $content = $post->post_content; 217 } 218 // If no content use date 219 if ( $content ) { 220 $content = mb_strimwidth( wp_strip_all_tags( $content ), 0, 40, '...' ); 221 } 222 } 223 if ( is_array( $content ) ) { 224 $content = wp_json_encode( $content ); 225 } 226 227 $content = apply_filters( 'kind_get_the_title_content', $content, $post ); 228 $before = apply_filters( 'kind_get_the_title_before', $before, $post ); 229 230 return trim( sprintf( '%1$s %2$s', $before, $content ) ); 231 } 310 $content = $post->post_excerpt; 311 // If no excerpt use content. 312 if ( ! $content ) { 313 $content = $post->post_content; 314 } 315 // If no content use date. 316 if ( $content ) { 317 $content = mb_strimwidth( wp_strip_all_tags( $content ), 0, 40, '...' ); 318 } 319 } 320 if ( is_array( $content ) ) { 321 $content = wp_json_encode( $content ); 322 } 323 // The content is text (a cited name or an excerpt), often from another site. 324 $content = esc_html( wp_strip_all_tags( (string) $content ) ); 325 326 /** 327 * Filters the text of a generated post title. 328 * 329 * The text is a cited name, an excerpt or similar, already escaped. A callback 330 * that adds markup must escape what it adds. 331 * 332 * @since 3.5.9 333 * 334 * @param string $content Escaped title text. 335 * @param WP_Post $post The post. 336 */ 337 $content = apply_filters( 'kind_get_the_title_content', $content, $post ); 338 /** 339 * Filters the markup shown before a generated post title. 340 * 341 * The markup is output as-is, so it must be escaped. 342 * 343 * @since 3.5.9 344 * 345 * @param string $before The kind's icon or name, from Kind_Taxonomy::get_before_kind(). 346 * @param WP_Post $post The post. 347 */ 348 $before = apply_filters( 'kind_get_the_title_before', $before, $post ); 349 350 return trim( sprintf( '%1$s %2$s', $before, $content ) ); 351 } 352 } -
indieweb-post-kinds/tags/4.0.0/includes/register-kinds.php
r3001401 r3738687 1 1 <?php 2 /* Registers built-in Post Kinds 2 /** 3 * Registers the built-in kinds. 4 * 5 * The arguments each kind takes are documented in 6 * Kind_Taxonomy::register_post_kind(). 7 * 8 * @package Post_Kinds 9 * @since 3.3.4 3 10 */ 4 11 12 defined( 'ABSPATH' ) || exit; 13 5 14 register_post_kind( 6 15 'article', 7 16 array( 8 'singular_name' => __( 'Article', 'indieweb-post-kinds' ), // Name for one instance of the kind9 'name' => __( 'Articles', 'indieweb-post-kinds' ), // General name for the kind plural10 'verb' => ' ', // The string for the verb or action (liked this)11 'property' => '', // microformats 2 property17 'singular_name' => __( 'Article', 'indieweb-post-kinds' ), 18 'name' => __( 'Articles', 'indieweb-post-kinds' ), 19 'verb' => ' ', 20 'property' => '', 12 21 'properties' => array(), 13 'format' => '', // Post Format that maps to this22 'format' => '', 14 23 'description' => __( 'traditional long form content: a post with an explicit title and body', 'indieweb-post-kinds' ), 15 24 'description_url' => 'http://indieweb.org/article', 16 25 'shortlink' => 'b', 17 'title' => true, // Should this kind have an explicit title18 'show' => true, // Show in Settings26 'title' => true, 27 'show' => true, 19 28 ) 20 29 ); … … 23 32 'note', 24 33 array( 25 'singular_name' => __( 'Note', 'indieweb-post-kinds' ), // Name for one instance of the kind26 'name' => __( 'Notes', 'indieweb-post-kinds' ), // General name for the kind plural27 'verb' => ' ', // The string for the verb or action (liked this)28 'property' => '', // microformats 2 property34 'singular_name' => __( 'Note', 'indieweb-post-kinds' ), 35 'name' => __( 'Notes', 'indieweb-post-kinds' ), 36 'verb' => ' ', 37 'property' => '', 29 38 'properties' => array(), 30 'format' => 'aside', // Post Format that maps to this39 'format' => 'aside', 31 40 'description' => __( 'short content: a post or status update with just plain content and typically without a title', 'indieweb-post-kinds' ), 32 41 'description_url' => 'http://indieweb.org/note', 33 42 'shortlink' => 't', 34 'title' => false, // Should this kind have an explicit title35 'show' => false, // Show in Settings43 'title' => false, 44 'show' => false, 36 45 ) 37 46 ); … … 40 49 'reply', 41 50 array( 42 'singular_name' => __( 'Reply', 'indieweb-post-kinds' ), // Name for one instance of the kind43 'name' => __( 'Replies', 'indieweb-post-kinds' ), // General name for the kind plural44 'verb' => __( 'Replied to', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)45 'property' => 'in-reply-to', // microformats 2 property51 'singular_name' => __( 'Reply', 'indieweb-post-kinds' ), 52 'name' => __( 'Replies', 'indieweb-post-kinds' ), 53 'verb' => __( 'Replied to', 'indieweb-post-kinds' ), 54 'property' => 'in-reply-to', 46 55 'properties' => array( 47 56 'in-reply-to' => array( … … 97 106 ), 98 107 ), 99 'format' => 'link', // Post Format that maps to this108 'format' => 'link', 100 109 'description' => __( 'a reply to content typically on another site', 'indieweb-post-kinds' ), 101 110 'description_url' => 'http://indieweb.org/reply', 102 111 'shortlink' => 't', 103 'title' => false, // Should this kind have an explicit title104 'show' => true, // Show in Settings112 'title' => false, 113 'show' => true, 105 114 ) 106 115 ); … … 109 118 'repost', 110 119 array( 111 'singular_name' => __( 'Repost', 'indieweb-post-kinds' ), // Name for one instance of the kind112 'name' => __( 'Reposts', 'indieweb-post-kinds' ), // General name for the kind plural113 'verb' => __( 'Reposted', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)114 'property' => 'repost-of', // microformats 2 property120 'singular_name' => __( 'Repost', 'indieweb-post-kinds' ), 121 'name' => __( 'Reposts', 'indieweb-post-kinds' ), 122 'verb' => __( 'Reposted', 'indieweb-post-kinds' ), 123 'property' => 'repost-of', 115 124 'properties' => array( 116 125 'repost-of' => array( … … 166 175 ), 167 176 ), 168 'format' => '', // Post Format that maps to this177 'format' => '', 169 178 'description' => __( 'a complete reposting of content from another site', 'indieweb-post-kinds' ), 170 179 'description_url' => 'http://indieweb.org/repost', 171 180 'shortlink' => 'b', 172 'title' => true, // Should this kind have an explicit title173 'show' => true, // Show in Settings181 'title' => true, 182 'show' => true, 174 183 ) 175 184 ); … … 178 187 'like', 179 188 array( 180 'singular_name' => __( 'Like', 'indieweb-post-kinds' ), // Name for one instance of the kind181 'name' => __( 'Likes', 'indieweb-post-kinds' ), // General name for the kind plural182 'verb' => __( 'Liked', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)183 'property' => 'like-of', // microformats 2 property189 'singular_name' => __( 'Like', 'indieweb-post-kinds' ), 190 'name' => __( 'Likes', 'indieweb-post-kinds' ), 191 'verb' => __( 'Liked', 'indieweb-post-kinds' ), 192 'property' => 'like-of', 184 193 'properties' => array( 185 194 'like-of' => array( … … 235 244 ), 236 245 ), 237 'format' => 'link', // Post Format that maps to this246 'format' => 'link', 238 247 'description' => __( 'a way to pay compliments to the original post/poster of external content', 'indieweb-post-kinds' ), 239 248 'description_url' => 'http://indieweb.org/like', 240 249 'shortlink' => 'f', 241 'title' => false, // Should this kind have an explicit title242 'show' => true, // Show in Settings250 'title' => false, 251 'show' => true, 243 252 ) 244 253 ); … … 247 256 'favorite', 248 257 array( 249 'singular_name' => __( 'Favorite', 'indieweb-post-kinds' ), // Name for one instance of the kind250 'name' => __( 'Favorites', 'indieweb-post-kinds' ), // General name for the kind plural251 'verb' => __( 'Favorited', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)252 'property' => 'favorite-of', // microformats 2 property258 'singular_name' => __( 'Favorite', 'indieweb-post-kinds' ), 259 'name' => __( 'Favorites', 'indieweb-post-kinds' ), 260 'verb' => __( 'Favorited', 'indieweb-post-kinds' ), 261 'property' => 'favorite-of', 253 262 'properties' => array( 254 263 'favorite-of' => array( … … 304 313 ), 305 314 ), 306 'format' => 'link', // Post Format that maps to this315 'format' => 'link', 307 316 'description' => __( 'special to the author', 'indieweb-post-kinds' ), 308 317 'description_url' => 'http://indieweb.org/favorite', 309 318 'shortlink' => 'f', 310 'title' => false, // Should this kind have an explicit title311 'show' => true, // Show in Settings319 'title' => false, 320 'show' => true, 312 321 ) 313 322 ); … … 316 325 'bookmark', 317 326 array( 318 'singular_name' => __( 'Bookmark', 'indieweb-post-kinds' ), // Name for one instance of the kind319 'name' => __( 'Bookmarks', 'indieweb-post-kinds' ), // General name for the kind plural320 'verb' => __( 'Bookmarked', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)321 'property' => 'bookmark-of', // microformats 2 property327 'singular_name' => __( 'Bookmark', 'indieweb-post-kinds' ), 328 'name' => __( 'Bookmarks', 'indieweb-post-kinds' ), 329 'verb' => __( 'Bookmarked', 'indieweb-post-kinds' ), 330 'property' => 'bookmark-of', 322 331 'properties' => array( 323 332 'bookmark-of' => array( … … 373 382 ), 374 383 ), 375 'format' => 'link', // Post Format that maps to this384 'format' => 'link', 376 385 'description' => __( 'storing a link/bookmark for personal use or sharing with others', 'indieweb-post-kinds' ), 377 386 'description_url' => 'http://indieweb.org/bookmark', 378 387 'shortlink' => 'h', 379 'title' => false, // Should this kind have an explicit title380 'show' => true, // Show in Settings388 'title' => false, 389 'show' => true, 381 390 ) 382 391 ); … … 385 394 'photo', 386 395 array( 387 'singular_name' => __( 'Photo', 'indieweb-post-kinds' ), // Name for one instance of the kind388 'name' => __( 'Photos', 'indieweb-post-kinds' ), // General name for the kind plural389 'verb' => ' ', // The string for the verb or action (liked this)390 'property' => 'photo', // microformats 2 property391 'format' => 'image', // Post Format that maps to this396 'singular_name' => __( 'Photo', 'indieweb-post-kinds' ), 397 'name' => __( 'Photos', 'indieweb-post-kinds' ), 398 'verb' => ' ', 399 'property' => 'photo', 400 'format' => 'image', 392 401 'description' => __( 'a post with an embedded image/photo as its primary focus', 'indieweb-post-kinds' ), 393 402 'description_url' => 'http://indieweb.org/photo', 394 403 'shortlink' => 'p', 395 'title' => false, // Should this kind have an explicit title396 'show' => true, // Show in Settings404 'title' => false, 405 'show' => true, 397 406 ) 398 407 ); … … 401 410 'video', 402 411 array( 403 'singular_name' => __( 'Video', 'indieweb-post-kinds' ), // Name for one instance of the kind404 'name' => __( 'Videos', 'indieweb-post-kinds' ), // General name for the kind plural405 'verb' => ' ', // The string for the verb or action (liked this)406 'property' => 'video', // microformats 2 property407 'format' => 'video', // Post Format that maps to this412 'singular_name' => __( 'Video', 'indieweb-post-kinds' ), 413 'name' => __( 'Videos', 'indieweb-post-kinds' ), 414 'verb' => ' ', 415 'property' => 'video', 416 'format' => 'video', 408 417 'description' => __( 'a post with an embedded video as its primary focus', 'indieweb-post-kinds' ), 409 418 'description_url' => 'http://indieweb.org/video', 410 419 'shortlink' => 'a', 411 'title' => false, // Should this kind have an explicit title412 'show' => true, // Show in Settings420 'title' => false, 421 'show' => true, 413 422 ) 414 423 ); … … 417 426 'audio', 418 427 array( 419 'singular_name' => __( 'Audio', 'indieweb-post-kinds' ), // Name for one instance of the kind420 'name' => __( 'Audios', 'indieweb-post-kinds' ), // General name for the kind plural421 'verb' => ' ', // The string for the verb or action (liked this)422 'property' => 'audio', // microformats 2 property423 'format' => 'audio', // Post Format that maps to this428 'singular_name' => __( 'Audio', 'indieweb-post-kinds' ), 429 'name' => __( 'Audios', 'indieweb-post-kinds' ), 430 'verb' => ' ', 431 'property' => 'audio', 432 'format' => 'audio', 424 433 'description' => __( 'a post with an embedded audio file as its primary focus', 'indieweb-post-kinds' ), 425 434 'description_url' => 'http://indieweb.org/audio', 426 435 'shortlink' => 'a', 427 'title' => false, // Should this kind have an explicit title428 'show' => true, // Show in Settings436 'title' => false, 437 'show' => true, 429 438 ) 430 439 ); … … 433 442 'tag', 434 443 array( 435 'singular_name' => __( 'Tag', 'indieweb-post-kinds' ), // Name for one instance of the kind436 'name' => __( 'Tags', 'indieweb-post-kinds' ), // General name for the kind plural437 'verb' => __( 'Tagged', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)438 'property' => 'tag-of', // microformats 2 property439 'format' => 'link', // Post Format that maps to this444 'singular_name' => __( 'Tag', 'indieweb-post-kinds' ), 445 'name' => __( 'Tags', 'indieweb-post-kinds' ), 446 'verb' => __( 'Tagged', 'indieweb-post-kinds' ), 447 'property' => 'tag-of', 448 'format' => 'link', 440 449 'description' => __( 'allows you to tag a post as being of a specific category or tag, or for person tagging', 'indieweb-post-kinds' ), 441 450 'description_url' => 'http://indieweb.org/tag', 442 451 'shortlink' => 'd', 443 'title' => false, // Should this kind have an explicit title444 'show' => false, // Show in Settings452 'title' => false, 453 'show' => false, 445 454 ) 446 455 ); … … 449 458 'rsvp', 450 459 array( 451 'singular_name' => __( 'RSVP', 'indieweb-post-kinds' ), // Name for one instance of the kind452 'name' => __( 'RSVPs', 'indieweb-post-kinds' ), // General name for the kind plural453 'verb' => __( 'RSVPed', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)454 'property' => 'in-reply-to', // microformats 2 property460 'singular_name' => __( 'RSVP', 'indieweb-post-kinds' ), 461 'name' => __( 'RSVPs', 'indieweb-post-kinds' ), 462 'verb' => __( 'RSVPed', 'indieweb-post-kinds' ), 463 'property' => 'in-reply-to', 455 464 'properties' => array( 456 465 'rsvp' => array( … … 509 518 ), 510 519 ), 511 'format' => 'link', // Post Format that maps to this520 'format' => 'link', 512 521 'description' => __( 'a specific type of reply regarding attendance of an event', 'indieweb-post-kinds' ), 513 522 'description_url' => 'http://indieweb.org/rsvp', 514 523 'shortlink' => 't', 515 'title' => false, // Should this kind have an explicit title516 'show' => true, // Show in Settings524 'title' => false, 525 'show' => true, 517 526 ) 518 527 ); … … 521 530 'listen', 522 531 array( 523 'singular_name' => __( 'Listen', 'indieweb-post-kinds' ), // Name for one instance of the kind524 'name' => __( 'Listens', 'indieweb-post-kinds' ), // General name for the kind plural525 'verb' => __( 'Listened', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)526 'property' => 'listen-of', // microformats 2 property532 'singular_name' => __( 'Listen', 'indieweb-post-kinds' ), 533 'name' => __( 'Listens', 'indieweb-post-kinds' ), 534 'verb' => __( 'Listened', 'indieweb-post-kinds' ), 535 'property' => 'listen-of', 527 536 'properties' => array( 528 537 'listen-of' => array( … … 574 583 ), 575 584 ), 576 'format' => 'audio', // Post Format that maps to this585 'format' => 'audio', 577 586 'description' => __( 'listening to audio; sometimes called a scrobble', 'indieweb-post-kinds' ), 578 587 'description_url' => 'http://indieweb.org/listen', 579 588 'shortlink' => 'x', 580 'title' => false, // Should this kind have an explicit title581 'show' => true, // Show in Settings589 'title' => false, 590 'show' => true, 582 591 ) 583 592 ); … … 586 595 'watch', 587 596 array( 588 'singular_name' => __( 'Watch', 'indieweb-post-kinds' ), // Name for one instance of the kind589 'name' => __( 'Watches', 'indieweb-post-kinds' ), // General name for the kind plural590 'verb' => __( 'Watched', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)591 'property' => 'watch-of', // microformats 2 property597 'singular_name' => __( 'Watch', 'indieweb-post-kinds' ), 598 'name' => __( 'Watches', 'indieweb-post-kinds' ), 599 'verb' => __( 'Watched', 'indieweb-post-kinds' ), 600 'property' => 'watch-of', 592 601 'properties' => array( 593 602 'watch-of' => array( … … 639 648 ), 640 649 ), 641 'format' => 'video', // Post Format that maps to this650 'format' => 'video', 642 651 'description' => __( 'watching a movie, television show, online video, play or other visual-based event', 'indieweb-post-kinds' ), 643 652 'description_url' => 'http://indieweb.org/watch', 644 653 'shortlink' => 'x', 645 'title' => false, // Should this kind have an explicit title646 'show' => true, // Show in Settings654 'title' => false, 655 'show' => true, 647 656 ) 648 657 ); … … 651 660 'checkin', 652 661 array( 653 'singular_name' => __( 'Checkin', 'indieweb-post-kinds' ), // Name for one instance of the kind654 'name' => __( 'Checkins', 'indieweb-post-kinds' ), // General name for the kind plural655 'verb' => __( 'Checked into', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)656 'property' => 'checkin', // microformats 2 property662 'singular_name' => __( 'Checkin', 'indieweb-post-kinds' ), 663 'name' => __( 'Checkins', 'indieweb-post-kinds' ), 664 'verb' => __( 'Checked into', 'indieweb-post-kinds' ), 665 'property' => 'checkin', 657 666 'properties' => array( 658 667 'checkin' => array( … … 707 716 ), 708 717 ), 709 'format' => 'status', // Post Format that maps to this718 'format' => 'status', 710 719 'description' => __( 'identifying you are at a particular geographic location', 'indieweb-post-kinds' ), 711 720 'description_url' => 'http://indieweb.org/checkin', 712 721 'shortlink' => 'g', 713 'title' => false, // Should this kind have an explicit title714 'show' => true, // Show in Settings722 'title' => false, 723 'show' => true, 715 724 ) 716 725 ); … … 719 728 'wish', 720 729 array( 721 'singular_name' => __( 'Wish', 'indieweb-post-kinds' ), // Name for one instance of the kind722 'name' => __( 'Wishes', 'indieweb-post-kinds' ), // General name for the kind plural723 'verb' => __( 'Wished', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)724 'property' => 'wish-of', // microformats 2 property725 'format' => 'link', // Post Format that maps to this730 'singular_name' => __( 'Wish', 'indieweb-post-kinds' ), 731 'name' => __( 'Wishes', 'indieweb-post-kinds' ), 732 'verb' => __( 'Wished', 'indieweb-post-kinds' ), 733 'property' => 'wish-of', 734 'format' => 'link', 726 735 'description' => __( 'a post indicating a desire/wish. The archive of which would be a wishlist, such as a gift registry or similar', 'indieweb-post-kinds' ), 727 736 'description_url' => '', 728 737 'shortlink' => 'f', 729 'title' => false, // Should this kind have an explicit title730 'show' => false, // Show in Settings738 'title' => false, 739 'show' => false, 731 740 ) 732 741 ); … … 735 744 'play', 736 745 array( 737 'singular_name' => __( 'Play', 'indieweb-post-kinds' ), // Name for one instance of the kind738 'name' => __( 'Playing', 'indieweb-post-kinds' ), // General name for the kind plural739 'verb' => __( 'Played', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)740 'property' => 'play-of', // microformats 2 property741 'format' => 'status', // Post Format that maps to this746 'singular_name' => __( 'Play', 'indieweb-post-kinds' ), 747 'name' => __( 'Playing', 'indieweb-post-kinds' ), 748 'verb' => __( 'Played', 'indieweb-post-kinds' ), 749 'property' => 'play-of', 750 'format' => 'status', 742 751 'description' => __( 'playing a game', 'indieweb-post-kinds' ), 743 752 'description_url' => 'https://indieweb.org/game_play', 744 753 'shortlink' => 'x', 745 'title' => false, // Should this kind have an explicit title746 'show' => true, // Show in Settings754 'title' => false, 755 'show' => true, 747 756 ) 748 757 ); … … 751 760 'weather', 752 761 array( 753 'singular_name' => __( 'Weather', 'indieweb-post-kinds' ), // Name for one instance of the kind754 'name' => __( 'Weather', 'indieweb-post-kinds' ), // General name for the kind plural755 'verb' => ' ', // The string for the verb or action (liked this)756 'property' => 'weather', // microformats 2 property757 'format' => 'status', // Post Format that maps to this762 'singular_name' => __( 'Weather', 'indieweb-post-kinds' ), 763 'name' => __( 'Weather', 'indieweb-post-kinds' ), 764 'verb' => ' ', 765 'property' => 'weather', 766 'format' => 'status', 758 767 'description' => __( 'current weather conditions', 'indieweb-post-kinds' ), 759 768 'description_url' => 'https://indieweb.org/weather', 760 769 'shortlink' => 'u', 761 'title' => false, // Should this kind have an explicit title762 'show' => false, // Show in Settings770 'title' => false, 771 'show' => false, 763 772 ) 764 773 ); … … 767 776 'exercise', 768 777 array( 769 'singular_name' => __( 'Exercise', 'indieweb-post-kinds' ), // Name for one instance of the kind770 'name' => __( 'Exercise', 'indieweb-post-kinds' ), // General name for the kind plural771 'verb' => __( 'Exercised', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)772 'property' => 'exercise', // microformats 2 property773 'format' => 'status', // Post Format that maps to this778 'singular_name' => __( 'Exercise', 'indieweb-post-kinds' ), 779 'name' => __( 'Exercise', 'indieweb-post-kinds' ), 780 'verb' => __( 'Exercised', 'indieweb-post-kinds' ), 781 'property' => 'exercise', 782 'format' => 'status', 774 783 'description' => __( 'some form of physical activity or workout (examples: walk, run, cycle, hike, yoga, etc.)', 'indieweb-post-kinds' ), 775 784 'description_url' => 'https://indieweb.org/exercise', 776 785 'shortlink' => 'm', 777 'title' => false, // Should this kind have an explicit title778 'show' => false, // Show in Settings786 'title' => false, 787 'show' => false, 779 788 ) 780 789 ); … … 783 792 'trip', 784 793 array( 785 'singular_name' => __( 'Trip', 'indieweb-post-kinds' ), // Name for one instance of the kind786 'name' => __( 'Trips', 'indieweb-post-kinds' ), // General name for the kind plural787 'verb' => __( 'Travelled', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)788 'property' => 'trip', // microformats 2 property789 'format' => '', // Post Format that maps to this794 'singular_name' => __( 'Trip', 'indieweb-post-kinds' ), 795 'name' => __( 'Trips', 'indieweb-post-kinds' ), 796 'verb' => __( 'Travelled', 'indieweb-post-kinds' ), 797 'property' => 'trip', 798 'format' => '', 790 799 'description' => __( 'represents a geographic journey', 'indieweb-post-kinds' ), 791 800 'description_url' => 'https://indieweb.org/trip', 792 801 'shortlink' => 'e', 793 'title' => false, // Should this kind have an explicit title794 'show' => false, // Show in Settings802 'title' => false, 803 'show' => false, 795 804 ) 796 805 ); … … 799 808 'itinerary', 800 809 array( 801 'singular_name' => __( 'Itinerary', 'indieweb-post-kinds' ), // Name for one instance of the kind802 'name' => __( 'Itineraries', 'indieweb-post-kinds' ), // General name for the kind plural803 'verb' => __( 'Travelled', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)804 'property' => 'itinerary', // microformats 2 property805 'format' => '', // Post Format that maps to this810 'singular_name' => __( 'Itinerary', 'indieweb-post-kinds' ), 811 'name' => __( 'Itineraries', 'indieweb-post-kinds' ), 812 'verb' => __( 'Travelled', 'indieweb-post-kinds' ), 813 'property' => 'itinerary', 814 'format' => '', 806 815 'description' => __( 'parts of a scheduled trip including transit by car, plane, train, etc.', 'indieweb-post-kinds' ), 807 816 'description_url' => 'https://indieweb.org/trip', 808 817 'shortlink' => 'e', 809 'title' => false, // Should this kind have an explicit title810 'show' => false, // Show in Settings818 'title' => false, 819 'show' => false, 811 820 ) 812 821 ); … … 816 825 'eat', 817 826 array( 818 'singular_name' => __( 'Eat', 'indieweb-post-kinds' ), // Name for one instance of the kind819 'name' => __( 'Eat', 'indieweb-post-kinds' ), // General name for the kind plural820 'verb' => __( 'Ate', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)821 'property' => 'ate', // microformats 2 property822 'format' => 'status', // Post Format that maps to this827 'singular_name' => __( 'Eat', 'indieweb-post-kinds' ), 828 'name' => __( 'Eat', 'indieweb-post-kinds' ), 829 'verb' => __( 'Ate', 'indieweb-post-kinds' ), 830 'property' => 'ate', 831 'format' => 'status', 823 832 'description' => __( 'what you are eating, perhaps for a food diary', 'indieweb-post-kinds' ), 824 833 'description_url' => 'https://indieweb.org/food', 825 834 'shortlink' => 'u', 826 'title' => false, // Should this kind have an explicit title827 'show' => true, // Show in Settings835 'title' => false, 836 'show' => true, 828 837 ) 829 838 ); … … 833 842 'drink', 834 843 array( 835 'singular_name' => __( 'Drink', 'indieweb-post-kinds' ), // Name for one instance of the kind836 'name' => __( 'Drinks', 'indieweb-post-kinds' ), // General name for the kind plural837 'verb' => __( 'Drank', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)838 'property' => 'drank', // microformats 2 property839 'format' => 'status', // Post Format that maps to this844 'singular_name' => __( 'Drink', 'indieweb-post-kinds' ), 845 'name' => __( 'Drinks', 'indieweb-post-kinds' ), 846 'verb' => __( 'Drank', 'indieweb-post-kinds' ), 847 'property' => 'drank', 848 'format' => 'status', 840 849 'description' => __( 'what you are drinking, perhaps for a food dairy', 'indieweb-post-kinds' ), 841 850 'description_url' => 'https://indieweb.org/food', 842 851 'shortlink' => 'u', 843 'title' => false, // Should this kind have an explicit title844 'show' => true, // Show in Settings852 'title' => false, 853 'show' => true, 845 854 ) 846 855 ); … … 850 859 'follow', 851 860 array( 852 'singular_name' => __( 'Follow', 'indieweb-post-kinds' ), // Name for one instance of the kind853 'name' => __( 'Follows', 'indieweb-post-kinds' ), // General name for the kind plural854 'verb' => __( 'Followed', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)855 'property' => 'follow-of', // microformats 2 property856 'format' => '', // Post Format that maps to this861 'singular_name' => __( 'Follow', 'indieweb-post-kinds' ), 862 'name' => __( 'Follows', 'indieweb-post-kinds' ), 863 'verb' => __( 'Followed', 'indieweb-post-kinds' ), 864 'property' => 'follow-of', 865 'format' => '', 857 866 'description' => __( 'indicating you are now following or subscribing to another person`s activities online', 'indieweb-post-kinds' ), 858 867 'description_url' => 'https://indieweb.org/follow', 859 868 'shortlink' => 'f', 860 'title' => false, // Should this kind have an explicit title861 'show' => false, // Show in Settings869 'title' => false, 870 'show' => false, 862 871 ) 863 872 ); … … 867 876 'jam', 868 877 array( 869 'singular_name' => __( 'Jam', 'indieweb-post-kinds' ), // Name for one instance of the kind870 'name' => __( 'Jams', 'indieweb-post-kinds' ), // General name for the kind plural871 'verb' => __( 'Listened to', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)872 'property' => 'jam-of', // microformats 2 property873 'format' => 'audio', // Post Format that maps to this878 'singular_name' => __( 'Jam', 'indieweb-post-kinds' ), 879 'name' => __( 'Jams', 'indieweb-post-kinds' ), 880 'verb' => __( 'Listened to', 'indieweb-post-kinds' ), 881 'property' => 'jam-of', 882 'format' => 'audio', 874 883 'description' => __( 'a particularly personally meaningful song (a listen with added emphasis)', 'indieweb-post-kinds' ), 875 884 'description_url' => 'https://indieweb.org/jam', 876 885 'shortlink' => 'f', 877 'title' => false, // Should this kind have an explicit title878 'show' => true, // Show in Settings886 'title' => false, 887 'show' => true, 879 888 ) 880 889 ); … … 884 893 'read', 885 894 array( 886 'singular_name' => __( 'Read', 'indieweb-post-kinds' ), // Name for one instance of the kind887 'name' => __( 'Reads', 'indieweb-post-kinds' ), // General name for the kind plural888 'verb' => __( 'Read', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)889 'property' => 'read-of', // microformats 2 property890 'format' => 'status', // Post Format that maps to this895 'singular_name' => __( 'Read', 'indieweb-post-kinds' ), 896 'name' => __( 'Reads', 'indieweb-post-kinds' ), 897 'verb' => __( 'Read', 'indieweb-post-kinds' ), 898 'property' => 'read-of', 899 'format' => 'status', 891 900 'description' => __( 'reading a book, magazine, newspaper, other physical document, or online post', 'indieweb-post-kinds' ), 892 901 'description_url' => 'https://indieweb.org/read', 893 902 'shortlink' => 'x', 894 'title' => false, // Should this kind have an explicit title895 'show' => true, // Show in Settings903 'title' => false, 904 'show' => true, 896 905 ) 897 906 ); … … 901 910 'quote', 902 911 array( 903 'singular_name' => __( 'Quote', 'indieweb-post-kinds' ), // Name for one instance of the kind904 'name' => __( 'Quotes', 'indieweb-post-kinds' ), // General name for the kind plural905 'verb' => __( 'Quoted', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)906 'property' => 'quotation-of', // microformats 2 property907 'format' => 'quote', // Post Format that maps to this912 'singular_name' => __( 'Quote', 'indieweb-post-kinds' ), 913 'name' => __( 'Quotes', 'indieweb-post-kinds' ), 914 'verb' => __( 'Quoted', 'indieweb-post-kinds' ), 915 'property' => 'quotation-of', 916 'format' => 'quote', 908 917 'description' => __( 'quoted content', 'indieweb-post-kinds' ), 909 918 'description_url' => 'https://indieweb.org/quote', 910 919 'shortlink' => 't', 911 'title' => false, // Should this kind have an explicit title912 'show' => true, // Show in Settings920 'title' => false, 921 'show' => true, 913 922 ) 914 923 ); … … 918 927 'mood', 919 928 array( 920 'singular_name' => __( 'Mood', 'indieweb-post-kinds' ), // Name for one instance of the kind921 'name' => __( 'Moods', 'indieweb-post-kinds' ), // General name for the kind plural922 'verb' => __( 'Felt', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)923 'property' => 'mood', // microformats 2 property924 'format' => 'status', // Post Format that maps to this929 'singular_name' => __( 'Mood', 'indieweb-post-kinds' ), 930 'name' => __( 'Moods', 'indieweb-post-kinds' ), 931 'verb' => __( 'Felt', 'indieweb-post-kinds' ), 932 'property' => 'mood', 933 'format' => 'status', 925 934 'description' => __( 'how you are feeling (example: happy, sad, indifferent, etc.)', 'indieweb-post-kinds' ), 926 935 'description_url' => 'https://indieweb.org/mood', 927 936 'shortlink' => 't', 928 'title' => false, // Should this kind have an explicit title929 'show' => false, // Show in Settings937 'title' => false, 938 'show' => false, 930 939 ) 931 940 ); … … 935 944 'recipe', 936 945 array( 937 'singular_name' => __( 'Recipe', 'indieweb-post-kinds' ), // Name for one instance of the kind938 'name' => __( 'Recipes', 'indieweb-post-kinds' ), // General name for the kind plural939 'verb' => __( 'Cooked', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)940 'property' => 'recipe', // microformats 2 property941 'format' => '', // Post Format that maps to this946 'singular_name' => __( 'Recipe', 'indieweb-post-kinds' ), 947 'name' => __( 'Recipes', 'indieweb-post-kinds' ), 948 'verb' => __( 'Cooked', 'indieweb-post-kinds' ), 949 'property' => 'recipe', 950 'format' => '', 942 951 'description' => __( 'list of ingredients and directions for making food or drink', 'indieweb-post-kinds' ), 943 952 'description_url' => 'https://indieweb.org/recipe', 944 953 'shortlink' => 'b', 945 'title' => true, // Should this kind have an explicit title946 'show' => false, // Show in Settings954 'title' => true, 955 'show' => false, 947 956 ) 948 957 ); … … 952 961 'issue', 953 962 array( 954 'singular_name' => __( 'Issue', 'indieweb-post-kinds' ), // Name for one instance of the kind955 'name' => __( 'Issues', 'indieweb-post-kinds' ), // General name for the kind plural956 'verb' => __( 'Filed an Issue', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)957 'property' => 'in-reply-to', // microformats 2 property958 'format' => '', // Post Format that maps to this963 'singular_name' => __( 'Issue', 'indieweb-post-kinds' ), 964 'name' => __( 'Issues', 'indieweb-post-kinds' ), 965 'verb' => __( 'Filed an Issue', 'indieweb-post-kinds' ), 966 'property' => 'in-reply-to', 967 'format' => '', 959 968 'description' => __( 'Issue is a special kind of article post that is a reply to typically some source code, though potentially anything at a source control repository.', 'indieweb-post-kinds' ), 960 969 'description_url' => 'https://indieweb.org/issue', 961 970 'shortlink' => 't', 962 'title' => true, // Should this kind have an explicit title963 'show' => true, // Show in Settings971 'title' => true, 972 'show' => true, 964 973 ) 965 974 ); … … 969 978 'question', 970 979 array( 971 'singular_name' => __( 'Question', 'indieweb-post-kinds' ), // Name for one instance of the kind972 'name' => __( 'Questions', 'indieweb-post-kinds' ), // General name for the kind plural973 'verb' => __( 'Asked a question', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)974 'property' => 'question', // microformats 2 property975 'format' => '', // Post Format that maps to this980 'singular_name' => __( 'Question', 'indieweb-post-kinds' ), 981 'name' => __( 'Questions', 'indieweb-post-kinds' ), 982 'verb' => __( 'Asked a question', 'indieweb-post-kinds' ), 983 'property' => 'question', 984 'format' => '', 976 985 'description' => __( 'Question is a post type for soliciting answer replies, which are then typically up/down voted by others and then displayed underneath the question post ordered by highest positive vote count rather than time ordered.', 'indieweb-post-kinds' ), 977 986 'description_url' => 'https://indieweb.org/question', 978 987 'shortlink' => 'q', 979 'title' => false, // Should this kind have an explicit title980 'show' => false, // Show in Settings988 'title' => false, 989 'show' => false, 981 990 ) 982 991 ); … … 986 995 'sleep', 987 996 array( 988 'singular_name' => __( 'Sleep', 'indieweb-post-kinds' ), // Name for one instance of the kind989 'name' => __( 'Sleeps', 'indieweb-post-kinds' ), // General name for the kind plural990 'verb' => __( 'Slept', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)991 'property' => 'sleep', // microformats 2 property992 'format' => '', // Post Format that maps to this997 'singular_name' => __( 'Sleep', 'indieweb-post-kinds' ), 998 'name' => __( 'Sleeps', 'indieweb-post-kinds' ), 999 'verb' => __( 'Slept', 'indieweb-post-kinds' ), 1000 'property' => 'sleep', 1001 'format' => '', 993 1002 'description' => __( 'Sleep is a passive metrics post type that indicates how much time (and often a graph of how deeply) a person has slept.', 'indieweb-post-kinds' ), 994 1003 'description_url' => 'https://indieweb.org/sleep', 995 1004 'shortlink' => 'm', 996 'title' => false, // Should this kind have an explicit title997 'show' => false, // Show in Settings1005 'title' => false, 1006 'show' => false, 998 1007 ) 999 1008 ); … … 1003 1012 'event', 1004 1013 array( 1005 'singular_name' => __( 'Event', 'indieweb-post-kinds' ), // Name for one instance of the kind1006 'name' => __( 'Events', 'indieweb-post-kinds' ), // General name for the kind plural1007 'verb' => __( 'Planned', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)1008 'property' => 'event', // microformats 2 property1009 'format' => '', // Post Format that maps to this1014 'singular_name' => __( 'Event', 'indieweb-post-kinds' ), 1015 'name' => __( 'Events', 'indieweb-post-kinds' ), 1016 'verb' => __( 'Planned', 'indieweb-post-kinds' ), 1017 'property' => 'event', 1018 'format' => '', 1010 1019 'description' => __( 'An event is a type of post that in addition to a post name (event title) has a start datetime (likely end datetime), and a location.', 'indieweb-post-kinds' ), 1011 1020 'description_url' => 'https://indieweb.org/event', 1012 1021 'shortlink' => 'e', 1013 'title' => true, // Should this kind have an explicit title1014 'show' => false, // Show in Settings1022 'title' => true, 1023 'show' => false, 1015 1024 ) 1016 1025 ); … … 1019 1028 'review', 1020 1029 array( 1021 'singular_name' => __( 'Review', 'indieweb-post-kinds' ), // Name for one instance of the kind1022 'name' => __( 'Reviews', 'indieweb-post-kinds' ), // General name for the kind plural1023 'verb' => __( 'Reviewed', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)1024 'property' => 'review-of', // microformats 2 property1025 'format' => '', // Post Format that maps to this1030 'singular_name' => __( 'Review', 'indieweb-post-kinds' ), 1031 'name' => __( 'Reviews', 'indieweb-post-kinds' ), 1032 'verb' => __( 'Reviewed', 'indieweb-post-kinds' ), 1033 'property' => 'review-of', 1034 'format' => '', 1026 1035 'description' => __( 'A review is a post evaluating a product or service, usually involving a written description, sometimes with summary numerical evaluations, also known as just a rating.', 'indieweb-post-kinds' ), 1027 1036 'description_url' => 'https://indieweb.org/review', 1028 1037 'shortlink' => 't', 1029 'title' => false, // Should this kind have an explicit title1030 'show' => true, // Show in Settings1038 'title' => false, 1039 'show' => true, 1031 1040 ) 1032 1041 ); … … 1036 1045 'acquisition', 1037 1046 array( 1038 'singular_name' => __( 'Acquisition', 'indieweb-post-kinds' ), // Name for one instance of the kind1039 'name' => __( 'Acquisitions', 'indieweb-post-kinds' ), // General name for the kind plural1040 'verb' => __( 'Acquired', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)1041 'property' => 'acquired-of', // microformats 2 property1042 'format' => 'status', // Post Format that maps to this1047 'singular_name' => __( 'Acquisition', 'indieweb-post-kinds' ), 1048 'name' => __( 'Acquisitions', 'indieweb-post-kinds' ), 1049 'verb' => __( 'Acquired', 'indieweb-post-kinds' ), 1050 'property' => 'acquired-of', 1051 'format' => 'status', 1043 1052 'description' => __( 'Purchases, gifts, found things, or objects donated', 'indieweb-post-kinds' ), 1044 1053 'description_url' => 'http://indieweb.org/acquisition', 1045 1054 'shortlink' => 'f', 1046 'title' => false, // Should this kind have an explicit title1047 'show' => false, // Show in Settings1055 'title' => false, 1056 'show' => false, 1048 1057 ) 1049 1058 ); … … 1052 1061 'craft', 1053 1062 array( 1054 'singular_name' => __( 'Craft', 'indieweb-post-kinds' ), // Name for one instance of the kind1055 'name' => __( 'Crafts', 'indieweb-post-kinds' ), // General name for the kind plural1056 'verb' => __( 'Crafted', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)1057 'property' => 'craft-of', // microformats 2 property1058 'format' => 'status', // Post Format that maps to this1063 'singular_name' => __( 'Craft', 'indieweb-post-kinds' ), 1064 'name' => __( 'Crafts', 'indieweb-post-kinds' ), 1065 'verb' => __( 'Crafted', 'indieweb-post-kinds' ), 1066 'property' => 'craft-of', 1067 'format' => 'status', 1059 1068 'description' => __( 'Activities like knitting, crocheting, cross stitch, wood working, restoration, 3d printing...the activity of building something.', 'indieweb-post-kinds' ), 1060 1069 'description_url' => 'https://indieweb.org/crafts', 1061 1070 'shortlink' => 'x', 1062 'title' => false, // Should this kind have an explicit title1063 'show' => true, // Show in Settings1064 ) 1065 ); 1071 'title' => false, 1072 'show' => true, 1073 ) 1074 ); -
indieweb-post-kinds/tags/4.0.0/includes/time-functions.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Time Functions3 * Global date and time functions. 4 4 * 5 * Global Scoped Functions for Handling Time. 5 * The generic helpers moved to Kind_Time in 4.0.0; their global names remain 6 * here as deprecated wrappers. 6 7 * 8 * @package Post_Kinds 9 * @since 3.0.0 7 10 */ 8 11 12 defined( 'ABSPATH' ) || exit; 13 14 if ( ! function_exists( 'kind_safe_datetime' ) ) { 15 /** 16 * Creates a date from a value without throwing on invalid input. 17 * 18 * Dates come from post meta and Micropub requests, where a malformed value 19 * would otherwise throw an uncaught exception and break the page. 20 * 21 * @since 4.0.0 22 * 23 * @param DateTimeInterface|string $value Date, or a date string. 24 * @param DateTimeZone|null $timezone Optional. Timezone for strings without one. 25 * @param bool $immutable Optional. Whether to return a DateTimeImmutable. Default true. 26 * @return DateTimeImmutable|DateTime|false The date, or false if the value is not a valid date. 27 */ 28 function kind_safe_datetime( $value, $timezone = null, $immutable = true ) { 29 if ( $value instanceof DateTimeInterface ) { 30 if ( $immutable ) { 31 return $value instanceof DateTimeImmutable ? $value : DateTimeImmutable::createFromMutable( $value ); 32 } 33 return $value instanceof DateTime ? $value : DateTime::createFromImmutable( $value ); 34 } 35 if ( ! is_string( $value ) || '' === trim( $value ) ) { 36 return false; 37 } 38 try { 39 return $immutable ? new DateTimeImmutable( $value, $timezone ) : new DateTime( $value, $timezone ); 40 } catch ( Exception $e ) { 41 return false; 42 } 43 } 44 } 45 46 if ( ! function_exists( 'kind_safe_interval' ) ) { 47 /** 48 * Creates a duration from an ISO 8601 duration without throwing on invalid input. 49 * 50 * @since 4.0.0 51 * 52 * @param DateInterval|string $value Duration, or an ISO 8601 duration such as PT3M30S. 53 * @return DateInterval|false The duration, or false if the value is not a valid duration. 54 */ 55 function kind_safe_interval( $value ) { 56 if ( $value instanceof DateInterval ) { 57 return $value; 58 } 59 if ( ! is_string( $value ) || '' === trim( $value ) ) { 60 return false; 61 } 62 try { 63 return new DateInterval( $value ); 64 } catch ( Exception $e ) { 65 return false; 66 } 67 } 68 } 69 9 70 if ( ! function_exists( 'tz_seconds_to_offset' ) ) { 71 /** 72 * Converts a UTC offset in seconds to an offset string. 73 * 74 * @since 2.3.0 75 * 76 * @param int $seconds Offset in seconds. 77 * @return string Offset such as '-05:00'. 78 */ 10 79 function tz_seconds_to_offset( $seconds ) { 11 80 return ( $seconds < 0 ? '-' : '+' ) . sprintf( '%02d:%02d', abs( $seconds / 60 / 60 ), abs( $seconds / 60 ) % 60 ); … … 14 83 15 84 if ( ! function_exists( 'tz_offset_to_seconds' ) ) { 85 /** 86 * Converts an offset string to a UTC offset in seconds. 87 * 88 * @since 2.3.0 89 * 90 * @param string $offset Offset such as '-05:00' or '-0500'. 91 * @return int Offset in seconds, or 0 if the string is not an offset. 92 */ 16 93 function tz_offset_to_seconds( $offset ) { 17 94 if ( preg_match( '/([+-])(\d{2}):?(\d{2})/', $offset, $match ) ) { 18 $sign = ( '-' ? -1 : 1 === $match[1] );95 $sign = ( '-' === $match[1] ) ? -1 : 1; 19 96 return ( ( $match[2] * 60 * 60 ) + ( $match[3] * 60 ) ) * $sign; 20 97 } else { … … 25 102 26 103 if ( ! function_exists( 'tz_seconds_to_timezone' ) ) { 104 /** 105 * Converts a UTC offset in seconds to a time zone. 106 * 107 * @since 3.2.2 108 * 109 * @param int $seconds Offset in seconds. 110 * @return DateTimeZone|false The time zone, or false if it cannot be created. 111 */ 27 112 function tz_seconds_to_timezone( $seconds ) { 28 if ( version_compare( phpversion(), '5.5.10', '<' ) ) {29 return timezone_name_from_abbr( '', $seconds, 0 );30 }31 113 if ( 0 !== $seconds ) { 32 114 $tz = timezone_open( tz_seconds_to_offset( $seconds ) ); … … 40 122 41 123 if ( ! function_exists( 'tz_timezone_to_seconds' ) ) { 124 /** 125 * Returns a time zone's UTC offset in seconds. 126 * 127 * @since 3.2.2 128 * 129 * @param string $timezone Time zone name or offset string. 130 * @return int|false Offset in seconds, or false if the time zone is not valid. 131 */ 42 132 function tz_timezone_to_seconds( $timezone ) { 43 133 $tz = timezone_open( $timezone ); … … 51 141 if ( ! function_exists( 'get_gmt_offsets' ) ) { 52 142 143 /** 144 * Returns the UTC offsets currently in use by any time zone, sorted. 145 * 146 * The list is built from every time zone, so it is kept in the object 147 * cache for the day: for the request, and across requests when the cache 148 * is persistent. Daylight saving changes it at most a few times a year. 149 * 150 * @since 3.0.0 151 * 152 * @return string[] Offsets such as '-05:00'. 153 */ 53 154 function get_gmt_offsets() { 155 $key = 'gmt_offsets_' . gmdate( 'Y-m-d' ); 156 $o = wp_cache_get( $key, 'post-kinds' ); 157 if ( is_array( $o ) ) { 158 return $o; 159 } 54 160 $o = array(); 55 161 $t_zones = timezone_identifiers_list(); 56 162 foreach ( $t_zones as $a ) { 57 $t = '';58 163 try { 59 // this throws exception for 'US/Pacific-New'164 // Some identifiers, such as 'US/Pacific-New', throw on some PHP versions. 60 165 $zone = new DateTimeZone( $a ); 61 166 $seconds = $zone->getOffset( new DateTime( 'now', $zone ) ); 62 167 $o[] = tz_seconds_to_offset( $seconds ); 63 168 } catch ( Exception $e ) { 64 die( 'Exception : ' . esc_html( $e->getMessage() ) . '<br />' );65 // what to do in catch ? , nothing just relax169 // Skip the zone rather than fail. 170 continue; 66 171 } 67 172 } 68 173 $o = array_unique( $o ); 69 174 asort( $o ); 175 wp_cache_set( $key, $o, 'post-kinds', DAY_IN_SECONDS ); 70 176 return $o; 71 177 } … … 73 179 74 180 if ( ! function_exists( 'get_default_offset' ) ) { 75 // Gets the default offset 181 /** 182 * Returns the site's UTC offset, from its gmt_offset option. 183 * 184 * @since 3.0.0 185 * 186 * @return string Offset such as '-05:00'. 187 */ 76 188 function get_default_offset() { 77 189 $tz_seconds = get_option( 'gmt_offset' ) * 3600; … … 81 193 82 194 if ( ! function_exists( 'build_iso8601_time' ) ) { 83 // Turns individual pieces of a date and time into a single ISO8601 string 195 /** 196 * Joins a date, time and offset into an ISO 8601 string. 197 * 198 * @since 3.0.0 199 * 200 * @param string $date Date in Y-m-d format. 201 * @param string $time Time in H:i:s format. 202 * @param string $offset Offset such as '-05:00'. Empty for the site's offset. 203 * @return string The date and time, or an empty string if both are empty. 204 */ 84 205 function build_iso8601_time( $date, $time, $offset ) { 85 206 if ( empty( $date ) && empty( $time ) ) { … … 94 215 95 216 if ( ! function_exists( 'build_iso8601_duration' ) ) { 96 // Given an array with the pieces of a duration build an ISO8601 duration 217 /** 218 * Builds an ISO 8601 duration from its pieces. 219 * 220 * @since 3.0.0 221 * 222 * @param int[] $values Years (Y), months (M), days (D), hours (H), minutes (I) and seconds (S). 223 * @return string The duration, or an empty string if every piece is empty. 224 */ 97 225 function build_iso8601_duration( $values ) { 98 226 $values = array_filter( $values ); … … 128 256 129 257 if ( ! function_exists( 'calculate_duration' ) ) { 130 // Given two ISO8601 time strings return a DateInterval Object 258 /** 259 * Returns the duration between two dates. 260 * 261 * @since 2.5.0 262 * @since 4.0.0 Also accepts DateTimeInterface objects, such as Kind_Post::get( 'start' ) returns. 263 * 264 * @param DateTimeInterface|string $start_string Start, as a date or a string in Y-m-d\TH:i:sP format. 265 * @param DateTimeInterface|string $end_string End, as a date or a string in Y-m-d\TH:i:sP format. 266 * @return DateInterval|false The duration, or false if either date is not valid or they are the same. 267 */ 131 268 function calculate_duration( $start_string, $end_string ) { 132 $start = array(); 133 $end = array(); 134 if ( ! is_string( $start_string ) || ! is_string( $end_string ) ) { 269 $dates = array(); 270 foreach ( array( $start_string, $end_string ) as $value ) { 271 if ( is_string( $value ) ) { 272 // Only the full format: looser parsing reads stray text, such as a single letter, as a date. 273 $value = date_create_immutable_from_format( 'Y-m-d\TH:i:sP', $value ); 274 } 275 if ( ! $value instanceof DateTimeInterface ) { 276 return false; 277 } 278 $dates[] = $value; 279 } 280 list( $start, $end ) = $dates; 281 if ( $start == $end ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- Compares the moments, not the objects. 135 282 return false; 136 283 } 137 if ( $start_string === $end_string ) { 138 return false; 139 } 140 $start = date_create_from_format( 'Y-m-d\TH:i:sP', $start_string ); 141 $end = date_create_from_format( 'Y-m-d\TH:i:sP', $end_string ); 142 if ( ( $start instanceof DateTime ) && ( $end instanceof DateTime ) ) { 143 $duration = $start->diff( $end ); 144 return $duration; 145 } 146 return false; 147 } 148 } 149 150 if ( ! function_exists( 'seconds_to_iso8601' ) ) { 151 function seconds_to_iso8601( $second ) { 152 $h = intval( $second / 3600 ); 153 $m = intval( ( $second - $h * 3600 ) / 60 ); 154 $s = $second - ( $h * 3600 + $m * 60 ); 155 $ret = 'PT'; 156 if ( $h ) { 157 $ret .= $h . 'H'; 158 } 159 if ( $m ) { 160 $ret .= $m . 'M'; 161 } 162 if ( ( ! $h && ! $m ) || $s ) { 163 $ret .= $s . 'S'; 164 } 165 return $ret; 284 return $start->diff( $end ); 166 285 } 167 286 } 168 287 169 288 if ( ! function_exists( 'date_interval_to_iso8601' ) ) { 170 171 // Return a date interval as an ISO8601 string 289 /** 290 * Converts a duration to an ISO 8601 duration string. 291 * 292 * @since 3.0.0 293 * 294 * @param \DateInterval $interval The duration. 295 * @return string The duration, such as 'PT3M30S'. 296 */ 172 297 function date_interval_to_iso8601( \DateInterval $interval ) { 173 298 // Reading all non-zero date parts. … … 202 327 } 203 328 204 function display_formatted_datetime( $date ) { 205 if ( is_string( $date ) ) { 206 $date = new DateTimeImmutable( $date ); 207 } 208 209 return $date->format( get_option( 'date_format' ) ) . ' ' . $date->format( get_option( 'time_format' ) ); 210 } 211 212 213 /** 214 * Function to divide a datetime into an array for use in a field 215 * 216 * @access public 217 * 218 * @param DateTime $datetime 219 * @return array { 220 * @type string $date Date in Y-m-d format. 221 * @type string $time Time in H:i:s format. 222 * @type DateTimeZone $timezone Timezone object. 223 * } 224 */ 225 function divide_datetime( $datetime ) { 226 if ( ! $datetime ) { 227 return false; 228 } 229 230 if ( is_string( $datetime ) ) { 231 $datetime = new DateTime( $datetime ); 232 } 233 234 if ( ! $datetime ) { 235 return false; 236 } 237 238 $time = array(); 239 $time['date'] = $datetime->format( 'Y-m-d' ); 240 if ( '0000-01-01' === $time['date'] ) { 241 $time['date'] = ''; 242 } 243 $time['time'] = $datetime->format( 'H:i:s' ); 244 $time['offset'] = get_datetime_offset( $datetime ); 245 $time['class'] = get_class( $datetime ); 246 return array_filter( $time ); 247 } 248 249 /** 250 * Function to build a datetime from individual pieces 251 * 252 * @access public 253 * 254 * @param string $date Date in Y-m-d format. 255 * @param string $time Time in H:i:s format. 256 * @param DateTimeZone $timezone Timezone object. 257 * 258 * @return DateTimeImmutable|false DateTime object or false if not valid 259 */ 260 function build_datetime( $date, $time, $offset = null ) { 261 if ( empty( $date ) && empty( $time ) ) { 262 return false; 263 } 264 if ( is_string( $offset ) ) { 265 $timezone = timezone_open( $offset ); 266 } elseif ( $offset instanceof DateTimeZone ) { 267 $timezone = $offset; 268 } else { 269 return false; 270 } 271 if ( ! $timezone ) { 272 $timezone = wp_timezone(); 273 } 274 return date_create_immutable_from_format( 'Y-m-d\TH:i:sP', $date . 'T' . $time, $timezone ); 275 } 276 277 /** 278 * Return a formatted offset from a datetime object 279 * 280 * @access public 281 * 282 * @param DateTime $datetime DateTime object or if not passed set to now and site timezone 283 * 284 * @return string|false Formatted offset or false if not valid 285 */ 286 function get_datetime_offset( $datetime = null ) { 287 if ( ! $datetime ) { 288 $datetime = new DateTimeImmutable( 'now', wp_timezone() ); 289 } 290 $seconds = $datetime->getOffset(); 291 if ( false === $seconds ) { 292 return false; 293 } 294 return ( $seconds < 0 ? '-' : '+' ) . sprintf( '%02d:%02d', abs( $seconds / 60 / 60 ), abs( $seconds / 60 ) % 60 ); 295 } 296 297 298 // Given an ISO8601 duration return an array with the piece otherwise 0 duration. 299 function divide_interval( $interval ) { 300 $default = array( 301 'Y' => 0, 302 'M' => 0, 303 'D' => 0, 304 'H' => 0, 305 'I' => 0, 306 'S' => 0, 307 ); 308 if ( ! $interval ) { 309 return $default; 310 } 311 if ( is_string( $interval ) && ! empty( $interval ) ) { 312 try { 313 $interval = new DateInterval( $interval ); 314 } catch ( \Exception $e ) { 315 return $default; 316 } 317 } 318 // Reading all non-zero date parts. 319 $return = array( 320 'Y' => $interval->y, 321 'M' => $interval->m, 322 'D' => $interval->d, 323 'H' => $interval->h, 324 'I' => $interval->i, 325 'S' => $interval->s, 326 ); 327 return wp_parse_args( $return, $default ); 328 } 329 330 331 // Given an array with the pieces of a duration build an ISO8601 duration 332 function build_interval( $values ) { 333 $date = wp_array_slice_assoc( $values, array( 'Y', 'M', 'D' ) ); 334 $time = wp_array_slice_assoc( $values, array( 'H', 'I', 'S' ) ); 335 if ( ! $date || ! $time ) { 336 return ''; 337 } 338 $spec = 'P'; 339 // Adding each part to the spec-string. 340 foreach ( $date as $key => $value ) { 341 $spec .= $value . $key; 342 } 343 if ( count( $time ) > 0 ) { 344 $spec .= 'T'; 345 foreach ( $time as $key => $value ) { 346 if ( 'I' === $key ) { 347 $spec .= $value . 'M'; 348 } else { 349 $spec .= $value . $key; 350 } 351 } 352 } 353 return $spec; 354 } 329 if ( ! function_exists( 'display_formatted_datetime' ) ) { 330 /** 331 * Deprecated wrapper; see Kind_Time::display_formatted_datetime(). 332 * 333 * @since 3.2.0 334 * @deprecated 4.0.0 Use Kind_Time::display_formatted_datetime(). Will be removed in 5.0.0. 335 * 336 * @param DateTimeInterface|string $date Date, or a string kind_safe_datetime() accepts. 337 * @return string The formatted date and time, or an empty string if the date is not valid. 338 */ 339 function display_formatted_datetime( $date ) { 340 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::display_formatted_datetime()' ); 341 return Kind_Time::display_formatted_datetime( $date ); 342 } 343 } 344 345 if ( ! function_exists( 'divide_datetime' ) ) { 346 /** 347 * Deprecated wrapper; see Kind_Time::divide_datetime(). 348 * 349 * @since 3.3.4 350 * @deprecated 4.0.0 Use Kind_Time::divide_datetime(). Will be removed in 5.0.0. 351 * 352 * @param DateTimeInterface|string $datetime Date, or a string kind_safe_datetime() accepts. 353 * @return array|false The date's parts. See Kind_Time::divide_datetime(). 354 */ 355 function divide_datetime( $datetime ) { 356 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::divide_datetime()' ); 357 return Kind_Time::divide_datetime( $datetime ); 358 } 359 } 360 361 if ( ! function_exists( 'build_datetime' ) ) { 362 /** 363 * Deprecated wrapper; see Kind_Time::build_datetime(). 364 * 365 * @since 3.3.4 366 * @deprecated 4.0.0 Use Kind_Time::build_datetime(). Will be removed in 5.0.0. 367 * 368 * @param string $date Date in Y-m-d format. 369 * @param string $time Time in H:i or H:i:s format. 370 * @param string|DateTimeZone|null $offset Optional. Offset or time zone. Default the site's time zone. 371 * @return DateTimeImmutable|false The date, or false if it is not valid. 372 */ 373 function build_datetime( $date, $time, $offset = null ) { 374 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::build_datetime()' ); 375 return Kind_Time::build_datetime( $date, $time, $offset ); 376 } 377 } 378 379 if ( ! function_exists( 'get_datetime_offset' ) ) { 380 /** 381 * Deprecated wrapper; see Kind_Time::get_datetime_offset(). 382 * 383 * @since 3.3.4 384 * @deprecated 4.0.0 Use Kind_Time::get_datetime_offset(). Will be removed in 5.0.0. 385 * 386 * @param DateTimeInterface|null $datetime Optional. Date. Default now, in the site's time zone. 387 * @return string|false The offset, or false if it cannot be read. 388 */ 389 function get_datetime_offset( $datetime = null ) { 390 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::get_datetime_offset()' ); 391 return Kind_Time::get_datetime_offset( $datetime ); 392 } 393 } 394 395 if ( ! function_exists( 'divide_interval' ) ) { 396 /** 397 * Deprecated wrapper; see Kind_Time::divide_interval(). 398 * 399 * @since 3.3.4 400 * @deprecated 4.0.0 Use Kind_Time::divide_interval(). Will be removed in 5.0.0. 401 * 402 * @param DateInterval|string|null $interval Duration, or an ISO 8601 duration string. 403 * @return int[] The duration's parts. See Kind_Time::divide_interval(). 404 */ 405 function divide_interval( $interval ) { 406 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::divide_interval()' ); 407 return Kind_Time::divide_interval( $interval ); 408 } 409 } 410 411 if ( ! function_exists( 'build_interval' ) ) { 412 /** 413 * Deprecated wrapper; see Kind_Time::build_interval(). 414 * 415 * @since 3.3.4 416 * @deprecated 4.0.0 Use Kind_Time::build_interval(). Will be removed in 5.0.0. 417 * 418 * @param int[] $values Years (Y), months (M), days (D), hours (H), minutes (I) and seconds (S). 419 * @return string The duration, or an empty string if every piece is 0. 420 */ 421 function build_interval( $values ) { 422 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::build_interval()' ); 423 return Kind_Time::build_interval( $values ); 424 } 425 } -
indieweb-post-kinds/tags/4.0.0/indieweb-post-kinds.php
r3067251 r3738687 4 4 * 5 5 * @link http://indieweb.org/Post_Kinds_Plugin 6 * @package Post Kinds6 * @package Post_Kinds 7 7 * Plugin Name: Post Kinds 8 8 * Plugin URI: https://wordpress.org/plugins/indieweb-post-kinds/ 9 9 * Description: Ever want to reply to someone else's post with a post on your own site? Or to "like" someone else's post, but with your own site? 10 * Version: 3.7.3 10 * Version: 4.0.0 11 * Requires at least: 6.2 12 * Requires PHP: 7.4 13 * License: GPLv2 or later 14 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 15 * Author: David Shanske 12 16 * Author URI: https://david.shanske.com … … 14 18 * Domain Path: /languages 15 19 */ 20 21 defined( 'ABSPATH' ) || exit; 16 22 17 23 if ( ! defined( 'POST_KINDS_KSES' ) ) { … … 40 46 add_action( 'upgrader_process_complete', array( 'Post_Kinds_Plugin', 'upgrader_process_complete' ), 10, 2 ); 41 47 42 if ( ! file_exists( plugin_dir_path( __FILE__ ) . 'lib/parse-this/parse-this.php' ) ) {43 add_action( 'admin_notices', array( 'Post_Kinds_Plugin', 'parse_this_error' ) );44 }45 48 46 49 if ( Post_Kinds_Plugin::show_editor_error() ) { … … 51 54 add_action( 'init', array( 'Post_Kinds_Plugin', 'init' ) ); 52 55 56 /** 57 * Loads the plugin, its assets and its global functions. 58 */ 53 59 class Post_Kinds_Plugin { 60 /** 61 * Plugin version, read from the plugin header on init. 62 * 63 * @since 2.4.0 64 * @var string 65 */ 54 66 public static $version; 55 67 68 /** 69 * Sets the version and registers the kind taxonomy. 70 * 71 * @since 2.4.0 72 */ 56 73 public static function init() { 57 74 self::$version = get_file_data( __FILE__, array( 'Version' => 'Version' ) )['Version']; … … 61 78 } 62 79 80 /** 81 * Outputs an admin notice when Parse This is not available at all. 82 * 83 * @since 3.1.0 84 */ 63 85 public static function parse_this_error() { 64 $class = 'notice notice-error'; 65 $message = __( 'Parse This is not installed. Please advise the developer', 'indieweb-post-kinds' ); 66 printf( '<div class="%1$s"><p>%2$s</p></div>', esc_attr( $class ), esc_html( $message ) ); 67 } 68 86 wp_admin_notice( 87 esc_html__( 'Parse This is not installed. Please advise the developer', 'indieweb-post-kinds' ), 88 array( 'type' => 'error' ) 89 ); 90 } 91 92 /** 93 * Outputs an admin notice when another plugin loaded a copy of Parse This 94 * older than 2.0.0. 95 * 96 * Post Kinds still works, because its bundled 2.0 API is loaded alongside the 97 * older copy, but the older copy serves the parse-this REST route used by the 98 * reply metabox. 99 * 100 * @since 4.0.0 101 */ 102 public static function parse_this_outdated_notice() { 103 if ( ! current_user_can( 'activate_plugins' ) ) { 104 return; 105 } 106 wp_admin_notice( 107 sprintf( 108 /* translators: %s: Name of the plugin, or path of the file, that loaded the older copy of Parse This. */ 109 esc_html__( 'Post Kinds: %s is loading a copy of Parse This older than version 2.0. Post Kinds will keep working, but URL lookups in the post editor use that older copy. Updating that plugin, or installing the Parse This plugin (version 2.0 or later), resolves this.', 'indieweb-post-kinds' ), 110 '<strong>' . esc_html( self::parse_this_source() ) . '</strong>' 111 ), 112 array( 'type' => 'warning' ) 113 ); 114 } 115 116 /** 117 * Returns whether the loaded copy of Parse This is version 2.0.0 or later. 118 * 119 * Copies older than 2.0.0 do not define PARSE_THIS_VERSION. 120 * 121 * @since 4.0.0 122 * 123 * @return bool 124 */ 125 public static function parse_this_is_current() { 126 return defined( 'PARSE_THIS_VERSION' ) && version_compare( PARSE_THIS_VERSION, '2.0.0', '>=' ); 127 } 128 129 /** 130 * Returns a name for whatever loaded Parse This: the plugin's name, or the 131 * file's path relative to the content directory if it isn't a plugin. 132 * 133 * @since 4.0.0 134 * 135 * @return string 136 */ 137 public static function parse_this_source() { 138 if ( ! function_exists( 'parse_this_loader' ) ) { 139 return ''; 140 } 141 $file = wp_normalize_path( ( new ReflectionFunction( 'parse_this_loader' ) )->getFileName() ); 142 $dir = trailingslashit( wp_normalize_path( WP_PLUGIN_DIR ) ); 143 if ( 0 === strpos( $file, $dir ) ) { 144 $slug = strtok( substr( $file, strlen( $dir ) ), '/' ); 145 if ( ! function_exists( 'get_plugins' ) ) { 146 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 147 } 148 foreach ( get_plugins() as $basename => $data ) { 149 if ( 0 === strpos( $basename, $slug . '/' ) ) { 150 return $data['Name']; 151 } 152 } 153 return $slug; 154 } 155 return str_replace( trailingslashit( wp_normalize_path( WP_CONTENT_DIR ) ), '', $file ); 156 } 157 158 /** 159 * Whether to check for the block editor and warn about it. 160 * 161 * Not on ClassicPress, or when the Classic Editor plugin is active. 162 * 163 * @since 3.3.4 164 * 165 * @return bool 166 */ 69 167 public static function show_editor_error() { 70 // Do not show this error in ClassicPress 168 // Do not show this error in ClassicPress. 71 169 if ( self::is_classicpress() ) { 72 170 return false; 73 171 } 74 // Do not show if less than Version 575 if ( version_compare( get_bloginfo( 'version' ), '5.0' ) <= 0 ) {76 return false;77 }78 172 if ( class_exists( 'Classic_Editor' ) ) { 79 173 return false; … … 82 176 } 83 177 178 /** 179 * Whether the site runs ClassicPress. 180 * 181 * @since 3.6.0 182 * 183 * @return bool 184 */ 84 185 public static function is_classicpress() { 85 186 return function_exists( 'classicpress_version' ); 86 187 } 87 188 189 /** 190 * Outputs an admin notice on screens that use the block editor. 191 * 192 * Post Kinds works only with the Classic Editor. The notice is not shown 193 * if the Classic Editor or Disable Gutenberg plugin is active. 194 * 195 * @since 3.2.6 196 * 197 * @return string|void An empty string if no notice is needed. 198 */ 88 199 public static function classic_editor_error() { 89 200 if ( ! self::post_uses_gutenberg() ) { … … 101 212 102 213 214 /** 215 * Flushes the rewrite rules after Post Kinds is updated. 216 * 217 * @since 3.4.19 218 * 219 * @param WP_Upgrader $upgrade_object The upgrader. 220 * @param array $options Details of the update. 221 */ 103 222 public static function upgrader_process_complete( $upgrade_object, $options ) { 104 223 $current_plugin_path_name = plugin_basename( __FILE__ ); … … 112 231 } 113 232 233 /** 234 * Creates the kind terms and flushes the rewrite rules on activation. 235 * 236 * @since 3.3.0 237 */ 114 238 public static function activate() { 115 239 Kind_Taxonomy::activate_kinds(); … … 117 241 } 118 242 243 /** 244 * Flushes the rewrite rules on deactivation. 245 * 246 * @since 3.3.0 247 */ 119 248 public static function deactivate() { 120 249 flush_rewrite_rules(); 121 250 } 122 251 252 /** 253 * Loads the translations, global functions and Parse This, and adds the hooks. 254 * 255 * @since 2.5.2 256 */ 123 257 public static function plugins_loaded() { 124 258 $cls = get_called_class(); 125 259 load_plugin_textdomain( 'indieweb-post-kinds', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); 126 260 127 // Add WordPress Compatibility File for Functions Introduced Post 4.9.9.261 // Polyfills for functions newer than the plugin's minimums. 128 262 require_once plugin_dir_path( __FILE__ ) . 'includes/compat.php'; 129 // Parse this Compat File.130 require_once plugin_dir_path( __FILE__ ) . 'lib/parse-this/includes/compat-functions.php';131 263 132 264 // Add Kind Global Functions. … … 136 268 require_once plugin_dir_path( __FILE__ ) . '/includes/time-functions.php'; 137 269 138 // Parse This 139 require_once plugin_dir_path( __FILE__ ) . 'lib/parse-this/includes/autoload.php'; 140 if ( ! class_exists( 'REST_Parse_This' ) ) { 141 require_once plugin_dir_path( __FILE__ ) . 'lib/parse-this/includes/class-rest-parse-this.php'; 142 } 143 require_once plugin_dir_path( __FILE__ ) . 'lib/parse-this/includes/functions.php'; 270 /* 271 * Parse This. The standalone plugin, if active, has already loaded at 272 * plugins_loaded priority 9 and defined parse_this_loader(). Otherwise 273 * load the bundled copy. Its loader sets up the classes, the functions 274 * and the REST route. 275 */ 276 $parse_this = plugin_dir_path( __FILE__ ) . 'lib/parse-this/parse-this.php'; 277 if ( ! function_exists( 'parse_this_loader' ) && file_exists( $parse_this ) ) { 278 require_once $parse_this; 279 parse_this_loader(); 280 } 281 282 if ( ! function_exists( 'parse_this_loader' ) ) { 283 add_action( 'admin_notices', array( $cls, 'parse_this_error' ) ); 284 } elseif ( ! self::parse_this_is_current() ) { 285 /* 286 * Another plugin loaded a copy older than 2.0.0. Load the bundled 2.0 287 * API alongside it: everything in 2.0 is in the ParseThis namespace, 288 * so it does not clash with the old copy's global names, and both 289 * versions only load their bundled Mf2\Parser and Masterminds\HTML5 290 * if those classes do not already exist. 291 */ 292 $includes = plugin_dir_path( __FILE__ ) . 'lib/parse-this/includes/'; 293 if ( file_exists( $includes . 'autoload.php' ) ) { 294 require_once $includes . 'autoload.php'; 295 require_once $includes . 'functions.php'; 296 } 297 add_action( 'admin_notices', array( $cls, 'parse_this_outdated_notice' ) ); 298 } 144 299 $class_load = array( 145 'Plugins', // Plugin Specific Customization 146 'Media_Metadata', // Media Metadata Enhancements 147 'Config', // Configuration Menu 148 'Metabox', // Metabox for Classic Editor 149 'View', // Kind Display Functionality 300 'Plugins', // Plugin Specific Customization. 301 'Media_Metadata', // Media Metadata Enhancements. 302 'Config', // Configuration Menu. 303 'Metabox', // Metabox for Classic Editor. 304 'View', // Kind Display Functionality. 305 'Upgrade', // One-time upgrade of stored data. 150 306 ); 151 307 152 308 foreach ( $class_load as $load ) { 153 309 add_action( 'init', array( 'Kind_' . $load, 'init' ) ); 310 } 311 312 if ( defined( 'WP_CLI' ) && WP_CLI ) { 313 WP_CLI::add_command( 'post-kinds upgrade', array( 'Kind_Upgrade', 'cli' ) ); 154 314 } 155 315 … … 162 322 add_action( 'admin_enqueue_scripts', array( $cls, 'admin_style_load' ) ); 163 323 164 // Load Privacy Declaration 324 // Load Privacy Declaration. 165 325 add_action( 'admin_init', array( $cls, 'privacy_declaration' ) ); 166 remove_all_actions( 'do_feed_rss2' ); 167 remove_all_actions( 'do_feed_atom' ); 168 add_action( 'do_feed_rss2', array( $cls, 'do_feed_rss2' ), 10, 1 ); 169 add_action( 'do_feed_atom', array( $cls, 'do_feed_atom' ), 10, 1 ); 170 171 // Register Widgets 326 327 // Register Widgets. 172 328 add_action( 173 329 'widgets_init', … … 179 335 } 180 336 181 public static function do_feed_atom( $for_comments ) {182 if ( $for_comments ) {183 load_template( plugin_dir_path( __FILE__ ) . 'templates/feed-atom-comments.php' );184 } else {185 load_template( plugin_dir_path( __FILE__ ) . 'templates/feed-atom.php' );186 }187 }188 189 public static function do_feed_rss2( $for_comments ) {190 if ( $for_comments ) {191 load_template( plugin_dir_path( __FILE__ ) . 'templates/feed-rss2-comments.php' );192 } else {193 load_template( plugin_dir_path( __FILE__ ) . 'templates/feed-rss2.php' );194 }195 }196 197 337 /** 198 338 * Adds link to Plugin Page for Options Page. 339 * 340 * @since 2.4.0 199 341 * 200 342 * @access public … … 217 359 /** 218 360 * Loads the Stylesheet for the Plugin. 361 * 362 * @since 2.4.0 219 363 */ 220 364 public static function style_load() { … … 224 368 /** 225 369 * Loads the Admin Stylesheet for the Plugin. 370 * 371 * @since 2.4.0 226 372 */ 227 373 public static function admin_style_load() { … … 229 375 } 230 376 377 /** 378 * Adds suggested text to the privacy policy guide. 379 * 380 * @since 3.0.5 381 */ 231 382 public static function privacy_declaration() { 232 383 if ( function_exists( 'wp_add_privacy_policy_content' ) ) { … … 242 393 } 243 394 395 /** 396 * Whether the current screen uses the block editor. 397 * 398 * Screens other than the post editor count as using it. 399 * 400 * @since 3.3.2 401 * 402 * @return bool 403 */ 244 404 public static function post_uses_gutenberg() { 245 405 $screen = get_current_screen(); -
indieweb-post-kinds/tags/4.0.0/js/clone-media-fragment.js
r2110745 r3738687 1 1 function cloneMediaFragment() { 2 // Check that the fragment is a Media Fragment (starts with t=)3 if(window.location.hash && window.location.hash.match(/^#t=/)) {4 // Find any video and audio tags on the page5 document.querySelectorAll("video,audio").forEach(function(el){6 // Create a virtual element to use as a URI parser7 var parser = document.createElement('a');8 parser.href = el.currentSrc;9 // Replace the hash 10 parser.hash = window.location.hash;11 // Set the src of the video/audio tag to the full URL12 el.src = parser.href;13 });14 }2 // Check that the fragment is a Media Fragment (starts with t=) 3 if ( window.location.hash && window.location.hash.match( /^#t=/ ) ) { 4 // Find any video and audio tags on the page 5 document.querySelectorAll( 'video,audio' ).forEach( function( el ) { 6 // Create a virtual element to use as a URI parser 7 var parser = document.createElement( 'a' ); 8 parser.href = el.currentSrc; 9 // Replace the hash 10 parser.hash = window.location.hash; 11 // Set the src of the video/audio tag to the full URL 12 el.src = parser.href; 13 } ); 14 } 15 15 } 16 16 17 document.addEventListener( "DOMContentLoaded", function() {18 cloneMediaFragment();19 // When the media is paused, update the fragment of the page20 document.querySelectorAll("video,audio").forEach(function(el) { 21 el.addEventListener("pause", function(event) {22 // Update the media fragment to the current time23 // Use replaceState to avoid triggering the "hashchange" listener above24 history.replaceState(null, null, "#t=" + Math.round(event.target.currentTime));25 });26 });27 } );17 document.addEventListener( 'DOMContentLoaded', function() { 18 cloneMediaFragment(); 19 // When the media is paused, update the fragment of the page 20 document.querySelectorAll( 'video,audio' ).forEach( function( el ) { 21 el.addEventListener( 'pause', function( event ) { 22 // Update the media fragment to the current time 23 // Use replaceState to avoid triggering the "hashchange" listener above 24 history.replaceState( null, null, '#t=' + Math.round( event.target.currentTime ) ); 25 } ); 26 } ); 27 } ); 28 28 29 29 // If the user changes the hash manually, clone the fragment to the media URLs 30 window.addEventListener( "hashchange", cloneMediaFragment);30 window.addEventListener( 'hashchange', cloneMediaFragment ); -
indieweb-post-kinds/tags/4.0.0/js/kind.js
r3001401 r3738687 1 /* global PKAPI, moment, wp */ 2 /* eslint-disable no-alert -- The metabox uses alert() and confirm() for feedback by design. */ 1 3 jQuery( document ).ready( function( $ ) { 4 var KindWindow; 5 2 6 changeSettings(); 3 7 4 5 function clearPostProperties() { 6 var fieldIds = [ 7 'cite_url', 8 'cite_name', 9 'cite_summary', 10 'cite_tags', 11 'cite_media', 12 'cite_author_name', 13 'cite_author_url', 14 'cite_author_photo', 15 'cite_featured', 16 'cite_publication', 17 'mf2_rsvp', 18 'mf2_rating', 19 'mf2_start_time', 20 'mf2_start_date', 21 'mf2_end_time', 22 'mf2_end_date', 23 'cite_published_time', 24 'cite_published_date', 25 'cite_updated_time', 26 'cite_updated_date', 27 'duration_years', 28 'duration_months', 29 'duration_days', 30 'duration_hours', 31 'duration_minutes', 32 'duration_seconds' 33 ]; 8 function clearPostProperties() { 9 var fieldIds = [ 10 'cite_url', 11 'cite_name', 12 'cite_summary', 13 'cite_tags', 14 'cite_media', 15 'cite_author_name', 16 'cite_author_url', 17 'cite_author_photo', 18 'cite_featured', 19 'cite_publication', 20 'mf2_rsvp', 21 'mf2_rating', 22 'mf2_start_time', 23 'mf2_start_date', 24 'mf2_end_time', 25 'mf2_end_date', 26 'cite_published_time', 27 'cite_published_date', 28 'cite_updated_time', 29 'cite_updated_date', 30 'duration_years', 31 'duration_months', 32 'duration_days', 33 'duration_hours', 34 'duration_minutes', 35 'duration_seconds', 36 ]; 34 37 if ( ! confirm( PKAPI.clear_message ) ) { 35 38 return; … … 37 40 $.each( fieldIds, function( count, val ) { 38 41 document.getElementById( val ).value = ''; 39 } );42 } ); 40 43 $( '#kind-media-container' ).addClass( 'hidden' ); 41 44 $( '#kind-media-container' ).children( 'img' ).hide(); 42 45 $( '#add-kind-media' ).show(); 43 44 } 45 46 function addhttp( url ) { 47 if ( ! /^(?:f|ht)tps?\:\/\//.test( url ) ) { 48 url = 'http://' + url; 49 } 50 return url; 51 } 52 53 function showLoadingSpinner() { 54 $( '#replybox-meta' ).addClass( 'is-loading' ); 55 } 56 57 function hideLoadingSpinner() { 58 $( '#replybox-meta' ).removeClass( 'is-loading' ); 59 } 60 61 //function used to validate website URL 62 function checkUrl( url ) { 63 64 //regular expression for URL 65 var pattern = /^(http|https)?:\/\/[a-zA-Z0-9-\.]+\.[a-z]{2,4}/; 66 67 if ( pattern.test( url ) ) { 68 return true; 69 } else { 70 return false; 71 } 72 } 73 74 function getLinkPreview() { 75 if ( '' === $( '#cite_url' ).val() ) { 76 return; 77 } 78 $.ajax({ 79 type: 'GET', 80 81 // Here we supply the endpoint url, as opposed to the action in the data object with the admin-ajax method 82 url: PKAPI.api_url + 'parse/', 83 beforeSend: function( xhr ) { 84 85 // Here we set a header 'X-WP-Nonce' with the nonce as opposed to the nonce in the data object with admin-ajax 86 xhr.setRequestHeader( 'X-WP-Nonce', PKAPI.api_nonce ); 87 }, 88 data: { 89 url: $( '#cite_url' ).val(), 90 kind: $( 'input[name=\'tax_input[kind]\']:checked' ).val(), 91 follow: true 92 }, 93 success: function( response ) { 94 var published; 95 var updated; 96 if ( 'undefined' === typeof response ) { 97 alert( 'Error: Unable to Retrieve' ); 46 } 47 48 function showLoadingSpinner() { 49 $( '#replybox-meta' ).addClass( 'is-loading' ); 50 } 51 52 function hideLoadingSpinner() { 53 $( '#replybox-meta' ).removeClass( 'is-loading' ); 54 } 55 56 //function used to validate website URL 57 function checkUrl( url ) { 58 //regular expression for URL 59 var pattern = /^(http|https)?:\/\/[a-zA-Z0-9-\.]+\.[a-z]{2,4}/; 60 61 return pattern.test( url ); 62 } 63 64 // Fills the Author tab from one author or a list. Each field holds one value 65 // per author, separated by semicolons, so the values stay paired by position. 66 function fillAuthors( authors ) { 67 var fields = { name: [], url: [], photo: [] }; 68 if ( ! Array.isArray( authors ) ) { 69 authors = [ authors ]; 70 } 71 $.each( authors, function( index, author ) { 72 if ( 'string' === typeof author ) { 73 author = checkUrl( author ) ? { url: author } : { name: author }; 74 } 75 if ( ! author || 'object' !== typeof author ) { 98 76 return; 99 77 } 100 if ( 'message' in response ) { 101 alert( response.message ); 78 $.each( fields, function( key, values ) { 79 var value = author[ key ]; 80 if ( Array.isArray( value ) ) { 81 value = value.join( '; ' ); 82 } else if ( value && 'object' === typeof value ) { 83 value = value.value; 84 } 85 values.push( 'string' === typeof value ? value : '' ); 86 } ); 87 } ); 88 $.each( fields, function( key, values ) { 89 $( '#cite_author_' + key ).val( values.join( '' ) ? values.join( '; ' ) : '' ); 90 } ); 91 } 92 93 function getLinkPreview() { 94 if ( '' === $( '#cite_url' ).val() ) { 95 return; 96 } 97 $.ajax( { 98 type: 'GET', 99 100 // Here we supply the endpoint url, as opposed to the action in the data object with the admin-ajax method 101 url: PKAPI.api_url + 'parse/', 102 beforeSend: function( xhr ) { 103 // Here we set a header 'X-WP-Nonce' with the nonce as opposed to the nonce in the data object with admin-ajax 104 xhr.setRequestHeader( 'X-WP-Nonce', PKAPI.api_nonce ); 105 }, 106 data: { 107 url: $( '#cite_url' ).val(), 108 follow: true, 109 }, 110 success: function( response ) { 111 var published, updated, duration; 112 if ( 'undefined' === typeof response ) { 113 alert( PKAPI.error_message ); 114 return; 115 } 116 if ( 'message' in response ) { 117 alert( response.message ); 118 return; 119 } 120 if ( 'name' in response ) { 121 $( '#cite_name' ).val( response.name ); 122 } 123 if ( 'published' in response ) { 124 published = moment.parseZone( response.published ); 125 $( '#cite_published_date' ).val( published.format( 'YYYY-MM-DD' ) ); 126 $( '#cite_published_time' ).val( published.format( 'HH:mm:ss' ) ); 127 $( '#cite_published_offset' ).val( published.format( 'Z' ) ); 128 } 129 if ( 'updated' in response ) { 130 updated = moment.parseZone( response.updated ); 131 $( '#cite_updated_date' ).val( updated.format( 'YYYY-MM-DD' ) ); 132 $( '#cite_updated_time' ).val( updated.format( 'HH:mm:ss' ) ); 133 $( '#cite_updated_offset' ).val( updated.format( 'Z' ) ); 134 } 135 if ( 'duration' in response ) { 136 duration = moment.duration( response.duration ); 137 $( '#duration_years' ).val( duration.years() ); 138 $( '#duration_months' ).val( duration.months() ); 139 $( '#duration_days' ).val( duration.days() ); 140 $( '#duration_hours' ).val( duration.hours() ); 141 $( '#duration_minutes' ).val( duration.minutes() ); 142 $( '#duration_seconds' ).val( duration.seconds() ); 143 } 144 145 if ( 'summary' in response ) { 146 $( '#cite_summary' ).val( response.summary ); 147 } 148 if ( 'featured' in response ) { 149 $( '#cite_featured' ).val( response.featured ); 150 } 151 if ( 'author' in response ) { 152 fillAuthors( response.author ); 153 } 154 if ( 'publication' in response && ( 'string' !== typeof response.publication ) ) { 155 if ( 'name' in response.publication ) { 156 $( '#cite_publication' ).val( response.publication.name ); 157 } 158 } else { 159 $( '#cite_publication' ).val( response.publication ); 160 } 161 162 if ( 'category' in response ) { 163 if ( 'object' === typeof response.category ) { 164 $( '#cite_tags' ).val( response.category.join( ';' ) ); 165 } 166 } 167 alert( PKAPI.success_message ); 168 }, 169 error: function( jqXHR ) { 170 if ( jqXHR.responseJSON && jqXHR.responseJSON.message ) { 171 alert( jqXHR.responseJSON.message ); 172 } else { 173 alert( PKAPI.error_message ); 174 } 175 }, 176 complete: hideLoadingSpinner, 177 } ); 178 } 179 180 function changeSettings() { 181 var kind = $( 'input[name=\'tax_input[kind]\']:checked' ).val(); 182 switch ( kind ) { 183 case 'note': 184 hideTitle(); 185 hideReply(); 186 hideRSVP(); 187 hideRating(); 188 hideTime(); 189 hideMedia(); 190 break; 191 case 'article': 192 showTitle(); 193 hideReply(); 194 hideRSVP(); 195 hideRating(); 196 hideTime(); 197 hideMedia(); 198 break; 199 case 'issue': 200 showTitle(); 201 showReply(); 202 hideRSVP(); 203 hideRating(); 204 hideTime(); 205 hideMedia(); 206 break; 207 case 'listen': 208 case 'jam': 209 case 'watch': 210 case 'read': 211 case 'play': 212 hideTitle(); 213 showReply(); 214 hideRSVP(); 215 showRating(); 216 showTime(); 217 hideMedia(); 218 break; 219 case 'photo': 220 case 'video': 221 case 'audio': 222 hideTitle(); 223 showReply(); 224 hideRSVP(); 225 hideRating(); 226 showTime(); 227 showMedia(); 228 break; 229 case 'rsvp': 230 showReply(); 231 hideTitle(); 232 hideTime(); 233 showRSVP(); 234 hideRating(); 235 hideMedia(); 236 break; 237 case 'review': 238 case 'drink': 239 case 'eat': 240 showReply(); 241 hideTitle(); 242 hideTime(); 243 hideRSVP(); 244 showRating(); 245 hideMedia(); 246 break; 247 default: 248 showReply(); 249 hideTime(); 250 hideTitle(); 251 hideRSVP(); 252 hideRating(); 253 hideMedia(); 254 } 255 } 256 257 function showReply() { 258 $( '#replybox-meta' ).removeClass( 'hidden' ); 259 } 260 261 function hideReply() { 262 $( '#replybox-meta' ).addClass( 'hidden' ); 263 } 264 265 function showMedia() { 266 $( '#add-kind-media' ).removeClass( 'hidden' ); 267 } 268 269 function hideMedia() { 270 $( '#add-kind-media' ).addClass( 'hidden' ); 271 } 272 273 function showTitle() { 274 var titlediv = $( '#titlediv' ).detach(); 275 titlediv.prependTo( '#post-body-content' ); 276 } 277 278 function hideTitle() { 279 var titlediv = $( '#titlediv' ).detach(); 280 titlediv.insertAfter( '#postdivrich' ); 281 } 282 283 function showRSVP() { 284 $( '#rsvp-option' ).removeClass( 'hide-if-js' ); 285 } 286 287 function hideRSVP() { 288 $( '#rsvp-option' ).addClass( 'hide-if-js' ); 289 } 290 291 function showRating() { 292 $( '#rating-option' ).removeClass( 'hide-if-js' ); 293 } 294 295 function hideRating() { 296 $( '#rating-option' ).addClass( 'hide-if-js' ); 297 } 298 299 function showTime() { 300 $( '#kind-time' ).removeClass( 'hide-if-js' ); 301 } 302 303 function hideTime() { 304 $( '#kind-time' ).addClass( 'hide-if-js' ); 305 } 306 307 function handleKindMediaWindow() { 308 'use strict'; 309 310 var json; 311 312 /** 313 * If an instance of KindWindow already exists, then we can open it 314 * rather than creating a new instance. 315 */ 316 if ( undefined !== KindWindow ) { 317 KindWindow.open(); 318 return; 319 } 320 321 KindWindow = wp.media.frames.KindWindow = wp.media( { 322 title: PKAPI.media_title, 323 button: { 324 text: PKAPI.media_button, 325 }, 326 multiple: false, 327 } ); 328 329 KindWindow.on( 'select', function() { 330 json = KindWindow.state().get( 'selection' ).first().toJSON(); 331 if ( ! json.url ) { 102 332 return; 103 333 } 104 if ( 'name' in response ) { 105 $( '#cite_name' ).val( response.name ); 106 } 107 if ( 'published' in response ) { 108 var published = moment.parseZone( response.published ); 109 $( '#cite_published_date' ).val( published.format( 'YYYY-MM-DD' ) ) ; 110 $( '#cite_published_time' ).val( published.format( 'HH:mm:ss' ) ) ; 111 $( '#cite_published_offset' ).val( published.format( 'Z' ) ); 112 } 113 if ( 'updated' in response ) { 114 var updated = moment.parseZone( response.updated ); 115 $( '#cite_updated_date' ).val( updated.format( 'YYYY-MM-DD' ) ) ; 116 $( '#cite_updated_time' ).val( updated.format( 'HH:mm:ss' ) ) ; 117 $( '#cite_updated_offset' ).val( updated.format( 'Z' ) ); 118 } 119 if ( 'duration' in response ) { 120 var duration = moment.duration( response.duration); 121 $( '#duration_years' ).val( duration.years() ); 122 $( '#duration_months' ).val( duration.months() ); 123 $( '#duration_days' ).val( duration.days() ); 124 $( '#duration_hours' ).val( duration.hours() ); 125 $( '#duration_minutes' ).val( duration.minutes() ); 126 $( '#duration_seconds' ).val( duration.seconds() ); 127 } 128 129 if ( 'summary' in response ) { 130 $( '#cite_summary' ).val( response.summary ) ; 131 } 132 if ( 'featured' in response ) { 133 $( '#cite_featured' ).val( response.featured ) ; 134 } 135 if ( ( 'author' in response ) && ( 'string' != typeof response.author ) ) { 136 if ( 'name' in response.author ) { 137 if ( 'string' === typeof response.author.name ) { 138 $( '#cite_author_name' ).val( response.author.name ); 139 } else { 140 $( '#cite_author_name' ).val( response.author.name.join( ';' ) ) ; 141 } 142 } 143 if ( 'photo' in response.author ) { 144 if ( 'string' === typeof response.author.name ) { 145 $( '#cite_author_photo' ).val( response.author.photo ); 146 } else { 147 $( '#cite_author_photo' ).val( response.author.photo.join( ';' ) ) ; 148 } 149 } 150 if ( 'url' in response.author ) { 151 if ( 'string' === typeof response.author.url ) { 152 $( '#cite_author_url' ).val( response.author.url ); 153 } else { 154 $( '#cite_author_url' ).val( response.author.url.join( ';' ) ) ; 155 } 156 } 157 } 158 if ( 'publication' in response && ( 'string' != typeof response.publication ) ) { 159 if ( 'name' in response.publication ) { 160 $( '#cite_publication' ).val( response.publication.name ); 161 } 334 $( '#cite_name' ).val( json.title ); 335 $( '#cite_url' ).val( json.url ); 336 $( '#cite_media' ).val( json.id ); 337 $( '#cite_summary' ).val( json.description ); 338 $( '#kind-media-container' ) 339 .children( 'img' ) 340 .attr( 'src', json.url ) 341 .attr( 'alt', json.caption ) 342 .attr( 'title', json.title ) 343 .show() 344 .parent() 345 .removeClass( 'hidden' ); 346 347 $( '#add-kind-media' ).hide(); 348 } ); 349 KindWindow.open(); 350 } 351 352 jQuery( document ) 353 .on( 'change', '#taxonomy-kind', function( event ) { 354 changeSettings(); 355 event.preventDefault(); 356 } ) 357 .on( 'blur', '#cite_url', function( event ) { 358 if ( '' !== $( '#cite_url' ).val() ) { 359 if ( false === checkUrl( $( '#cite_url' ).val() ) ) { 360 alert( PKAPI.invalid_url ); 361 } else if ( '' === $( '#cite_name' ).val() ) { 362 showLoadingSpinner(); 363 getLinkPreview(); 364 } 365 event.preventDefault(); 366 } 367 } ) 368 .on( 'click', '.clear-kindmeta-button', function( event ) { 369 clearPostProperties(); 370 event.preventDefault(); 371 } ) 372 .on( 'click', 'a.show-kind-details', function( event ) { 373 if ( $( '#kind-details' ).is( ':hidden' ) ) { 374 $( '#kind-author' ).slideUp( 'fast' ).siblings( 'a.show-kind-author' ); 375 $( '#kind-details' ).slideDown( 'fast' ).siblings( 'a.hide-kind-details' ).show().focus(); 162 376 } else { 163 $( '#cite_publication' ).val( response.publication ) ; 164 } 165 166 if ( 'category' in response ) { 167 if ( 'object' === typeof response.category ) { 168 $( '#cite_tags' ).val( response.category.join( ';' ) ); 169 } 170 } 171 alert( PKAPI.success_message ); 172 console.log( response ); 173 }, 174 fail: function( response ) { 175 console.log( response ); 176 alert( response.message ); 177 }, 178 error: function( jqXHR, textStatus, errorThrown ) { 179 alert( jqXHR.responseJSON.message ); 180 console.log( jqXHR ); 181 }, 182 always: hideLoadingSpinner() 183 }); 184 } 185 186 function changeSettings() { 187 kind = $( 'input[name=\'tax_input[kind]\']:checked' ).val(); 188 switch ( kind ) { 189 case 'note': 190 hideTitle(); 191 hideReply(); 192 hideRSVP(); 193 hideRating(); 194 hideTime(); 195 hideMedia(); 196 break; 197 case 'article': 198 showTitle(); 199 hideReply(); 200 hideRSVP(); 201 hideRating(); 202 hideTime(); 203 hideMedia(); 204 break; 205 case 'issue': 206 showTitle(); 207 showReply(); 208 hideRSVP(); 209 hideRating(); 210 hideTime(); 211 hideMedia(); 212 break; 213 case 'listen': 214 case 'jam': 215 case 'watch': 216 case 'read': 217 case 'play': 218 hideTitle(); 219 showReply(); 220 hideRSVP(); 221 showRating(); 222 showTime(); 223 hideMedia(); 224 break; 225 case 'photo': 226 case 'video': 227 case 'audio': 228 hideTitle(); 229 showReply(); 230 hideRSVP(); 231 hideRating(); 232 showTime(); 233 showMedia(); 234 break; 235 case 'rsvp': 236 showReply(); 237 hideTitle(); 238 hideTime(); 239 showRSVP(); 240 hideRating(); 241 hideMedia(); 242 break; 243 case 'review': 244 case 'drink': 245 case 'eat': 246 showReply(); 247 hideTitle(); 248 hideTime(); 249 hideRSVP(); 250 showRating(); 251 hideMedia(); 252 break; 253 default: 254 showReply(); 255 hideTime(); 256 hideTitle(); 257 hideRSVP(); 258 hideRating(); 259 hideMedia(); 260 } 261 } 262 263 function showReply() { 264 $( '#replybox-meta' ).removeClass( 'hidden' ); 265 } 266 267 function hideReply() { 268 $( '#replybox-meta' ).addClass( 'hidden' ); 269 } 270 271 function showMedia() { 272 $( '#add-kind-media' ).removeClass( 'hidden' ); 273 } 274 275 function hideMedia() { 276 $( '#add-kind-media' ).addClass( 'hidden' ); 277 } 278 279 280 function showTitle() { 281 var titlediv = $( '#titlediv' ).detach(); 282 titlediv.prependTo( '#post-body-content' ); 283 } 284 285 function hideTitle() { 286 var titlediv = $( '#titlediv' ).detach(); 287 titlediv.insertAfter( '#postdivrich' ); 288 } 289 290 function showRSVP() { 291 $( '#rsvp-option' ).removeClass( 'hide-if-js' ); 292 } 293 294 function hideRSVP() { 295 $( '#rsvp-option' ).addClass( 'hide-if-js' ); 296 } 297 298 function showRating() { 299 $( '#rating-option' ).removeClass( 'hide-if-js' ); 300 } 301 302 function hideRating() { 303 $( '#rating-option' ).addClass( 'hide-if-js' ); 304 } 305 306 function showTime() { 307 $( '#kind-time' ).removeClass( 'hide-if-js' ); 308 } 309 310 function hideTime() { 311 $( '#kind-time' ).addClass( 'hide-if-js' ); 312 } 313 314 function handleKindMediaWindow() { 315 'use strict'; 316 317 var KindWindow, ImageData, json; 318 319 /** 320 * If an instance of KindWindow already exists, then we can open it 321 * rather than creating a new instance. 322 */ 323 if ( undefined !== KindWindow ) { 324 KindWindow.open(); 325 return; 326 } 327 328 KindWindow = wp.media.frames.KindWindow = wp.media({ 329 title: 'Attach', 330 button: { 331 text: 'Use this media' 332 }, 333 multiple: false 334 }); 335 336 KindWindow.on( 'select', function() { 337 json = KindWindow.state().get( 'selection' ).first().toJSON(); 338 console.log( json ); 339 if ( 0 > $.trim( json.url.length ) ) { 340 return; 341 } 342 $( '#cite_name' ).val( json.title ); 343 $( '#cite_url' ).val( json.url ); 344 $( '#cite_media' ).val( json.id ); 345 $( '#cite_summary' ).val( json.description ); 346 $( '#kind-media-container' ) 347 .children( 'img' ) 348 .attr( 'src', json.url ) 349 .attr( 'alt', json.caption ) 350 .attr( 'title', json.title ) 351 .show() 352 .parent() 353 .removeClass( 'hidden' ); 354 355 $( '#add-kind-media' ).hide(); 356 }); 357 KindWindow.open(); 358 359 } 360 361 jQuery( document ) 362 .on( 'change', '#taxonomy-kind', function( event ) { 363 changeSettings(); 364 event.preventDefault(); 365 }) 366 .on( 'blur', '#cite_url', function( event ) { 367 if ( '' !== $( '#cite_url' ).val() ) { 368 if ( false == checkUrl( $( '#cite_url' ).val() ) ) { 369 alert( 'Invalid URL' ); 370 } else if ( '' === $( '#cite_name' ).val() ) { 371 showLoadingSpinner(); 372 getLinkPreview(); 373 } 374 event.preventDefault(); 375 } 376 }) 377 .on( 'click', '.clear-kindmeta-button', function( event ) { 378 clearPostProperties(); 379 event.preventDefault(); 380 }) 381 .on( 'click', 'a.show-kind-details', function( event ) { 382 if ( $( '#kind-details' ).is( ':hidden' ) ) { 383 $( '#kind-author' ).slideUp( 'fast' ).siblings( 'a.show-kind-author' ); 384 $( '#kind-details' ).slideDown( 'fast' ).siblings( 'a.hide-kind-details' ).show().focus(); 385 } else { 386 $( '#kind-details' ).slideUp( 'fast' ).siblings( 'a.show-kind-details' ).focus(); 387 } 388 event.preventDefault(); 389 }) 390 .on( 'click', 'a.show-kind-author-details', function( event ) { 391 if ( $( '#kind-author' ).is( ':hidden' ) ) { 392 $( '#kind-details' ).slideUp( 'fast' ).siblings( 'a.show-kind-details' ); 393 $( '#kind-author' ).slideDown( 'fast' ).siblings( 'a.hide-kind-author' ).show().focus(); 394 } else { 395 $( '#kind-author' ).slideUp( 'fast' ).siblings( 'a.show-kind-author' ).focus(); 396 } 397 event.preventDefault(); 398 }) 399 .on( 'click', '#add-kind-media', function( event ) { 400 event.preventDefault(); 401 handleKindMediaWindow(); 402 }); 403 }); 377 $( '#kind-details' ).slideUp( 'fast' ).siblings( 'a.show-kind-details' ).focus(); 378 } 379 event.preventDefault(); 380 } ) 381 .on( 'click', 'a.show-kind-author-details', function( event ) { 382 if ( $( '#kind-author' ).is( ':hidden' ) ) { 383 $( '#kind-details' ).slideUp( 'fast' ).siblings( 'a.show-kind-details' ); 384 $( '#kind-author' ).slideDown( 'fast' ).siblings( 'a.hide-kind-author' ).show().focus(); 385 } else { 386 $( '#kind-author' ).slideUp( 'fast' ).siblings( 'a.show-kind-author' ).focus(); 387 } 388 event.preventDefault(); 389 } ) 390 .on( 'click', '#add-kind-media', function( event ) { 391 event.preventDefault(); 392 handleKindMediaWindow(); 393 } ); 394 } ); -
indieweb-post-kinds/tags/4.0.0/languages/indieweb-post-kinds.pot
r3067251 r3738687 1 # Copyright (C) 202 4David Shanske2 # This file is distributed under the same license as the Post Kinds plugin.1 # Copyright (C) 2026 David Shanske 2 # This file is distributed under the GPLv2 or later. 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: Post Kinds 3.7.3\n"5 "Project-Id-Version: Post Kinds 4.0.0\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/indieweb-post-kinds\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 202 4-04-09T03:10:32+00:00\n"12 "POT-Creation-Date: 2026-10-10T20:26:16+00:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 "X-Generator: WP-CLI 2.1 0.0\n"14 "X-Generator: WP-CLI 2.12.0\n" 15 15 "X-Domain: indieweb-post-kinds\n" 16 16 17 17 #. Plugin Name of the plugin 18 #: indieweb-post-kinds.php 19 #: includes/class-kind-config.php: 25120 #: includes/class-kind-config.php: 25221 #: includes/class-kind-config.php: 25818 #: indieweb-post-kinds.php:7 19 #: includes/class-kind-config.php:433 20 #: includes/class-kind-config.php:434 21 #: includes/class-kind-config.php:440 22 22 msgid "Post Kinds" 23 23 msgstr "" 24 24 25 25 #. Plugin URI of the plugin 26 #: indieweb-post-kinds.php 26 #: indieweb-post-kinds.php:8 27 27 msgid "https://wordpress.org/plugins/indieweb-post-kinds/" 28 28 msgstr "" 29 29 30 30 #. Description of the plugin 31 #: indieweb-post-kinds.php 31 #: indieweb-post-kinds.php:9 32 32 msgid "Ever want to reply to someone else's post with a post on your own site? Or to \"like\" someone else's post, but with your own site?" 33 33 msgstr "" 34 34 35 35 #. Author of the plugin 36 #: indieweb-post-kinds.php 36 #: indieweb-post-kinds.php:15 37 37 msgid "David Shanske" 38 38 msgstr "" 39 39 40 40 #. Author URI of the plugin 41 #: indieweb-post-kinds.php 41 #: indieweb-post-kinds.php:16 42 42 msgid "https://david.shanske.com" 43 43 msgstr "" 44 44 45 #: includes/class-kind-config.php:96 45 #: includes/class-kind-config.php:224 46 msgid "The allowed HTML must be a valid JSON object. The previous value has been kept." 47 msgstr "" 48 49 #: includes/class-kind-config.php:261 50 msgid "Kind base" 51 msgstr "" 52 53 #: includes/class-kind-config.php:269 46 54 msgid "Content Options" 47 55 msgstr "" 48 56 49 #: includes/class-kind-config.php: 10557 #: includes/class-kind-config.php:278 50 58 msgid "Select All Kinds You Wish to Use" 51 59 msgstr "" 52 60 53 #: includes/class-kind-config.php: 11261 #: includes/class-kind-config.php:285 54 62 msgid "Default Kind for New Posts" 55 63 msgstr "" 56 64 57 #: includes/class-kind-config.php: 12065 #: includes/class-kind-config.php:293 58 66 msgid "Embed Sites into your Response" 59 67 msgstr "" 60 68 61 #: includes/class-kind-config.php: 13169 #: includes/class-kind-config.php:304 62 70 msgid "Kinds to Show in Main Feed. Selecting None will Disable this Filter" 63 71 msgstr "" 64 72 65 #: includes/class-kind-config.php: 14273 #: includes/class-kind-config.php:315 66 74 msgid "Move Response to Bottom of Content" 67 75 msgstr "" 68 76 69 #: includes/class-kind-config.php: 15377 #: includes/class-kind-config.php:326 70 78 msgid "Display Before Kind" 71 79 msgstr "" 72 80 73 #: includes/class-kind-config.php: 16181 #: includes/class-kind-config.php:334 74 82 msgid "Show Icon" 75 83 msgstr "" 76 84 77 #: includes/class-kind-config.php: 16285 #: includes/class-kind-config.php:335 78 86 msgid "Show Text" 79 87 msgstr "" 80 88 81 #: includes/class-kind-config.php: 16389 #: includes/class-kind-config.php:336 82 90 msgid "Show Icon and Text" 83 91 msgstr "" 84 92 85 #: includes/class-kind-config.php: 16493 #: includes/class-kind-config.php:337 86 94 msgid "Display Nothing" 87 95 msgstr "" 88 96 89 #: includes/class-kind-config.php: 17297 #: includes/class-kind-config.php:345 90 98 msgid "Response Content Allowed Html Elements" 91 99 msgstr "" 92 100 93 #: includes/class-kind-config.php: 183101 #: includes/class-kind-config.php:356 94 102 msgid "Automatically add the Kind to the Title" 95 103 msgstr "" 96 104 97 #: includes/class-kind-config.php: 391105 #: includes/class-kind-config.php:593 98 106 msgid "Indieweb Post Kinds" 99 107 msgstr "" 100 108 101 #: includes/class-kind-config.php: 392109 #: includes/class-kind-config.php:594 102 110 msgid "Adds support for responding and interacting with other sites." 103 111 msgstr "" 104 112 105 #: includes/class-kind-config.php: 415113 #: includes/class-kind-config.php:619 106 114 msgid "Post Properties" 107 115 msgstr "" 108 116 109 #: includes/class-kind-config.php: 416117 #: includes/class-kind-config.php:620 110 118 msgid "" 111 119 "\n" … … 139 147 msgstr "" 140 148 141 #: includes/class-kind-fields.php: 27149 #: includes/class-kind-fields.php:39 142 150 msgid "(GMT-11:00) Niue" 143 151 msgstr "" 144 152 145 #: includes/class-kind-fields.php: 28153 #: includes/class-kind-fields.php:40 146 154 msgid "(GMT-11:00) Pago Pago" 147 155 msgstr "" 148 156 149 #: includes/class-kind-fields.php: 29157 #: includes/class-kind-fields.php:41 150 158 msgid "(GMT-10:00) Hawaii Time" 151 159 msgstr "" 152 160 153 #: includes/class-kind-fields.php: 30161 #: includes/class-kind-fields.php:42 154 162 msgid "(GMT-10:00) Rarotonga" 155 163 msgstr "" 156 164 157 #: includes/class-kind-fields.php: 31165 #: includes/class-kind-fields.php:43 158 166 msgid "(GMT-10:00) Tahiti" 159 167 msgstr "" 160 168 161 #: includes/class-kind-fields.php: 32169 #: includes/class-kind-fields.php:44 162 170 msgid "(GMT-09:30) Marquesas" 163 171 msgstr "" 164 172 165 #: includes/class-kind-fields.php: 33173 #: includes/class-kind-fields.php:45 166 174 msgid "(GMT-09:00) Alaska Time" 167 175 msgstr "" 168 176 169 #: includes/class-kind-fields.php: 34177 #: includes/class-kind-fields.php:46 170 178 msgid "(GMT-09:00) Gambier" 171 179 msgstr "" 172 180 173 #: includes/class-kind-fields.php: 35181 #: includes/class-kind-fields.php:47 174 182 msgid "(GMT-08:00) Pacific Time" 175 183 msgstr "" 176 184 177 #: includes/class-kind-fields.php: 36185 #: includes/class-kind-fields.php:48 178 186 msgid "(GMT-08:00) Pacific Time - Tijuana" 179 187 msgstr "" 180 188 181 #: includes/class-kind-fields.php: 37189 #: includes/class-kind-fields.php:49 182 190 msgid "(GMT-08:00) Pacific Time - Vancouver" 183 191 msgstr "" 184 192 185 #: includes/class-kind-fields.php: 38193 #: includes/class-kind-fields.php:50 186 194 msgid "(GMT-08:00) Pacific Time - Whitehorse" 187 195 msgstr "" 188 196 189 #: includes/class-kind-fields.php: 39197 #: includes/class-kind-fields.php:51 190 198 msgid "(GMT-08:00) Pitcairn" 191 199 msgstr "" 192 200 193 #: includes/class-kind-fields.php: 40201 #: includes/class-kind-fields.php:52 194 202 msgid "(GMT-07:00) Mountain Time - Dawson Creek" 195 203 msgstr "" 196 204 197 #: includes/class-kind-fields.php: 41205 #: includes/class-kind-fields.php:53 198 206 msgid "(GMT-07:00) Mountain Time" 199 207 msgstr "" 200 208 201 #: includes/class-kind-fields.php: 42209 #: includes/class-kind-fields.php:54 202 210 msgid "(GMT-07:00) Mountain Time - Edmonton" 203 211 msgstr "" 204 212 205 #: includes/class-kind-fields.php: 43213 #: includes/class-kind-fields.php:55 206 214 msgid "(GMT-07:00) Mountain Time - Hermosillo" 207 215 msgstr "" 208 216 209 #: includes/class-kind-fields.php: 44217 #: includes/class-kind-fields.php:56 210 218 msgid "(GMT-07:00) Mountain Time - Chihuahua, Mazatlan" 211 219 msgstr "" 212 220 213 #: includes/class-kind-fields.php: 45221 #: includes/class-kind-fields.php:57 214 222 msgid "(GMT-07:00) Mountain Time - Arizona" 215 223 msgstr "" 216 224 217 #: includes/class-kind-fields.php: 46225 #: includes/class-kind-fields.php:58 218 226 msgid "(GMT-07:00) Mountain Time - Yellowknife" 219 227 msgstr "" 220 228 221 #: includes/class-kind-fields.php: 47229 #: includes/class-kind-fields.php:59 222 230 msgid "(GMT-06:00) Belize" 223 231 msgstr "" 224 232 225 #: includes/class-kind-fields.php: 48233 #: includes/class-kind-fields.php:60 226 234 msgid "(GMT-06:00) Central Time" 227 235 msgstr "" 228 236 229 #: includes/class-kind-fields.php: 49237 #: includes/class-kind-fields.php:61 230 238 msgid "(GMT-06:00) Costa Rica" 231 239 msgstr "" 232 240 233 #: includes/class-kind-fields.php: 50241 #: includes/class-kind-fields.php:62 234 242 msgid "(GMT-06:00) El Salvador" 235 243 msgstr "" 236 244 237 #: includes/class-kind-fields.php: 51245 #: includes/class-kind-fields.php:63 238 246 msgid "(GMT-06:00) Guatemala" 239 247 msgstr "" 240 248 241 #: includes/class-kind-fields.php: 52249 #: includes/class-kind-fields.php:64 242 250 msgid "(GMT-06:00) Managua" 243 251 msgstr "" 244 252 245 #: includes/class-kind-fields.php: 53253 #: includes/class-kind-fields.php:65 246 254 msgid "(GMT-06:00) Central Time - Mexico City" 247 255 msgstr "" 248 256 249 #: includes/class-kind-fields.php: 54257 #: includes/class-kind-fields.php:66 250 258 msgid "(GMT-06:00) Central Time - Regina" 251 259 msgstr "" 252 260 253 #: includes/class-kind-fields.php: 55261 #: includes/class-kind-fields.php:67 254 262 msgid "(GMT-06:00) Central Time - Tegucigalpa" 255 263 msgstr "" 256 264 257 #: includes/class-kind-fields.php: 56265 #: includes/class-kind-fields.php:68 258 266 msgid "(GMT-06:00) Central Time - Winnipeg" 259 267 msgstr "" 260 268 261 #: includes/class-kind-fields.php: 57269 #: includes/class-kind-fields.php:69 262 270 msgid "(GMT-06:00) Galapagos" 263 271 msgstr "" 264 272 265 #: includes/class-kind-fields.php: 58273 #: includes/class-kind-fields.php:70 266 274 msgid "(GMT-05:00) Bogota" 267 275 msgstr "" 268 276 269 #: includes/class-kind-fields.php: 59277 #: includes/class-kind-fields.php:71 270 278 msgid "(GMT-05:00) America Cancun" 271 279 msgstr "" 272 280 273 #: includes/class-kind-fields.php: 60281 #: includes/class-kind-fields.php:72 274 282 msgid "(GMT-05:00) Cayman" 275 283 msgstr "" 276 284 277 #: includes/class-kind-fields.php: 61285 #: includes/class-kind-fields.php:73 278 286 msgid "(GMT-05:00) Guayaquil" 279 287 msgstr "" 280 288 281 #: includes/class-kind-fields.php: 62289 #: includes/class-kind-fields.php:74 282 290 msgid "(GMT-05:00) Havana" 283 291 msgstr "" 284 292 285 #: includes/class-kind-fields.php: 63293 #: includes/class-kind-fields.php:75 286 294 msgid "(GMT-05:00) Eastern Time - Iqaluit" 287 295 msgstr "" 288 296 289 #: includes/class-kind-fields.php: 64297 #: includes/class-kind-fields.php:76 290 298 msgid "(GMT-05:00) Jamaica" 291 299 msgstr "" 292 300 293 #: includes/class-kind-fields.php: 65301 #: includes/class-kind-fields.php:77 294 302 msgid "(GMT-05:00) Lima" 295 303 msgstr "" 296 304 297 #: includes/class-kind-fields.php: 66305 #: includes/class-kind-fields.php:78 298 306 msgid "(GMT-05:00) Nassau" 299 307 msgstr "" 300 308 301 #: includes/class-kind-fields.php: 67309 #: includes/class-kind-fields.php:79 302 310 msgid "(GMT-05:00) Eastern Time" 303 311 msgstr "" 304 312 305 #: includes/class-kind-fields.php: 68313 #: includes/class-kind-fields.php:80 306 314 msgid "(GMT-05:00) Panama" 307 315 msgstr "" 308 316 309 #: includes/class-kind-fields.php: 69317 #: includes/class-kind-fields.php:81 310 318 msgid "(GMT-05:00) Port-au-Prince" 311 319 msgstr "" 312 320 313 #: includes/class-kind-fields.php: 70321 #: includes/class-kind-fields.php:82 314 322 msgid "(GMT-05:00) Rio Branco" 315 323 msgstr "" 316 324 317 #: includes/class-kind-fields.php: 71325 #: includes/class-kind-fields.php:83 318 326 msgid "(GMT-05:00) Eastern Time - Toronto" 319 327 msgstr "" 320 328 321 #: includes/class-kind-fields.php: 72329 #: includes/class-kind-fields.php:84 322 330 msgid "(GMT-05:00) Easter Island" 323 331 msgstr "" 324 332 325 #: includes/class-kind-fields.php: 73333 #: includes/class-kind-fields.php:85 326 334 msgid "(GMT-04:30) Caracas" 327 335 msgstr "" 328 336 329 #: includes/class-kind-fields.php: 74337 #: includes/class-kind-fields.php:86 330 338 msgid "(GMT-03:00) Asuncion" 331 339 msgstr "" 332 340 333 #: includes/class-kind-fields.php: 75341 #: includes/class-kind-fields.php:87 334 342 msgid "(GMT-04:00) Barbados" 335 343 msgstr "" 336 344 337 #: includes/class-kind-fields.php: 76345 #: includes/class-kind-fields.php:88 338 346 msgid "(GMT-04:00) Boa Vista" 339 347 msgstr "" 340 348 341 #: includes/class-kind-fields.php: 77349 #: includes/class-kind-fields.php:89 342 350 msgid "(GMT-03:00) Campo Grande" 343 351 msgstr "" 344 352 345 #: includes/class-kind-fields.php: 78353 #: includes/class-kind-fields.php:90 346 354 msgid "(GMT-03:00) Cuiaba" 347 355 msgstr "" 348 356 349 #: includes/class-kind-fields.php: 79357 #: includes/class-kind-fields.php:91 350 358 msgid "(GMT-04:00) Curacao" 351 359 msgstr "" 352 360 353 #: includes/class-kind-fields.php: 80361 #: includes/class-kind-fields.php:92 354 362 msgid "(GMT-04:00) Grand Turk" 355 363 msgstr "" 356 364 357 #: includes/class-kind-fields.php: 81365 #: includes/class-kind-fields.php:93 358 366 msgid "(GMT-04:00) Guyana" 359 367 msgstr "" 360 368 361 #: includes/class-kind-fields.php: 82369 #: includes/class-kind-fields.php:94 362 370 msgid "(GMT-04:00) Atlantic Time - Halifax" 363 371 msgstr "" 364 372 365 #: includes/class-kind-fields.php: 83373 #: includes/class-kind-fields.php:95 366 374 msgid "(GMT-04:00) La Paz" 367 375 msgstr "" 368 376 369 #: includes/class-kind-fields.php: 84377 #: includes/class-kind-fields.php:96 370 378 msgid "(GMT-04:00) Manaus" 371 379 msgstr "" 372 380 373 #: includes/class-kind-fields.php: 85381 #: includes/class-kind-fields.php:97 374 382 msgid "(GMT-04:00) Martinique" 375 383 msgstr "" 376 384 377 #: includes/class-kind-fields.php: 86385 #: includes/class-kind-fields.php:98 378 386 msgid "(GMT-04:00) Port of Spain" 379 387 msgstr "" 380 388 381 #: includes/class-kind-fields.php: 87389 #: includes/class-kind-fields.php:99 382 390 msgid "(GMT-04:00) Porto Velho" 383 391 msgstr "" 384 392 385 #: includes/class-kind-fields.php: 88393 #: includes/class-kind-fields.php:100 386 394 msgid "(GMT-04:00) Puerto Rico" 387 395 msgstr "" 388 396 389 #: includes/class-kind-fields.php: 89397 #: includes/class-kind-fields.php:101 390 398 msgid "(GMT-04:00) Santo Domingo" 391 399 msgstr "" 392 400 393 #: includes/class-kind-fields.php: 90401 #: includes/class-kind-fields.php:102 394 402 msgid "(GMT-04:00) Thule" 395 403 msgstr "" 396 404 397 #: includes/class-kind-fields.php: 91405 #: includes/class-kind-fields.php:103 398 406 msgid "(GMT-04:00) Bermuda" 399 407 msgstr "" 400 408 401 #: includes/class-kind-fields.php: 92409 #: includes/class-kind-fields.php:104 402 410 msgid "(GMT-03:30) Newfoundland Time - St. Johns" 403 411 msgstr "" 404 412 405 #: includes/class-kind-fields.php: 93413 #: includes/class-kind-fields.php:105 406 414 msgid "(GMT-03:00) Araguaina" 407 415 msgstr "" 408 416 409 #: includes/class-kind-fields.php: 94417 #: includes/class-kind-fields.php:106 410 418 msgid "(GMT-03:00) Buenos Aires" 411 419 msgstr "" 412 420 413 #: includes/class-kind-fields.php: 95421 #: includes/class-kind-fields.php:107 414 422 msgid "(GMT-03:00) Salvador" 415 423 msgstr "" 416 424 417 #: includes/class-kind-fields.php: 96425 #: includes/class-kind-fields.php:108 418 426 msgid "(GMT-03:00) Belem" 419 427 msgstr "" 420 428 421 #: includes/class-kind-fields.php: 97429 #: includes/class-kind-fields.php:109 422 430 msgid "(GMT-03:00) Cayenne" 423 431 msgstr "" 424 432 425 #: includes/class-kind-fields.php: 98433 #: includes/class-kind-fields.php:110 426 434 msgid "(GMT-03:00) Fortaleza" 427 435 msgstr "" 428 436 429 #: includes/class-kind-fields.php: 99437 #: includes/class-kind-fields.php:111 430 438 msgid "(GMT-03:00) Godthab" 431 439 msgstr "" 432 440 433 #: includes/class-kind-fields.php:1 00441 #: includes/class-kind-fields.php:112 434 442 msgid "(GMT-03:00) Maceio" 435 443 msgstr "" 436 444 437 #: includes/class-kind-fields.php:1 01445 #: includes/class-kind-fields.php:113 438 446 msgid "(GMT-03:00) Miquelon" 439 447 msgstr "" 440 448 441 #: includes/class-kind-fields.php:1 02449 #: includes/class-kind-fields.php:114 442 450 msgid "(GMT-03:00) Montevideo" 443 451 msgstr "" 444 452 445 #: includes/class-kind-fields.php:1 03453 #: includes/class-kind-fields.php:115 446 454 msgid "(GMT-03:00) Paramaribo" 447 455 msgstr "" 448 456 449 #: includes/class-kind-fields.php:1 04457 #: includes/class-kind-fields.php:116 450 458 msgid "(GMT-03:00) Recife" 451 459 msgstr "" 452 460 453 #: includes/class-kind-fields.php:1 05461 #: includes/class-kind-fields.php:117 454 462 msgid "(GMT-03:00) Santiago" 455 463 msgstr "" 456 464 457 #: includes/class-kind-fields.php:1 06465 #: includes/class-kind-fields.php:118 458 466 msgid "(GMT-02:00) Sao Paulo" 459 467 msgstr "" 460 468 461 #: includes/class-kind-fields.php:1 07469 #: includes/class-kind-fields.php:119 462 470 msgid "(GMT-03:00) Palmer" 463 471 msgstr "" 464 472 465 #: includes/class-kind-fields.php:1 08473 #: includes/class-kind-fields.php:120 466 474 msgid "(GMT-03:00) Rothera" 467 475 msgstr "" 468 476 469 #: includes/class-kind-fields.php:1 09477 #: includes/class-kind-fields.php:121 470 478 msgid "(GMT-03:00) Stanley" 471 479 msgstr "" 472 480 473 #: includes/class-kind-fields.php:1 10481 #: includes/class-kind-fields.php:122 474 482 msgid "(GMT-02:00) Noronha" 475 483 msgstr "" 476 484 477 #: includes/class-kind-fields.php:1 11485 #: includes/class-kind-fields.php:123 478 486 msgid "(GMT-02:00) South Georgia" 479 487 msgstr "" 480 488 481 #: includes/class-kind-fields.php:1 12489 #: includes/class-kind-fields.php:124 482 490 msgid "(GMT-01:00) Scoresbysund" 483 491 msgstr "" 484 492 485 #: includes/class-kind-fields.php:1 13493 #: includes/class-kind-fields.php:125 486 494 msgid "(GMT-01:00) Azores" 487 495 msgstr "" 488 496 489 #: includes/class-kind-fields.php:1 14497 #: includes/class-kind-fields.php:126 490 498 msgid "(GMT-01:00) Cape Verde" 491 499 msgstr "" 492 500 493 #: includes/class-kind-fields.php:1 15501 #: includes/class-kind-fields.php:127 494 502 msgid "(GMT+00:00) Abidjan" 495 503 msgstr "" 496 504 497 #: includes/class-kind-fields.php:1 16505 #: includes/class-kind-fields.php:128 498 506 msgid "(GMT+00:00) Accra" 499 507 msgstr "" 500 508 501 #: includes/class-kind-fields.php:1 17509 #: includes/class-kind-fields.php:129 502 510 msgid "(GMT+00:00) Bissau" 503 511 msgstr "" 504 512 505 #: includes/class-kind-fields.php:1 18513 #: includes/class-kind-fields.php:130 506 514 msgid "(GMT+00:00) Casablanca" 507 515 msgstr "" 508 516 509 #: includes/class-kind-fields.php:1 19517 #: includes/class-kind-fields.php:131 510 518 msgid "(GMT+00:00) El Aaiun" 511 519 msgstr "" 512 520 513 #: includes/class-kind-fields.php:1 20521 #: includes/class-kind-fields.php:132 514 522 msgid "(GMT+00:00) Monrovia" 515 523 msgstr "" 516 524 517 #: includes/class-kind-fields.php:1 21525 #: includes/class-kind-fields.php:133 518 526 msgid "(GMT+00:00) Danmarkshavn" 519 527 msgstr "" 520 528 521 #: includes/class-kind-fields.php:1 22529 #: includes/class-kind-fields.php:134 522 530 msgid "(GMT+00:00) Canary Islands" 523 531 msgstr "" 524 532 525 #: includes/class-kind-fields.php:1 23533 #: includes/class-kind-fields.php:135 526 534 msgid "(GMT+00:00) Faeroe" 527 535 msgstr "" 528 536 529 #: includes/class-kind-fields.php:1 24537 #: includes/class-kind-fields.php:136 530 538 msgid "(GMT+00:00) Reykjavik" 531 539 msgstr "" 532 540 533 #: includes/class-kind-fields.php:1 25541 #: includes/class-kind-fields.php:137 534 542 msgid "(GMT+00:00) GMT (no daylight saving)" 535 543 msgstr "" 536 544 537 #: includes/class-kind-fields.php:1 26545 #: includes/class-kind-fields.php:138 538 546 msgid "(GMT+00:00) Dublin" 539 547 msgstr "" 540 548 541 #: includes/class-kind-fields.php:1 27549 #: includes/class-kind-fields.php:139 542 550 msgid "(GMT+00:00) Lisbon" 543 551 msgstr "" 544 552 545 #: includes/class-kind-fields.php:1 28553 #: includes/class-kind-fields.php:140 546 554 msgid "(GMT+00:00) London" 547 555 msgstr "" 548 556 549 #: includes/class-kind-fields.php:1 29557 #: includes/class-kind-fields.php:141 550 558 msgid "(GMT+01:00) Algiers" 551 559 msgstr "" 552 560 553 #: includes/class-kind-fields.php:1 30561 #: includes/class-kind-fields.php:142 554 562 msgid "(GMT+01:00) Ceuta" 555 563 msgstr "" 556 564 557 #: includes/class-kind-fields.php:1 31565 #: includes/class-kind-fields.php:143 558 566 msgid "(GMT+01:00) Lagos" 559 567 msgstr "" 560 568 561 #: includes/class-kind-fields.php:1 32569 #: includes/class-kind-fields.php:144 562 570 msgid "(GMT+01:00) Ndjamena" 563 571 msgstr "" 564 572 565 #: includes/class-kind-fields.php:1 33573 #: includes/class-kind-fields.php:145 566 574 msgid "(GMT+01:00) Tunis" 567 575 msgstr "" 568 576 569 #: includes/class-kind-fields.php:1 34577 #: includes/class-kind-fields.php:146 570 578 msgid "(GMT+02:00) Windhoek" 571 579 msgstr "" 572 580 573 #: includes/class-kind-fields.php:1 35581 #: includes/class-kind-fields.php:147 574 582 msgid "(GMT+01:00) Amsterdam" 575 583 msgstr "" 576 584 577 #: includes/class-kind-fields.php:1 36585 #: includes/class-kind-fields.php:148 578 586 msgid "(GMT+01:00) Andorra" 579 587 msgstr "" 580 588 581 #: includes/class-kind-fields.php:1 37589 #: includes/class-kind-fields.php:149 582 590 msgid "(GMT+01:00) Central European Time - Belgrade" 583 591 msgstr "" 584 592 585 #: includes/class-kind-fields.php:1 38593 #: includes/class-kind-fields.php:150 586 594 msgid "(GMT+01:00) Berlin" 587 595 msgstr "" 588 596 589 #: includes/class-kind-fields.php:1 39597 #: includes/class-kind-fields.php:151 590 598 msgid "(GMT+01:00) Brussels" 591 599 msgstr "" 592 600 593 #: includes/class-kind-fields.php:1 40601 #: includes/class-kind-fields.php:152 594 602 msgid "(GMT+01:00) Budapest" 595 603 msgstr "" 596 604 597 #: includes/class-kind-fields.php:1 41605 #: includes/class-kind-fields.php:153 598 606 msgid "(GMT+01:00) Copenhagen" 599 607 msgstr "" 600 608 601 #: includes/class-kind-fields.php:1 42609 #: includes/class-kind-fields.php:154 602 610 msgid "(GMT+01:00) Gibraltar" 603 611 msgstr "" 604 612 605 #: includes/class-kind-fields.php:1 43613 #: includes/class-kind-fields.php:155 606 614 msgid "(GMT+01:00) Luxembourg" 607 615 msgstr "" 608 616 609 #: includes/class-kind-fields.php:1 44617 #: includes/class-kind-fields.php:156 610 618 msgid "(GMT+01:00) Madrid" 611 619 msgstr "" 612 620 613 #: includes/class-kind-fields.php:1 45621 #: includes/class-kind-fields.php:157 614 622 msgid "(GMT+01:00) Malta" 615 623 msgstr "" 616 624 617 #: includes/class-kind-fields.php:1 46625 #: includes/class-kind-fields.php:158 618 626 msgid "(GMT+01:00) Monaco" 619 627 msgstr "" 620 628 621 #: includes/class-kind-fields.php:1 47629 #: includes/class-kind-fields.php:159 622 630 msgid "(GMT+01:00) Oslo" 623 631 msgstr "" 624 632 625 #: includes/class-kind-fields.php:1 48633 #: includes/class-kind-fields.php:160 626 634 msgid "(GMT+01:00) Paris" 627 635 msgstr "" 628 636 629 #: includes/class-kind-fields.php:1 49637 #: includes/class-kind-fields.php:161 630 638 msgid "(GMT+01:00) Central European Time - Prague" 631 639 msgstr "" 632 640 633 #: includes/class-kind-fields.php:1 50641 #: includes/class-kind-fields.php:162 634 642 msgid "(GMT+01:00) Rome" 635 643 msgstr "" 636 644 637 #: includes/class-kind-fields.php:1 51645 #: includes/class-kind-fields.php:163 638 646 msgid "(GMT+01:00) Stockholm" 639 647 msgstr "" 640 648 641 #: includes/class-kind-fields.php:1 52649 #: includes/class-kind-fields.php:164 642 650 msgid "(GMT+01:00) Tirane" 643 651 msgstr "" 644 652 645 #: includes/class-kind-fields.php:1 53653 #: includes/class-kind-fields.php:165 646 654 msgid "(GMT+01:00) Vienna" 647 655 msgstr "" 648 656 649 #: includes/class-kind-fields.php:1 54657 #: includes/class-kind-fields.php:166 650 658 msgid "(GMT+01:00) Warsaw" 651 659 msgstr "" 652 660 653 #: includes/class-kind-fields.php:1 55661 #: includes/class-kind-fields.php:167 654 662 msgid "(GMT+01:00) Zurich" 655 663 msgstr "" 656 664 657 #: includes/class-kind-fields.php:1 56665 #: includes/class-kind-fields.php:168 658 666 msgid "(GMT+02:00) Cairo" 659 667 msgstr "" 660 668 661 #: includes/class-kind-fields.php:1 57669 #: includes/class-kind-fields.php:169 662 670 msgid "(GMT+02:00) Johannesburg" 663 671 msgstr "" 664 672 665 #: includes/class-kind-fields.php:1 58673 #: includes/class-kind-fields.php:170 666 674 msgid "(GMT+02:00) Maputo" 667 675 msgstr "" 668 676 669 #: includes/class-kind-fields.php:1 59677 #: includes/class-kind-fields.php:171 670 678 msgid "(GMT+02:00) Tripoli" 671 679 msgstr "" 672 680 673 #: includes/class-kind-fields.php:1 60681 #: includes/class-kind-fields.php:172 674 682 msgid "(GMT+02:00) Amman" 675 683 msgstr "" 676 684 677 #: includes/class-kind-fields.php:1 61685 #: includes/class-kind-fields.php:173 678 686 msgid "(GMT+02:00) Beirut" 679 687 msgstr "" 680 688 681 #: includes/class-kind-fields.php:1 62689 #: includes/class-kind-fields.php:174 682 690 msgid "(GMT+02:00) Damascus" 683 691 msgstr "" 684 692 685 #: includes/class-kind-fields.php:1 63693 #: includes/class-kind-fields.php:175 686 694 msgid "(GMT+02:00) Gaza" 687 695 msgstr "" 688 696 689 #: includes/class-kind-fields.php:1 64697 #: includes/class-kind-fields.php:176 690 698 msgid "(GMT+02:00) Jerusalem" 691 699 msgstr "" 692 700 693 #: includes/class-kind-fields.php:1 65701 #: includes/class-kind-fields.php:177 694 702 msgid "(GMT+02:00) Nicosia" 695 703 msgstr "" 696 704 697 #: includes/class-kind-fields.php:1 66705 #: includes/class-kind-fields.php:178 698 706 msgid "(GMT+02:00) Athens" 699 707 msgstr "" 700 708 701 #: includes/class-kind-fields.php:1 67709 #: includes/class-kind-fields.php:179 702 710 msgid "(GMT+02:00) Bucharest" 703 711 msgstr "" 704 712 705 #: includes/class-kind-fields.php:1 68713 #: includes/class-kind-fields.php:180 706 714 msgid "(GMT+02:00) Chisinau" 707 715 msgstr "" 708 716 709 #: includes/class-kind-fields.php:1 69717 #: includes/class-kind-fields.php:181 710 718 msgid "(GMT+02:00) Helsinki" 711 719 msgstr "" 712 720 713 #: includes/class-kind-fields.php:1 70721 #: includes/class-kind-fields.php:182 714 722 msgid "(GMT+02:00) Istanbul" 715 723 msgstr "" 716 724 717 #: includes/class-kind-fields.php:1 71725 #: includes/class-kind-fields.php:183 718 726 msgid "(GMT+02:00) Moscow-01 - Kaliningrad" 719 727 msgstr "" 720 728 721 #: includes/class-kind-fields.php:1 72729 #: includes/class-kind-fields.php:184 722 730 msgid "(GMT+02:00) Kiev" 723 731 msgstr "" 724 732 725 #: includes/class-kind-fields.php:1 73733 #: includes/class-kind-fields.php:185 726 734 msgid "(GMT+02:00) Riga" 727 735 msgstr "" 728 736 729 #: includes/class-kind-fields.php:1 74737 #: includes/class-kind-fields.php:186 730 738 msgid "(GMT+02:00) Sofia" 731 739 msgstr "" 732 740 733 #: includes/class-kind-fields.php:1 75741 #: includes/class-kind-fields.php:187 734 742 msgid "(GMT+02:00) Tallinn" 735 743 msgstr "" 736 744 737 #: includes/class-kind-fields.php:1 76745 #: includes/class-kind-fields.php:188 738 746 msgid "(GMT+02:00) Vilnius" 739 747 msgstr "" 740 748 741 #: includes/class-kind-fields.php:1 77749 #: includes/class-kind-fields.php:189 742 750 msgid "(GMT+03:00) Khartoum" 743 751 msgstr "" 744 752 745 #: includes/class-kind-fields.php:1 78753 #: includes/class-kind-fields.php:190 746 754 msgid "(GMT+03:00) Nairobi" 747 755 msgstr "" 748 756 749 #: includes/class-kind-fields.php:1 79757 #: includes/class-kind-fields.php:191 750 758 msgid "(GMT+03:00) Syowa" 751 759 msgstr "" 752 760 753 #: includes/class-kind-fields.php:1 80761 #: includes/class-kind-fields.php:192 754 762 msgid "(GMT+03:00) Baghdad" 755 763 msgstr "" 756 764 757 #: includes/class-kind-fields.php:1 81765 #: includes/class-kind-fields.php:193 758 766 msgid "(GMT+03:00) Qatar" 759 767 msgstr "" 760 768 761 #: includes/class-kind-fields.php:1 82769 #: includes/class-kind-fields.php:194 762 770 msgid "(GMT+03:00) Riyadh" 763 771 msgstr "" 764 772 765 #: includes/class-kind-fields.php:1 83773 #: includes/class-kind-fields.php:195 766 774 msgid "(GMT+03:00) Minsk" 767 775 msgstr "" 768 776 769 #: includes/class-kind-fields.php:1 84777 #: includes/class-kind-fields.php:196 770 778 msgid "(GMT+03:00) Moscow+00 - Moscow" 771 779 msgstr "" 772 780 773 #: includes/class-kind-fields.php:1 85781 #: includes/class-kind-fields.php:197 774 782 msgid "(GMT+03:30) Tehran" 775 783 msgstr "" 776 784 777 #: includes/class-kind-fields.php:1 86785 #: includes/class-kind-fields.php:198 778 786 msgid "(GMT+04:00) Baku" 779 787 msgstr "" 780 788 781 #: includes/class-kind-fields.php:1 87789 #: includes/class-kind-fields.php:199 782 790 msgid "(GMT+04:00) Dubai" 783 791 msgstr "" 784 792 785 #: includes/class-kind-fields.php: 188793 #: includes/class-kind-fields.php:200 786 794 msgid "(GMT+04:00) Tbilisi" 787 795 msgstr "" 788 796 789 #: includes/class-kind-fields.php: 189797 #: includes/class-kind-fields.php:201 790 798 msgid "(GMT+04:00) Yerevan" 791 799 msgstr "" 792 800 793 #: includes/class-kind-fields.php: 190801 #: includes/class-kind-fields.php:202 794 802 msgid "(GMT+04:00) Moscow+01 - Samara" 795 803 msgstr "" 796 804 797 #: includes/class-kind-fields.php: 191805 #: includes/class-kind-fields.php:203 798 806 msgid "(GMT+04:00) Mahe" 799 807 msgstr "" 800 808 801 #: includes/class-kind-fields.php: 192809 #: includes/class-kind-fields.php:204 802 810 msgid "(GMT+04:00) Mauritius" 803 811 msgstr "" 804 812 805 #: includes/class-kind-fields.php: 193813 #: includes/class-kind-fields.php:205 806 814 msgid "(GMT+04:00) Reunion" 807 815 msgstr "" 808 816 809 #: includes/class-kind-fields.php: 194817 #: includes/class-kind-fields.php:206 810 818 msgid "(GMT+04:30) Kabul" 811 819 msgstr "" 812 820 813 #: includes/class-kind-fields.php: 195821 #: includes/class-kind-fields.php:207 814 822 msgid "(GMT+05:00) Mawson" 815 823 msgstr "" 816 824 817 #: includes/class-kind-fields.php: 196825 #: includes/class-kind-fields.php:208 818 826 msgid "(GMT+05:00) Aqtau" 819 827 msgstr "" 820 828 821 #: includes/class-kind-fields.php: 197829 #: includes/class-kind-fields.php:209 822 830 msgid "(GMT+05:00) Aqtobe" 823 831 msgstr "" 824 832 825 #: includes/class-kind-fields.php: 198833 #: includes/class-kind-fields.php:210 826 834 msgid "(GMT+05:00) Ashgabat" 827 835 msgstr "" 828 836 829 #: includes/class-kind-fields.php: 199837 #: includes/class-kind-fields.php:211 830 838 msgid "(GMT+05:00) Dushanbe" 831 839 msgstr "" 832 840 833 #: includes/class-kind-fields.php:2 00841 #: includes/class-kind-fields.php:212 834 842 msgid "(GMT+05:00) Karachi" 835 843 msgstr "" 836 844 837 #: includes/class-kind-fields.php:2 01845 #: includes/class-kind-fields.php:213 838 846 msgid "(GMT+05:00) Tashkent" 839 847 msgstr "" 840 848 841 #: includes/class-kind-fields.php:2 02849 #: includes/class-kind-fields.php:214 842 850 msgid "(GMT+05:00) Moscow+02 - Yekaterinburg" 843 851 msgstr "" 844 852 845 #: includes/class-kind-fields.php:2 03853 #: includes/class-kind-fields.php:215 846 854 msgid "(GMT+05:00) Kerguelen" 847 855 msgstr "" 848 856 849 #: includes/class-kind-fields.php:2 04857 #: includes/class-kind-fields.php:216 850 858 msgid "(GMT+05:00) Maldives" 851 859 msgstr "" 852 860 853 #: includes/class-kind-fields.php:2 05861 #: includes/class-kind-fields.php:217 854 862 msgid "(GMT+05:30) India Standard Time" 855 863 msgstr "" 856 864 857 #: includes/class-kind-fields.php:2 06865 #: includes/class-kind-fields.php:218 858 866 msgid "(GMT+05:30) Colombo" 859 867 msgstr "" 860 868 861 #: includes/class-kind-fields.php:2 07869 #: includes/class-kind-fields.php:219 862 870 msgid "(GMT+05:45) Katmandu" 863 871 msgstr "" 864 872 865 #: includes/class-kind-fields.php:2 08873 #: includes/class-kind-fields.php:220 866 874 msgid "(GMT+06:00) Vostok" 867 875 msgstr "" 868 876 869 #: includes/class-kind-fields.php:2 09877 #: includes/class-kind-fields.php:221 870 878 msgid "(GMT+06:00) Almaty" 871 879 msgstr "" 872 880 873 #: includes/class-kind-fields.php:2 10881 #: includes/class-kind-fields.php:222 874 882 msgid "(GMT+06:00) Bishkek" 875 883 msgstr "" 876 884 877 #: includes/class-kind-fields.php:2 11885 #: includes/class-kind-fields.php:223 878 886 msgid "(GMT+06:00) Dhaka" 879 887 msgstr "" 880 888 881 #: includes/class-kind-fields.php:2 12889 #: includes/class-kind-fields.php:224 882 890 msgid "(GMT+06:00) Moscow+03 - Omsk, Novosibirsk" 883 891 msgstr "" 884 892 885 #: includes/class-kind-fields.php:2 13893 #: includes/class-kind-fields.php:225 886 894 msgid "(GMT+06:00) Thimphu" 887 895 msgstr "" 888 896 889 #: includes/class-kind-fields.php:2 14897 #: includes/class-kind-fields.php:226 890 898 msgid "(GMT+06:00) Chagos" 891 899 msgstr "" 892 900 893 #: includes/class-kind-fields.php:2 15901 #: includes/class-kind-fields.php:227 894 902 msgid "(GMT+06:30) Rangoon" 895 903 msgstr "" 896 904 897 #: includes/class-kind-fields.php:2 16905 #: includes/class-kind-fields.php:228 898 906 msgid "(GMT+06:30) Cocos" 899 907 msgstr "" 900 908 901 #: includes/class-kind-fields.php:2 17909 #: includes/class-kind-fields.php:229 902 910 msgid "(GMT+07:00) Davis" 903 911 msgstr "" 904 912 905 #: includes/class-kind-fields.php:2 18913 #: includes/class-kind-fields.php:230 906 914 msgid "(GMT+07:00) Bangkok" 907 915 msgstr "" 908 916 909 #: includes/class-kind-fields.php:2 19917 #: includes/class-kind-fields.php:231 910 918 msgid "(GMT+07:00) Hovd" 911 919 msgstr "" 912 920 913 #: includes/class-kind-fields.php:2 20921 #: includes/class-kind-fields.php:232 914 922 msgid "(GMT+07:00) Jakarta" 915 923 msgstr "" 916 924 917 #: includes/class-kind-fields.php:2 21925 #: includes/class-kind-fields.php:233 918 926 msgid "(GMT+07:00) Moscow+04 - Krasnoyarsk" 919 927 msgstr "" 920 928 921 #: includes/class-kind-fields.php:2 22929 #: includes/class-kind-fields.php:234 922 930 msgid "(GMT+07:00) Hanoi" 923 931 msgstr "" 924 932 925 #: includes/class-kind-fields.php:2 23933 #: includes/class-kind-fields.php:235 926 934 msgid "(GMT+07:00) Ho Chi Minh" 927 935 msgstr "" 928 936 929 #: includes/class-kind-fields.php:2 24937 #: includes/class-kind-fields.php:236 930 938 msgid "(GMT+07:00) Christmas" 931 939 msgstr "" 932 940 933 #: includes/class-kind-fields.php:2 25941 #: includes/class-kind-fields.php:237 934 942 msgid "(GMT+08:00) Casey" 935 943 msgstr "" 936 944 937 #: includes/class-kind-fields.php:2 26945 #: includes/class-kind-fields.php:238 938 946 msgid "(GMT+08:00) Brunei" 939 947 msgstr "" 940 948 941 #: includes/class-kind-fields.php:2 27949 #: includes/class-kind-fields.php:239 942 950 msgid "(GMT+08:00) Choibalsan" 943 951 msgstr "" 944 952 945 #: includes/class-kind-fields.php:2 28953 #: includes/class-kind-fields.php:240 946 954 msgid "(GMT+08:00) Hong Kong" 947 955 msgstr "" 948 956 949 #: includes/class-kind-fields.php:2 29957 #: includes/class-kind-fields.php:241 950 958 msgid "(GMT+08:00) Moscow+05 - Irkutsk" 951 959 msgstr "" 952 960 953 #: includes/class-kind-fields.php:2 30961 #: includes/class-kind-fields.php:242 954 962 msgid "(GMT+08:00) Kuala Lumpur" 955 963 msgstr "" 956 964 957 #: includes/class-kind-fields.php:2 31965 #: includes/class-kind-fields.php:243 958 966 msgid "(GMT+08:00) Macau" 959 967 msgstr "" 960 968 961 #: includes/class-kind-fields.php:2 32969 #: includes/class-kind-fields.php:244 962 970 msgid "(GMT+08:00) Makassar" 963 971 msgstr "" 964 972 965 #: includes/class-kind-fields.php:2 33973 #: includes/class-kind-fields.php:245 966 974 msgid "(GMT+08:00) Manila" 967 975 msgstr "" 968 976 969 #: includes/class-kind-fields.php:2 34977 #: includes/class-kind-fields.php:246 970 978 msgid "(GMT+08:00) China Time - Beijing" 971 979 msgstr "" 972 980 973 #: includes/class-kind-fields.php:2 35981 #: includes/class-kind-fields.php:247 974 982 msgid "(GMT+08:00) Singapore" 975 983 msgstr "" 976 984 977 #: includes/class-kind-fields.php:2 36985 #: includes/class-kind-fields.php:248 978 986 msgid "(GMT+08:00) Taipei" 979 987 msgstr "" 980 988 981 #: includes/class-kind-fields.php:2 37989 #: includes/class-kind-fields.php:249 982 990 msgid "(GMT+08:00) Ulaanbaatar" 983 991 msgstr "" 984 992 985 #: includes/class-kind-fields.php:2 38993 #: includes/class-kind-fields.php:250 986 994 msgid "(GMT+08:00) Western Time - Perth" 987 995 msgstr "" 988 996 989 #: includes/class-kind-fields.php:2 39997 #: includes/class-kind-fields.php:251 990 998 msgid "(GMT+08:30) Pyongyang" 991 999 msgstr "" 992 1000 993 #: includes/class-kind-fields.php:2 401001 #: includes/class-kind-fields.php:252 994 1002 msgid "(GMT+09:00) Dili" 995 1003 msgstr "" 996 1004 997 #: includes/class-kind-fields.php:2 411005 #: includes/class-kind-fields.php:253 998 1006 msgid "(GMT+09:00) Jayapura" 999 1007 msgstr "" 1000 1008 1001 #: includes/class-kind-fields.php:2 421009 #: includes/class-kind-fields.php:254 1002 1010 msgid "(GMT+09:00) Seoul" 1003 1011 msgstr "" 1004 1012 1005 #: includes/class-kind-fields.php:2 431013 #: includes/class-kind-fields.php:255 1006 1014 msgid "(GMT+09:00) Tokyo" 1007 1015 msgstr "" 1008 1016 1009 #: includes/class-kind-fields.php:2 441017 #: includes/class-kind-fields.php:256 1010 1018 msgid "(GMT+09:00) Moscow+06 - Yakutsk" 1011 1019 msgstr "" 1012 1020 1013 #: includes/class-kind-fields.php:2 451021 #: includes/class-kind-fields.php:257 1014 1022 msgid "(GMT+09:00) Palau" 1015 1023 msgstr "" 1016 1024 1017 #: includes/class-kind-fields.php:2 461025 #: includes/class-kind-fields.php:258 1018 1026 msgid "(GMT+10:30) Central Time - Adelaide" 1019 1027 msgstr "" 1020 1028 1021 #: includes/class-kind-fields.php:2 471029 #: includes/class-kind-fields.php:259 1022 1030 msgid "(GMT+09:30) Central Time - Darwin" 1023 1031 msgstr "" 1024 1032 1025 #: includes/class-kind-fields.php:2 491033 #: includes/class-kind-fields.php:261 1026 1034 msgid "(GMT+10:00) Moscow+07 - Magadan" 1027 1035 msgstr "" 1028 1036 1029 #: includes/class-kind-fields.php:2 501037 #: includes/class-kind-fields.php:262 1030 1038 msgid "(GMT+10:00) Moscow+07 - Yuzhno-Sakhalinsk" 1031 1039 msgstr "" 1032 1040 1033 #: includes/class-kind-fields.php:2 511041 #: includes/class-kind-fields.php:263 1034 1042 msgid "(GMT+10:00) Eastern Time - Brisbane" 1035 1043 msgstr "" 1036 1044 1037 #: includes/class-kind-fields.php:2 521045 #: includes/class-kind-fields.php:264 1038 1046 msgid "(GMT+11:00) Eastern Time - Hobart" 1039 1047 msgstr "" 1040 1048 1041 #: includes/class-kind-fields.php:2 531049 #: includes/class-kind-fields.php:265 1042 1050 msgid "(GMT+11:00) Eastern Time - Melbourne, Sydney" 1043 1051 msgstr "" 1044 1052 1045 #: includes/class-kind-fields.php:2 541053 #: includes/class-kind-fields.php:266 1046 1054 msgid "(GMT+10:00) Truk" 1047 1055 msgstr "" 1048 1056 1049 #: includes/class-kind-fields.php:2 551057 #: includes/class-kind-fields.php:267 1050 1058 msgid "(GMT+10:00) Guam" 1051 1059 msgstr "" 1052 1060 1053 #: includes/class-kind-fields.php:2 561061 #: includes/class-kind-fields.php:268 1054 1062 msgid "(GMT+10:00) Port Moresby" 1055 1063 msgstr "" 1056 1064 1057 #: includes/class-kind-fields.php:2 571065 #: includes/class-kind-fields.php:269 1058 1066 msgid "(GMT+11:00) Efate" 1059 1067 msgstr "" 1060 1068 1061 #: includes/class-kind-fields.php:2 581069 #: includes/class-kind-fields.php:270 1062 1070 msgid "(GMT+11:00) Guadalcanal" 1063 1071 msgstr "" 1064 1072 1065 #: includes/class-kind-fields.php:2 591073 #: includes/class-kind-fields.php:271 1066 1074 msgid "(GMT+11:00) Kosrae" 1067 1075 msgstr "" 1068 1076 1069 #: includes/class-kind-fields.php:2 601077 #: includes/class-kind-fields.php:272 1070 1078 msgid "(GMT+11:00) Norfolk" 1071 1079 msgstr "" 1072 1080 1073 #: includes/class-kind-fields.php:2 611081 #: includes/class-kind-fields.php:273 1074 1082 msgid "(GMT+11:00) Noumea" 1075 1083 msgstr "" 1076 1084 1077 #: includes/class-kind-fields.php:2 621085 #: includes/class-kind-fields.php:274 1078 1086 msgid "(GMT+11:00) Ponape" 1079 1087 msgstr "" 1080 1088 1081 #: includes/class-kind-fields.php:2 631089 #: includes/class-kind-fields.php:275 1082 1090 msgid "(GMT+12:00) Moscow+09 - Petropavlovsk-Kamchatskiy" 1083 1091 msgstr "" 1084 1092 1085 #: includes/class-kind-fields.php:2 641093 #: includes/class-kind-fields.php:276 1086 1094 msgid "(GMT+13:00) Auckland" 1087 1095 msgstr "" 1088 1096 1089 #: includes/class-kind-fields.php:2 651097 #: includes/class-kind-fields.php:277 1090 1098 msgid "(GMT+13:00) Fiji" 1091 1099 msgstr "" 1092 1100 1093 #: includes/class-kind-fields.php:2 661101 #: includes/class-kind-fields.php:278 1094 1102 msgid "(GMT+12:00) Funafuti" 1095 1103 msgstr "" 1096 1104 1097 #: includes/class-kind-fields.php:2 671105 #: includes/class-kind-fields.php:279 1098 1106 msgid "(GMT+12:00) Kwajalein" 1099 1107 msgstr "" 1100 1108 1101 #: includes/class-kind-fields.php:2 681109 #: includes/class-kind-fields.php:280 1102 1110 msgid "(GMT+12:00) Majuro" 1103 1111 msgstr "" 1104 1112 1105 #: includes/class-kind-fields.php:2 691113 #: includes/class-kind-fields.php:281 1106 1114 msgid "(GMT+12:00) Nauru" 1107 1115 msgstr "" 1108 1116 1109 #: includes/class-kind-fields.php:2 701117 #: includes/class-kind-fields.php:282 1110 1118 msgid "(GMT+12:00) Tarawa" 1111 1119 msgstr "" 1112 1120 1113 #: includes/class-kind-fields.php:2 711121 #: includes/class-kind-fields.php:283 1114 1122 msgid "(GMT+12:00) Wake" 1115 1123 msgstr "" 1116 1124 1117 #: includes/class-kind-fields.php:2 721125 #: includes/class-kind-fields.php:284 1118 1126 msgid "(GMT+12:00) Wallis" 1119 1127 msgstr "" 1120 1128 1121 #: includes/class-kind-fields.php:2 731129 #: includes/class-kind-fields.php:285 1122 1130 msgid "(GMT+14:00) Apia" 1123 1131 msgstr "" 1124 1132 1125 #: includes/class-kind-fields.php:2 741133 #: includes/class-kind-fields.php:286 1126 1134 msgid "(GMT+13:00) Enderbury" 1127 1135 msgstr "" 1128 1136 1129 #: includes/class-kind-fields.php:2 751137 #: includes/class-kind-fields.php:287 1130 1138 msgid "(GMT+13:00) Fakaofo" 1131 1139 msgstr "" 1132 1140 1133 #: includes/class-kind-fields.php:2 761141 #: includes/class-kind-fields.php:288 1134 1142 msgid "(GMT+13:00) Tongatapu" 1135 1143 msgstr "" 1136 1144 1137 #: includes/class-kind-fields.php:2 771145 #: includes/class-kind-fields.php:289 1138 1146 msgid "(GMT+14:00) Kiritimati" 1139 1147 msgstr "" 1140 1148 1141 #: includes/class-kind-fields.php:3 851149 #: includes/class-kind-fields.php:396 1142 1150 msgid "Author Name" 1143 1151 msgstr "" 1144 1152 1145 #: includes/class-kind-fields.php: 3891146 #: templates/reply-author.php: 121153 #: includes/class-kind-fields.php:400 1154 #: templates/reply-author.php:26 1147 1155 msgid "Author URL" 1148 1156 msgstr "" 1149 1157 1150 #: includes/class-kind-fields.php: 3931158 #: includes/class-kind-fields.php:404 1151 1159 msgid "Author Photo" 1152 1160 msgstr "" 1153 1161 1154 #: includes/class-kind-media-metadata.php: 661155 #: includes/register-kinds.php:5 421156 #: includes/register-kinds.php:6 071162 #: includes/class-kind-media-metadata.php:125 1163 #: includes/register-kinds.php:551 1164 #: includes/register-kinds.php:616 1157 1165 msgid "Artist" 1158 1166 msgstr "" 1159 1167 1160 #: includes/class-kind-media-metadata.php: 671168 #: includes/class-kind-media-metadata.php:126 1161 1169 msgid "The creator of the image" 1162 1170 msgstr "" 1163 1171 1164 #: includes/class-kind-media-metadata.php: 721172 #: includes/class-kind-media-metadata.php:131 1165 1173 msgid "Artist URL" 1166 1174 msgstr "" 1167 1175 1168 #: includes/class-kind-media-metadata.php: 731176 #: includes/class-kind-media-metadata.php:132 1169 1177 msgid "The Artists URL or webpage" 1170 1178 msgstr "" 1171 1179 1172 #: includes/class-kind-menu-widget.php: 101180 #: includes/class-kind-menu-widget.php:25 1173 1181 msgid "Kind Menu Widget" 1174 1182 msgstr "" 1175 1183 1176 #: includes/class-kind-menu-widget.php: 131184 #: includes/class-kind-menu-widget.php:28 1177 1185 msgid "A widget that allows you to display a menu of kind archives" 1178 1186 msgstr "" 1179 1187 1180 1188 #. translators: 1. Blog Title 2. Kind Singular Name 1181 #: includes/class-kind-menu-widget.php:62 1182 msgid "title=\"%1$s %2$s Feed\"" 1183 msgstr "" 1184 1185 #: includes/class-kind-menu-widget.php:78 1189 #: includes/class-kind-menu-widget.php:80 1190 #, php-format 1191 msgid "%1$s %2$s Feed" 1192 msgstr "" 1193 1194 #: includes/class-kind-menu-widget.php:96 1186 1195 msgid "All Posts" 1187 1196 msgstr "" 1188 1197 1189 #: includes/class-kind-menu-widget.php:1 221190 #: includes/class-kind-post-widget.php: 951198 #: includes/class-kind-menu-widget.php:149 1199 #: includes/class-kind-post-widget.php:118 1191 1200 msgid "Title: " 1192 1201 msgstr "" 1193 1202 1194 #: includes/class-kind-menu-widget.php:1 431203 #: includes/class-kind-menu-widget.php:169 1195 1204 msgid "Show Link to All:" 1196 1205 msgstr "" 1197 1206 1198 #: includes/class-kind-menu-widget.php:1 481207 #: includes/class-kind-menu-widget.php:174 1199 1208 msgid "Show Count:" 1200 1209 msgstr "" 1201 1210 1202 #: includes/class-kind-metabox.php: 1541211 #: includes/class-kind-metabox.php:211 1203 1212 msgid "Your URL has been successfully retrieved and parsed" 1204 1213 msgstr "" 1205 1214 1206 #: includes/class-kind-metabox.php: 1551215 #: includes/class-kind-metabox.php:212 1207 1216 msgid "Are you sure you want to clear post properties?" 1208 1217 msgstr "" 1209 1218 1210 #: includes/class-kind-metabox.php:272 1219 #: includes/class-kind-metabox.php:213 1220 msgid "Error: Unable to Retrieve" 1221 msgstr "" 1222 1223 #: includes/class-kind-metabox.php:214 1224 msgid "Invalid URL" 1225 msgstr "" 1226 1227 #: includes/class-kind-metabox.php:215 1228 msgid "Attach" 1229 msgstr "" 1230 1231 #: includes/class-kind-metabox.php:216 1232 msgid "Use this media" 1233 msgstr "" 1234 1235 #: includes/class-kind-metabox.php:346 1211 1236 msgid "Very Dissatisfied" 1212 1237 msgstr "" 1213 1238 1214 #: includes/class-kind-metabox.php: 2731239 #: includes/class-kind-metabox.php:347 1215 1240 msgid "Dissatisfied" 1216 1241 msgstr "" 1217 1242 1218 #: includes/class-kind-metabox.php: 2741243 #: includes/class-kind-metabox.php:348 1219 1244 msgid "Neutral" 1220 1245 msgstr "" 1221 1246 1222 #: includes/class-kind-metabox.php: 2751247 #: includes/class-kind-metabox.php:349 1223 1248 msgid "Satisfied" 1224 1249 msgstr "" 1225 1250 1226 #: includes/class-kind-metabox.php: 2761251 #: includes/class-kind-metabox.php:350 1227 1252 msgid "Very Satisfied" 1228 1253 msgstr "" 1229 1254 1230 #: includes/class-kind-metabox.php:3 001231 #: includes/register-kinds.php:4 591255 #: includes/class-kind-metabox.php:376 1256 #: includes/register-kinds.php:468 1232 1257 msgid "Yes" 1233 1258 msgstr "" 1234 1259 1235 #: includes/class-kind-metabox.php:301 1260 #: includes/class-kind-metabox.php:377 1261 #: includes/register-kinds.php:469 1262 msgid "No" 1263 msgstr "" 1264 1265 #: includes/class-kind-metabox.php:378 1266 #: includes/register-kinds.php:470 1267 msgid "Maybe" 1268 msgstr "" 1269 1270 #: includes/class-kind-metabox.php:379 1271 #: includes/register-kinds.php:471 1272 msgid "Interested" 1273 msgstr "" 1274 1275 #: includes/class-kind-metabox.php:380 1276 #: includes/register-kinds.php:472 1277 msgid "Remote" 1278 msgstr "" 1279 1280 #: includes/class-kind-metabox.php:404 1236 1281 #: includes/register-kinds.php:460 1237 msgid "No" 1238 msgstr "" 1239 1240 #: includes/class-kind-metabox.php:302 1241 #: includes/register-kinds.php:461 1242 msgid "Maybe" 1243 msgstr "" 1244 1245 #: includes/class-kind-metabox.php:303 1246 #: includes/register-kinds.php:462 1247 msgid "Interested" 1248 msgstr "" 1249 1250 #: includes/class-kind-metabox.php:304 1251 #: includes/register-kinds.php:463 1252 msgid "Remote" 1253 msgstr "" 1254 1255 #: includes/class-kind-metabox.php:326 1256 #: includes/register-kinds.php:451 1282 #: views/kind.php:81 1257 1283 msgid "RSVP" 1258 1284 msgstr "" 1259 1285 1260 #: includes/class-kind-metabox.php: 3421286 #: includes/class-kind-metabox.php:422 1261 1287 msgid "Rating" 1262 1288 msgstr "" 1263 1289 1264 #: includes/class-kind-metabox.php: 3571290 #: includes/class-kind-metabox.php:461 1265 1291 msgid "Response Properties" 1266 1292 msgstr "" 1267 1293 1268 #: includes/class-kind-plugins.php: 1331294 #: includes/class-kind-plugins.php:209 1269 1295 msgctxt "direct article" 1270 1296 msgid "this" 1271 1297 msgstr "" 1272 1298 1273 #: includes/class-kind-post-widget.php:10 1299 #: includes/class-kind-plugins.php:535 1300 msgid "Nothing could be parsed from the URL." 1301 msgstr "" 1302 1303 #: includes/class-kind-post-widget.php:25 1274 1304 msgid "Kind Post Widget" 1275 1305 msgstr "" 1276 1306 1277 #: includes/class-kind-post-widget.php: 131307 #: includes/class-kind-post-widget.php:28 1278 1308 msgid "A widget that allows you to display a list of posts by type" 1279 1309 msgstr "" 1280 1310 1281 #: includes/class-kind-post-widget.php:63 1282 msgid "No Posts Found" 1283 msgstr "" 1284 1285 #: includes/class-kind-post-widget.php:115 1311 #: includes/class-kind-post-widget.php:137 1286 1312 msgid "Number of Posts:" 1287 1313 msgstr "" 1288 1314 1289 #: includes/class-kind-taxonomy.php:305 1290 msgid "Post Kind" 1291 msgstr "" 1292 1293 #: includes/class-kind-taxonomy.php:526 1315 #: includes/class-kind-taxonomy.php:161 1316 msgid "Unknown post kind." 1317 msgstr "" 1318 1319 #: includes/class-kind-taxonomy.php:363 1320 msgid "The post kind, or false if the post has none." 1321 msgstr "" 1322 1323 #: includes/class-kind-taxonomy.php:609 1324 msgid "Post Kinds replaces the IndieWeb Taxonomy plugin. Deactivate IndieWeb Taxonomy, then activate Post Kinds." 1325 msgstr "" 1326 1327 #: includes/class-kind-taxonomy.php:697 1294 1328 msgctxt "taxonomy general name" 1295 1329 msgid "Kinds" 1296 1330 msgstr "" 1297 1331 1298 #: includes/class-kind-taxonomy.php: 5271332 #: includes/class-kind-taxonomy.php:698 1299 1333 msgctxt "taxonomy singular name" 1300 1334 msgid "Kind" 1301 1335 msgstr "" 1302 1336 1303 #: includes/class-kind-taxonomy.php: 5281337 #: includes/class-kind-taxonomy.php:699 1304 1338 msgctxt "search locations" 1305 1339 msgid "Search Kinds" 1306 1340 msgstr "" 1307 1341 1308 #: includes/class-kind-taxonomy.php: 5291342 #: includes/class-kind-taxonomy.php:700 1309 1343 msgctxt "popular kinds" 1310 1344 msgid "Popular Kinds" 1311 1345 msgstr "" 1312 1346 1313 #: includes/class-kind-taxonomy.php: 5301347 #: includes/class-kind-taxonomy.php:701 1314 1348 msgctxt "all taxonomy items" 1315 1349 msgid "All Kinds" 1316 1350 msgstr "" 1317 1351 1318 #: includes/class-kind-taxonomy.php: 5311352 #: includes/class-kind-taxonomy.php:702 1319 1353 msgctxt "taxonomy parent item" 1320 1354 msgid "Parent Kind" 1321 1355 msgstr "" 1322 1356 1323 #: includes/class-kind-taxonomy.php: 5321357 #: includes/class-kind-taxonomy.php:703 1324 1358 msgctxt "taxonomy parent item with colon" 1325 1359 msgid "Parent Kind:" 1326 1360 msgstr "" 1327 1361 1328 #: includes/class-kind-taxonomy.php: 5331362 #: includes/class-kind-taxonomy.php:704 1329 1363 msgid "This will appear on archives as the description if your theme supports" 1330 1364 msgstr "" 1331 1365 1332 #: includes/class-kind-taxonomy.php: 5341366 #: includes/class-kind-taxonomy.php:705 1333 1367 msgctxt "edit taxonomy item" 1334 1368 msgid "Edit Kind" 1335 1369 msgstr "" 1336 1370 1337 #: includes/class-kind-taxonomy.php: 5351371 #: includes/class-kind-taxonomy.php:706 1338 1372 msgctxt "view taxonomy item" 1339 1373 msgid "View Kind" 1340 1374 msgstr "" 1341 1375 1342 #: includes/class-kind-taxonomy.php: 5361376 #: includes/class-kind-taxonomy.php:707 1343 1377 msgctxt "update taxonomy item" 1344 1378 msgid "Update Kind" 1345 1379 msgstr "" 1346 1380 1347 #: includes/class-kind-taxonomy.php: 5371381 #: includes/class-kind-taxonomy.php:708 1348 1382 msgctxt "add taxonomy item" 1349 1383 msgid "Add New Kind" 1350 1384 msgstr "" 1351 1385 1352 #: includes/class-kind-taxonomy.php: 5381386 #: includes/class-kind-taxonomy.php:709 1353 1387 msgctxt "new taxonomy item" 1354 1388 msgid "New Kind" 1355 1389 msgstr "" 1356 1390 1357 #: includes/class-kind-taxonomy.php: 5391391 #: includes/class-kind-taxonomy.php:710 1358 1392 msgctxt "separate kinds with commas" 1359 1393 msgid "Separate kinds with commas" 1360 1394 msgstr "" 1361 1395 1362 #: includes/class-kind-taxonomy.php: 5401396 #: includes/class-kind-taxonomy.php:711 1363 1397 msgctxt "add or remove items" 1364 1398 msgid "Add or remove kinds" 1365 1399 msgstr "" 1366 1400 1367 #: includes/class-kind-taxonomy.php: 5411401 #: includes/class-kind-taxonomy.php:712 1368 1402 msgctxt "choose most used" 1369 1403 msgid "Choose from the most used kinds" 1370 1404 msgstr "" 1371 1405 1372 #: includes/class-kind-taxonomy.php: 5421406 #: includes/class-kind-taxonomy.php:713 1373 1407 msgctxt "no kinds found" 1374 1408 msgid "No kinds found" 1375 1409 msgstr "" 1376 1410 1377 #: includes/class-kind-taxonomy.php: 5431411 #: includes/class-kind-taxonomy.php:714 1378 1412 msgctxt "no kinds" 1379 1413 msgid "No kinds" 1380 1414 msgstr "" 1381 1415 1382 #: includes/class-kind-taxonomy.php: 5441416 #: includes/class-kind-taxonomy.php:715 1383 1417 msgid "Back to Kinds" 1384 1418 msgstr "" 1385 1419 1386 #: includes/class-kind-taxonomy.php: 5451420 #: includes/class-kind-taxonomy.php:716 1387 1421 msgid "Kind Link" 1388 1422 msgstr "" 1389 1423 1390 #: includes/class-kind-taxonomy.php: 5461424 #: includes/class-kind-taxonomy.php:717 1391 1425 msgid "A link to a kind" 1392 msgstr ""1393 1394 #: includes/class-kind-taxonomy.php:5651395 #: includes/register-kinds.php:81396 msgid "Article"1397 1426 msgstr "" 1398 1427 … … 1400 1429 #. translators: 1: Taxonomy. 2: Month name and year 1401 1430 #. translators: 1. Taxonomy . 1: Date 1402 #: includes/class-kind-taxonomy.php:860 1403 #: includes/class-kind-taxonomy.php:863 1404 #: includes/class-kind-taxonomy.php:866 1431 #: includes/class-kind-taxonomy.php:1114 1432 #: includes/class-kind-taxonomy.php:1117 1433 #: includes/class-kind-taxonomy.php:1120 1434 #, php-format 1405 1435 msgid "%1$1s - %2$2s" 1406 1436 msgstr "" 1407 1437 1408 1438 #. translators: 1: Taxonomy. Yearly archive title. 2: Year 1409 #: includes/class-kind-taxonomy.php: 8601439 #: includes/class-kind-taxonomy.php:1114 1410 1440 msgctxt "yearly archives date format" 1411 1441 msgid "Y" … … 1413 1443 1414 1444 #. translators: 1: Taxonomy. 2: Month name and year 1415 #: includes/class-kind-taxonomy.php: 8631445 #: includes/class-kind-taxonomy.php:1117 1416 1446 msgctxt "monthly archives date format" 1417 1447 msgid "F Y" … … 1419 1449 1420 1450 #. translators: 1. Taxonomy . 1: Date 1421 #: includes/class-kind-taxonomy.php: 8661451 #: includes/class-kind-taxonomy.php:1120 1422 1452 msgctxt "daily archives date format" 1423 1453 msgid "F j, Y" … … 1425 1455 1426 1456 #. translators: 1. Title 2. Translated term for Firehose Feed 1427 #: includes/class-kind-taxonomy.php:870 1457 #: includes/class-kind-taxonomy.php:1124 1458 #, php-format 1428 1459 msgid "%1$s - %2$2s" 1429 1460 msgstr "" 1430 1461 1431 1462 #. translators: 1. Title 2. Translated term for Firehose Feed 1432 #: includes/class-kind-taxonomy.php: 8701463 #: includes/class-kind-taxonomy.php:1124 1433 1464 msgid "Firehose" 1434 1465 msgstr "" 1435 1466 1436 1467 #. translators: 1: Title prefix. 2: Title. 1437 #: includes/class-kind-taxonomy.php:876 1468 #: includes/class-kind-taxonomy.php:1130 1469 #, php-format 1438 1470 msgctxt "archive title" 1439 1471 msgid "%1$s %2$s" … … 1441 1473 1442 1474 #. translators: All 1443 #: includes/class-kind-taxonomy.php:1164 1475 #: includes/class-kind-taxonomy.php:1455 1476 #, php-format 1444 1477 msgid "All %1s" 1445 1478 msgstr "" 1446 1479 1447 #: includes/class-kind-taxonomy.php:1 3341480 #: includes/class-kind-taxonomy.php:1647 1448 1481 msgid "Invalid post" 1449 1482 msgstr "" 1450 1483 1451 #: includes/class-kind-taxonomy.php:1 3371484 #: includes/class-kind-taxonomy.php:1650 1452 1485 msgid "Invalid Kind" 1453 1486 msgstr "" 1454 1487 1455 #: includes/class-kind-view.php:418 1488 #: includes/class-kind-taxonomy.php:1670 1489 msgid "Invalid post." 1490 msgstr "" 1491 1492 #: includes/class-kind-taxonomy.php:1673 1493 msgid "Sorry, you are not allowed to set the kind of this post." 1494 msgstr "" 1495 1496 #: includes/class-kind-view.php:495 1456 1497 msgctxt "singular Twitter" 1457 1498 msgid "a tweet" 1458 1499 msgstr "" 1459 1500 1460 #: includes/class-kind-view.php:4 191501 #: includes/class-kind-view.php:496 1461 1502 msgctxt "singular Vimeo" 1462 1503 msgid "a video" 1463 1504 msgstr "" 1464 1505 1465 #: includes/class-kind-view.php:4 201506 #: includes/class-kind-view.php:497 1466 1507 msgctxt "singular Youtube" 1467 1508 msgid "a video" 1468 1509 msgstr "" 1469 1510 1470 #: includes/class-kind-view.php:4 211511 #: includes/class-kind-view.php:498 1471 1512 msgctxt "singular Intagram" 1472 1513 msgid "an image" 1473 1514 msgstr "" 1474 1515 1475 #: includes/class-kind-view.php: 4271516 #: includes/class-kind-view.php:513 1476 1517 msgctxt "singular post" 1477 1518 msgid "a post" 1478 1519 msgstr "" 1479 1520 1480 #: includes/class-kind-view.php: 4941521 #: includes/class-kind-view.php:589 1481 1522 msgid "an author" 1482 1523 msgstr "" 1483 1524 1484 #. translators: URL for link to event and name of event 1485 #: includes/class-kind-view.php:614 1486 msgid "Attending <a href=\"%1$s\" class=\"u-in-reply-to\">%2$s</a>" 1487 msgstr "" 1488 1489 #. translators: URL for link to event and name of event 1490 #: includes/class-kind-view.php:616 1491 msgid "Might be attending <a href=\"%1$s\" class=\"u-in-reply-to\">%2$s</a>" 1492 msgstr "" 1493 1494 #. translators: URL for link to event and name of event 1495 #: includes/class-kind-view.php:618 1496 msgid "Unable to Attend <a href=\"%1$s\" class=\"u-in-reply-to\">%2$s</a>" 1497 msgstr "" 1498 1499 #. translators: URL for link to event and name of event 1500 #: includes/class-kind-view.php:620 1501 msgid "Interested in Attending <a href=\"%1$s\" class=u-in-reply-to\">%2$s</a>" 1502 msgstr "" 1503 1504 #. translators: URL for link to event and name of event 1505 #: includes/class-kind-view.php:622 1506 msgid "Attending <a href=\"%1$s\" class=\"u-in-reply-to\">%2$s</a> remotely" 1507 msgstr "" 1508 1509 #: includes/class-kind-view.php:640 1525 #. translators: %s: Link to the event 1526 #: includes/class-kind-view.php:752 1527 #, php-format 1528 msgid "Attending %s" 1529 msgstr "" 1530 1531 #. translators: %s: Link to the event 1532 #: includes/class-kind-view.php:754 1533 #, php-format 1534 msgid "Might be attending %s" 1535 msgstr "" 1536 1537 #. translators: %s: Link to the event 1538 #: includes/class-kind-view.php:756 1539 #, php-format 1540 msgid "Unable to Attend %s" 1541 msgstr "" 1542 1543 #. translators: %s: Link to the event 1544 #: includes/class-kind-view.php:758 1545 #, php-format 1546 msgid "Interested in Attending %s" 1547 msgstr "" 1548 1549 #. translators: %s: Link to the event 1550 #: includes/class-kind-view.php:760 1551 #, php-format 1552 msgid "Attending %s remotely" 1553 msgstr "" 1554 1555 #: includes/class-kind-view.php:794 1510 1556 msgid "Want to Read: " 1511 1557 msgstr "" 1512 1558 1513 #: includes/class-kind-view.php: 6411559 #: includes/class-kind-view.php:795 1514 1560 msgid "Reading: " 1515 1561 msgstr "" 1516 1562 1517 #: includes/class-kind-view.php: 6421563 #: includes/class-kind-view.php:796 1518 1564 msgid "Finished Reading: " 1519 1565 msgstr "" 1520 1566 1521 1567 #. translators: singular and plural 1522 #: includes/class-kind-view.php:674 1568 #: includes/class-kind-view.php:828 1569 #, php-format 1523 1570 msgid "%d year" 1524 1571 msgid_plural "%d years" … … 1527 1574 1528 1575 #. translators: singular and plural 1529 #: includes/class-kind-view.php:678 1576 #: includes/class-kind-view.php:832 1577 #, php-format 1530 1578 msgid "%d month" 1531 1579 msgid_plural "%d months" … … 1534 1582 1535 1583 #. translators: singular and plural 1536 #: includes/class-kind-view.php:682 1584 #: includes/class-kind-view.php:836 1585 #, php-format 1537 1586 msgid "%d day" 1538 1587 msgid_plural "%d days" … … 1541 1590 1542 1591 #. translators: singular and plural 1543 #: includes/class-kind-view.php:686 1592 #: includes/class-kind-view.php:840 1593 #, php-format 1544 1594 msgid "%d hour" 1545 1595 msgid_plural "%d hours" … … 1548 1598 1549 1599 #. translators: singular and plural 1550 #: includes/class-kind-view.php:690 1600 #: includes/class-kind-view.php:844 1601 #, php-format 1551 1602 msgid "%d minute" 1552 1603 msgid_plural "%d minutes" … … 1555 1606 1556 1607 #. translators: singular and plural 1557 #: includes/class-kind-view.php:694 1608 #: includes/class-kind-view.php:848 1609 #, php-format 1558 1610 msgid "%d second" 1559 1611 msgid_plural "%d seconds" … … 1561 1613 msgstr[1] "" 1562 1614 1563 #: includes/register-kinds.php:9 1615 #: includes/register-kinds.php:17 1616 msgid "Article" 1617 msgstr "" 1618 1619 #: includes/register-kinds.php:18 1564 1620 msgid "Articles" 1565 1621 msgstr "" 1566 1622 1567 #: includes/register-kinds.php: 141623 #: includes/register-kinds.php:23 1568 1624 msgid "traditional long form content: a post with an explicit title and body" 1569 1625 msgstr "" 1570 1626 1571 #: includes/register-kinds.php: 251627 #: includes/register-kinds.php:34 1572 1628 msgid "Note" 1573 1629 msgstr "" 1574 1630 1575 #: includes/register-kinds.php: 261631 #: includes/register-kinds.php:35 1576 1632 msgid "Notes" 1577 1633 msgstr "" 1578 1634 1579 #: includes/register-kinds.php: 311635 #: includes/register-kinds.php:40 1580 1636 msgid "short content: a post or status update with just plain content and typically without a title" 1581 1637 msgstr "" 1582 1638 1583 #: includes/register-kinds.php: 421639 #: includes/register-kinds.php:51 1584 1640 msgid "Reply" 1585 1641 msgstr "" 1586 1642 1587 #: includes/register-kinds.php: 431643 #: includes/register-kinds.php:52 1588 1644 msgid "Replies" 1589 1645 msgstr "" 1590 1646 1591 #: includes/register-kinds.php: 441647 #: includes/register-kinds.php:53 1592 1648 msgid "Replied to" 1593 1649 msgstr "" 1594 1650 1595 #: includes/register-kinds.php: 491651 #: includes/register-kinds.php:58 1596 1652 msgid "In Reply To" 1597 1653 msgstr "" 1598 1654 1599 #: includes/register-kinds.php:53 1655 #: includes/register-kinds.php:62 1656 #: includes/register-kinds.php:131 1657 #: includes/register-kinds.php:200 1658 #: includes/register-kinds.php:269 1659 #: includes/register-kinds.php:338 1660 #: includes/register-kinds.php:481 1661 #: includes/register-kinds.php:543 1662 #: includes/register-kinds.php:608 1663 #: includes/register-kinds.php:673 1664 #: templates/reply-metabox.php:56 1665 msgid "Name" 1666 msgstr "" 1667 1668 #: includes/register-kinds.php:66 1669 #: includes/register-kinds.php:135 1670 #: includes/register-kinds.php:204 1671 #: includes/register-kinds.php:273 1672 #: includes/register-kinds.php:342 1673 #: includes/register-kinds.php:485 1674 #: includes/register-kinds.php:547 1675 #: includes/register-kinds.php:612 1676 #: includes/register-kinds.php:677 1677 #: templates/reply-metabox.php:50 1678 msgid "URL" 1679 msgstr "" 1680 1681 #: includes/register-kinds.php:70 1682 #: includes/register-kinds.php:139 1683 #: includes/register-kinds.php:208 1684 #: includes/register-kinds.php:277 1685 #: includes/register-kinds.php:346 1686 #: templates/reply-author.php:20 1687 #: templates/reply-metabox.php:45 1688 msgid "Author" 1689 msgstr "" 1690 1691 #: includes/register-kinds.php:77 1692 #: includes/register-kinds.php:146 1693 #: includes/register-kinds.php:215 1694 #: includes/register-kinds.php:284 1695 #: includes/register-kinds.php:353 1696 #: includes/register-kinds.php:489 1697 #: includes/register-kinds.php:558 1698 #: includes/register-kinds.php:623 1699 msgid "Summary" 1700 msgstr "" 1701 1702 #: includes/register-kinds.php:81 1703 #: includes/register-kinds.php:150 1704 #: includes/register-kinds.php:219 1705 #: includes/register-kinds.php:288 1706 #: includes/register-kinds.php:357 1707 #: includes/register-kinds.php:493 1708 msgid "Publication/Website" 1709 msgstr "" 1710 1711 #: includes/register-kinds.php:85 1712 #: includes/register-kinds.php:154 1713 #: includes/register-kinds.php:223 1714 #: includes/register-kinds.php:292 1715 #: includes/register-kinds.php:361 1716 msgid "Publish Date" 1717 msgstr "" 1718 1719 #: includes/register-kinds.php:89 1720 #: includes/register-kinds.php:158 1721 #: includes/register-kinds.php:227 1722 #: includes/register-kinds.php:296 1723 #: includes/register-kinds.php:365 1724 msgid "Updated Time" 1725 msgstr "" 1726 1727 #: includes/register-kinds.php:93 1728 #: includes/register-kinds.php:162 1729 #: includes/register-kinds.php:231 1730 #: includes/register-kinds.php:300 1731 #: includes/register-kinds.php:369 1732 #: includes/register-kinds.php:445 1733 #: includes/register-kinds.php:505 1734 #: includes/register-kinds.php:570 1735 #: includes/register-kinds.php:635 1736 msgid "Tags" 1737 msgstr "" 1738 1739 #: includes/register-kinds.php:100 1740 #: includes/register-kinds.php:169 1741 #: includes/register-kinds.php:238 1742 #: includes/register-kinds.php:307 1743 #: includes/register-kinds.php:376 1744 #: includes/register-kinds.php:512 1745 msgid "Featured Media" 1746 msgstr "" 1747 1748 #: includes/register-kinds.php:109 1749 msgid "a reply to content typically on another site" 1750 msgstr "" 1751 1752 #: includes/register-kinds.php:120 1753 #: includes/register-kinds.php:127 1754 msgid "Repost" 1755 msgstr "" 1756 1757 #: includes/register-kinds.php:121 1758 msgid "Reposts" 1759 msgstr "" 1760 1600 1761 #: includes/register-kinds.php:122 1762 msgid "Reposted" 1763 msgstr "" 1764 1765 #: includes/register-kinds.php:178 1766 msgid "a complete reposting of content from another site" 1767 msgstr "" 1768 1769 #: includes/register-kinds.php:189 1770 #: includes/register-kinds.php:196 1771 msgid "Like" 1772 msgstr "" 1773 1774 #: includes/register-kinds.php:190 1775 msgid "Likes" 1776 msgstr "" 1777 1601 1778 #: includes/register-kinds.php:191 1779 msgid "Liked" 1780 msgstr "" 1781 1782 #: includes/register-kinds.php:247 1783 msgid "a way to pay compliments to the original post/poster of external content" 1784 msgstr "" 1785 1786 #: includes/register-kinds.php:258 1787 msgid "Favorite" 1788 msgstr "" 1789 1790 #: includes/register-kinds.php:259 1791 msgid "Favorites" 1792 msgstr "" 1793 1602 1794 #: includes/register-kinds.php:260 1795 #: includes/register-kinds.php:265 1796 msgid "Favorited" 1797 msgstr "" 1798 1799 #: includes/register-kinds.php:316 1800 msgid "special to the author" 1801 msgstr "" 1802 1803 #: includes/register-kinds.php:327 1804 #: includes/register-kinds.php:334 1805 msgid "Bookmark" 1806 msgstr "" 1807 1808 #: includes/register-kinds.php:328 1809 msgid "Bookmarks" 1810 msgstr "" 1811 1603 1812 #: includes/register-kinds.php:329 1604 #: includes/register-kinds.php:472 1813 msgid "Bookmarked" 1814 msgstr "" 1815 1816 #: includes/register-kinds.php:385 1817 msgid "storing a link/bookmark for personal use or sharing with others" 1818 msgstr "" 1819 1820 #: includes/register-kinds.php:396 1821 msgid "Photo" 1822 msgstr "" 1823 1824 #: includes/register-kinds.php:397 1825 msgid "Photos" 1826 msgstr "" 1827 1828 #: includes/register-kinds.php:401 1829 msgid "a post with an embedded image/photo as its primary focus" 1830 msgstr "" 1831 1832 #: includes/register-kinds.php:412 1833 msgid "Video" 1834 msgstr "" 1835 1836 #: includes/register-kinds.php:413 1837 msgid "Videos" 1838 msgstr "" 1839 1840 #: includes/register-kinds.php:417 1841 msgid "a post with an embedded video as its primary focus" 1842 msgstr "" 1843 1844 #: includes/register-kinds.php:428 1845 msgid "Audio" 1846 msgstr "" 1847 1848 #: includes/register-kinds.php:429 1849 msgid "Audios" 1850 msgstr "" 1851 1852 #: includes/register-kinds.php:433 1853 msgid "a post with an embedded audio file as its primary focus" 1854 msgstr "" 1855 1856 #: includes/register-kinds.php:444 1857 msgid "Tag" 1858 msgstr "" 1859 1860 #: includes/register-kinds.php:446 1861 msgid "Tagged" 1862 msgstr "" 1863 1864 #: includes/register-kinds.php:449 1865 msgid "allows you to tag a post as being of a specific category or tag, or for person tagging" 1866 msgstr "" 1867 1868 #: includes/register-kinds.php:461 1869 msgid "RSVPs" 1870 msgstr "" 1871 1872 #: includes/register-kinds.php:462 1873 msgid "RSVPed" 1874 msgstr "" 1875 1876 #: includes/register-kinds.php:477 1877 msgid "Event Details" 1878 msgstr "" 1879 1880 #: includes/register-kinds.php:497 1881 msgid "Start Date" 1882 msgstr "" 1883 1884 #: includes/register-kinds.php:501 1885 msgid "End Date" 1886 msgstr "" 1887 1888 #: includes/register-kinds.php:521 1889 msgid "a specific type of reply regarding attendance of an event" 1890 msgstr "" 1891 1892 #: includes/register-kinds.php:532 1893 msgid "Listen" 1894 msgstr "" 1895 1896 #: includes/register-kinds.php:533 1897 msgid "Listens" 1898 msgstr "" 1899 1605 1900 #: includes/register-kinds.php:534 1901 msgid "Listened" 1902 msgstr "" 1903 1904 #: includes/register-kinds.php:539 1905 msgid "Listen To" 1906 msgstr "" 1907 1908 #: includes/register-kinds.php:562 1909 msgid "Album" 1910 msgstr "" 1911 1912 #: includes/register-kinds.php:566 1913 #: includes/register-kinds.php:631 1914 msgid "Release Date" 1915 msgstr "" 1916 1917 #: includes/register-kinds.php:577 1918 msgid "Album Art" 1919 msgstr "" 1920 1921 #: includes/register-kinds.php:586 1922 msgid "listening to audio; sometimes called a scrobble" 1923 msgstr "" 1924 1925 #: includes/register-kinds.php:597 1926 msgid "Watch" 1927 msgstr "" 1928 1929 #: includes/register-kinds.php:598 1930 msgid "Watches" 1931 msgstr "" 1932 1606 1933 #: includes/register-kinds.php:599 1934 msgid "Watched" 1935 msgstr "" 1936 1937 #: includes/register-kinds.php:604 1938 msgid "Watching To" 1939 msgstr "" 1940 1941 #: includes/register-kinds.php:627 1942 msgid "Series" 1943 msgstr "" 1944 1945 #: includes/register-kinds.php:642 1946 msgid "Poster" 1947 msgstr "" 1948 1949 #: includes/register-kinds.php:651 1950 msgid "watching a movie, television show, online video, play or other visual-based event" 1951 msgstr "" 1952 1953 #: includes/register-kinds.php:662 1954 msgid "Checkin" 1955 msgstr "" 1956 1957 #: includes/register-kinds.php:663 1958 msgid "Checkins" 1959 msgstr "" 1960 1607 1961 #: includes/register-kinds.php:664 1608 #: templates/reply-metabox.php:421609 msgid "Name"1610 msgstr ""1611 1612 #: includes/register-kinds.php:571613 #: includes/register-kinds.php:1261614 #: includes/register-kinds.php:1951615 #: includes/register-kinds.php:2641616 #: includes/register-kinds.php:3331617 #: includes/register-kinds.php:4761618 #: includes/register-kinds.php:5381619 #: includes/register-kinds.php:6031620 #: includes/register-kinds.php:6681621 #: lib/parse-this/includes/class-rest-parse-this.php:491622 #: templates/reply-metabox.php:361623 msgid "URL"1624 msgstr ""1625 1626 #: includes/register-kinds.php:611627 #: includes/register-kinds.php:1301628 #: includes/register-kinds.php:1991629 #: includes/register-kinds.php:2681630 #: includes/register-kinds.php:3371631 #: templates/reply-author.php:61632 #: templates/reply-metabox.php:311633 msgid "Author"1634 msgstr ""1635 1636 #: includes/register-kinds.php:681637 #: includes/register-kinds.php:1371638 #: includes/register-kinds.php:2061639 #: includes/register-kinds.php:2751640 #: includes/register-kinds.php:3441641 #: includes/register-kinds.php:4801642 #: includes/register-kinds.php:5491643 #: includes/register-kinds.php:6141644 msgid "Summary"1645 msgstr ""1646 1647 #: includes/register-kinds.php:721648 #: includes/register-kinds.php:1411649 #: includes/register-kinds.php:2101650 #: includes/register-kinds.php:2791651 #: includes/register-kinds.php:3481652 #: includes/register-kinds.php:4841653 msgid "Publication/Website"1654 msgstr ""1655 1656 #: includes/register-kinds.php:761657 #: includes/register-kinds.php:1451658 #: includes/register-kinds.php:2141659 #: includes/register-kinds.php:2831660 #: includes/register-kinds.php:3521661 msgid "Publish Date"1662 msgstr ""1663 1664 #: includes/register-kinds.php:801665 #: includes/register-kinds.php:1491666 #: includes/register-kinds.php:2181667 #: includes/register-kinds.php:2871668 #: includes/register-kinds.php:3561669 msgid "Updated Time"1670 msgstr ""1671 1672 #: includes/register-kinds.php:841673 #: includes/register-kinds.php:1531674 #: includes/register-kinds.php:2221675 #: includes/register-kinds.php:2911676 #: includes/register-kinds.php:3601677 #: includes/register-kinds.php:4361678 #: includes/register-kinds.php:4961679 #: includes/register-kinds.php:5611680 #: includes/register-kinds.php:6261681 msgid "Tags"1682 msgstr ""1683 1684 #: includes/register-kinds.php:911685 #: includes/register-kinds.php:1601686 #: includes/register-kinds.php:2291687 #: includes/register-kinds.php:2981688 #: includes/register-kinds.php:3671689 #: includes/register-kinds.php:5031690 msgid "Featured Media"1691 msgstr ""1692 1693 #: includes/register-kinds.php:1001694 msgid "a reply to content typically on another site"1695 msgstr ""1696 1697 #: includes/register-kinds.php:1111698 #: includes/register-kinds.php:1181699 msgid "Repost"1700 msgstr ""1701 1702 #: includes/register-kinds.php:1121703 msgid "Reposts"1704 msgstr ""1705 1706 #: includes/register-kinds.php:1131707 msgid "Reposted"1708 msgstr ""1709 1710 #: includes/register-kinds.php:1691711 msgid "a complete reposting of content from another site"1712 msgstr ""1713 1714 #: includes/register-kinds.php:1801715 #: includes/register-kinds.php:1871716 msgid "Like"1717 msgstr ""1718 1719 #: includes/register-kinds.php:1811720 msgid "Likes"1721 msgstr ""1722 1723 #: includes/register-kinds.php:1821724 msgid "Liked"1725 msgstr ""1726 1727 #: includes/register-kinds.php:2381728 msgid "a way to pay compliments to the original post/poster of external content"1729 msgstr ""1730 1731 #: includes/register-kinds.php:2491732 msgid "Favorite"1733 msgstr ""1734 1735 #: includes/register-kinds.php:2501736 msgid "Favorites"1737 msgstr ""1738 1739 #: includes/register-kinds.php:2511740 #: includes/register-kinds.php:2561741 msgid "Favorited"1742 msgstr ""1743 1744 #: includes/register-kinds.php:3071745 msgid "special to the author"1746 msgstr ""1747 1748 #: includes/register-kinds.php:3181749 #: includes/register-kinds.php:3251750 msgid "Bookmark"1751 msgstr ""1752 1753 #: includes/register-kinds.php:3191754 msgid "Bookmarks"1755 msgstr ""1756 1757 #: includes/register-kinds.php:3201758 msgid "Bookmarked"1759 msgstr ""1760 1761 #: includes/register-kinds.php:3761762 msgid "storing a link/bookmark for personal use or sharing with others"1763 msgstr ""1764 1765 #: includes/register-kinds.php:3871766 msgid "Photo"1767 msgstr ""1768 1769 #: includes/register-kinds.php:3881770 msgid "Photos"1771 msgstr ""1772 1773 #: includes/register-kinds.php:3921774 msgid "a post with an embedded image/photo as its primary focus"1775 msgstr ""1776 1777 #: includes/register-kinds.php:4031778 msgid "Video"1779 msgstr ""1780 1781 #: includes/register-kinds.php:4041782 msgid "Videos"1783 msgstr ""1784 1785 #: includes/register-kinds.php:4081786 msgid "a post with an embedded video as its primary focus"1787 msgstr ""1788 1789 #: includes/register-kinds.php:4191790 msgid "Audio"1791 msgstr ""1792 1793 #: includes/register-kinds.php:4201794 msgid "Audios"1795 msgstr ""1796 1797 #: includes/register-kinds.php:4241798 msgid "a post with an embedded audio file as its primary focus"1799 msgstr ""1800 1801 #: includes/register-kinds.php:4351802 msgid "Tag"1803 msgstr ""1804 1805 #: includes/register-kinds.php:4371806 msgid "Tagged"1807 msgstr ""1808 1809 #: includes/register-kinds.php:4401810 msgid "allows you to tag a post as being of a specific category or tag, or for person tagging"1811 msgstr ""1812 1813 #: includes/register-kinds.php:4521814 msgid "RSVPs"1815 msgstr ""1816 1817 #: includes/register-kinds.php:4531818 msgid "RSVPed"1819 msgstr ""1820 1821 #: includes/register-kinds.php:4681822 msgid "Event Details"1823 msgstr ""1824 1825 #: includes/register-kinds.php:4881826 msgid "Start Date"1827 msgstr ""1828 1829 #: includes/register-kinds.php:4921830 msgid "End Date"1831 msgstr ""1832 1833 #: includes/register-kinds.php:5121834 msgid "a specific type of reply regarding attendance of an event"1835 msgstr ""1836 1837 #: includes/register-kinds.php:5231838 msgid "Listen"1839 msgstr ""1840 1841 #: includes/register-kinds.php:5241842 msgid "Listens"1843 msgstr ""1844 1845 #: includes/register-kinds.php:5251846 msgid "Listened"1847 msgstr ""1848 1849 #: includes/register-kinds.php:5301850 msgid "Listen To"1851 msgstr ""1852 1853 #: includes/register-kinds.php:5531854 msgid "Album"1855 msgstr ""1856 1857 #: includes/register-kinds.php:5571858 #: includes/register-kinds.php:6221859 msgid "Release Date"1860 msgstr ""1861 1862 #: includes/register-kinds.php:5681863 msgid "Album Art"1864 msgstr ""1865 1866 #: includes/register-kinds.php:5771867 msgid "listening to audio; sometimes called a scrobble"1868 msgstr ""1869 1870 #: includes/register-kinds.php:5881871 msgid "Watch"1872 msgstr ""1873 1874 #: includes/register-kinds.php:5891875 msgid "Watches"1876 msgstr ""1877 1878 #: includes/register-kinds.php:5901879 msgid "Watched"1880 msgstr ""1881 1882 #: includes/register-kinds.php:5951883 msgid "Watching To"1884 msgstr ""1885 1886 #: includes/register-kinds.php:6181887 msgid "Series"1888 msgstr ""1889 1890 #: includes/register-kinds.php:6331891 msgid "Poster"1892 msgstr ""1893 1894 #: includes/register-kinds.php:6421895 msgid "watching a movie, television show, online video, play or other visual-based event"1896 msgstr ""1897 1898 #: includes/register-kinds.php:6531899 msgid "Checkin"1900 msgstr ""1901 1902 #: includes/register-kinds.php:6541903 msgid "Checkins"1904 msgstr ""1905 1906 #: includes/register-kinds.php:6551907 1962 msgid "Checked into" 1908 1963 msgstr "" 1909 1964 1910 #: includes/register-kinds.php:66 01965 #: includes/register-kinds.php:669 1911 1966 msgid "Checked In" 1912 1967 msgstr "" 1913 1968 1914 #: includes/register-kinds.php:6 721969 #: includes/register-kinds.php:681 1915 1970 msgid "Latitude" 1916 1971 msgstr "" 1917 1972 1918 #: includes/register-kinds.php:68 01973 #: includes/register-kinds.php:689 1919 1974 msgid "Longitude" 1920 1975 msgstr "" 1921 1976 1922 #: includes/register-kinds.php:6 881977 #: includes/register-kinds.php:697 1923 1978 msgid "Street Address" 1924 1979 msgstr "" 1925 1980 1926 #: includes/register-kinds.php: 6921981 #: includes/register-kinds.php:701 1927 1982 msgid "Locality" 1928 1983 msgstr "" 1929 1984 1930 #: includes/register-kinds.php: 6961985 #: includes/register-kinds.php:705 1931 1986 msgid "Region" 1932 1987 msgstr "" 1933 1988 1934 #: includes/register-kinds.php:70 01989 #: includes/register-kinds.php:709 1935 1990 msgid "Country Name" 1936 1991 msgstr "" 1937 1992 1938 #: includes/register-kinds.php:7 041993 #: includes/register-kinds.php:713 1939 1994 msgid "Postal Code" 1940 1995 msgstr "" 1941 1996 1942 #: includes/register-kinds.php:71 01997 #: includes/register-kinds.php:719 1943 1998 msgid "identifying you are at a particular geographic location" 1944 1999 msgstr "" 1945 2000 1946 #: includes/register-kinds.php:7 212001 #: includes/register-kinds.php:730 1947 2002 msgid "Wish" 1948 2003 msgstr "" 1949 2004 1950 #: includes/register-kinds.php:7 222005 #: includes/register-kinds.php:731 1951 2006 msgid "Wishes" 1952 2007 msgstr "" 1953 2008 1954 #: includes/register-kinds.php:7 232009 #: includes/register-kinds.php:732 1955 2010 msgid "Wished" 1956 2011 msgstr "" 1957 2012 1958 #: includes/register-kinds.php:7 262013 #: includes/register-kinds.php:735 1959 2014 msgid "a post indicating a desire/wish. The archive of which would be a wishlist, such as a gift registry or similar" 1960 2015 msgstr "" 1961 2016 1962 #: includes/register-kinds.php:7 372017 #: includes/register-kinds.php:746 1963 2018 msgid "Play" 1964 2019 msgstr "" 1965 2020 1966 #: includes/register-kinds.php:7 382021 #: includes/register-kinds.php:747 1967 2022 msgid "Playing" 1968 2023 msgstr "" 1969 2024 1970 #: includes/register-kinds.php:7 392025 #: includes/register-kinds.php:748 1971 2026 msgid "Played" 1972 2027 msgstr "" 1973 2028 1974 #: includes/register-kinds.php:7 422029 #: includes/register-kinds.php:751 1975 2030 msgid "playing a game" 1976 2031 msgstr "" 1977 2032 1978 #: includes/register-kinds.php:7 531979 #: includes/register-kinds.php:7 542033 #: includes/register-kinds.php:762 2034 #: includes/register-kinds.php:763 1980 2035 msgid "Weather" 1981 2036 msgstr "" 1982 2037 1983 #: includes/register-kinds.php:7 582038 #: includes/register-kinds.php:767 1984 2039 msgid "current weather conditions" 1985 2040 msgstr "" 1986 2041 1987 #: includes/register-kinds.php:7 691988 #: includes/register-kinds.php:77 02042 #: includes/register-kinds.php:778 2043 #: includes/register-kinds.php:779 1989 2044 msgid "Exercise" 1990 2045 msgstr "" 1991 2046 1992 #: includes/register-kinds.php:7 712047 #: includes/register-kinds.php:780 1993 2048 msgid "Exercised" 1994 2049 msgstr "" 1995 2050 1996 #: includes/register-kinds.php:7 742051 #: includes/register-kinds.php:783 1997 2052 msgid "some form of physical activity or workout (examples: walk, run, cycle, hike, yoga, etc.)" 1998 2053 msgstr "" 1999 2054 2000 #: includes/register-kinds.php:7 852055 #: includes/register-kinds.php:794 2001 2056 msgid "Trip" 2002 2057 msgstr "" 2003 2058 2004 #: includes/register-kinds.php:7 862059 #: includes/register-kinds.php:795 2005 2060 msgid "Trips" 2006 2061 msgstr "" 2007 2062 2008 #: includes/register-kinds.php:7 872009 #: includes/register-kinds.php:8 032063 #: includes/register-kinds.php:796 2064 #: includes/register-kinds.php:812 2010 2065 msgid "Travelled" 2011 2066 msgstr "" 2012 2067 2013 #: includes/register-kinds.php:79 02068 #: includes/register-kinds.php:799 2014 2069 msgid "represents a geographic journey" 2015 2070 msgstr "" 2016 2071 2017 #: includes/register-kinds.php:8 012072 #: includes/register-kinds.php:810 2018 2073 msgid "Itinerary" 2019 2074 msgstr "" 2020 2075 2021 #: includes/register-kinds.php:8 022076 #: includes/register-kinds.php:811 2022 2077 msgid "Itineraries" 2023 2078 msgstr "" 2024 2079 2025 #: includes/register-kinds.php:8 062080 #: includes/register-kinds.php:815 2026 2081 msgid "parts of a scheduled trip including transit by car, plane, train, etc." 2027 2082 msgstr "" 2028 2083 2029 #: includes/register-kinds.php:8 182030 #: includes/register-kinds.php:8 192084 #: includes/register-kinds.php:827 2085 #: includes/register-kinds.php:828 2031 2086 msgid "Eat" 2032 2087 msgstr "" 2033 2088 2034 #: includes/register-kinds.php:82 02089 #: includes/register-kinds.php:829 2035 2090 msgid "Ate" 2036 2091 msgstr "" 2037 2092 2038 #: includes/register-kinds.php:8 232093 #: includes/register-kinds.php:832 2039 2094 msgid "what you are eating, perhaps for a food diary" 2040 2095 msgstr "" 2041 2096 2042 #: includes/register-kinds.php:8 352097 #: includes/register-kinds.php:844 2043 2098 msgid "Drink" 2044 2099 msgstr "" 2045 2100 2046 #: includes/register-kinds.php:8 362101 #: includes/register-kinds.php:845 2047 2102 msgid "Drinks" 2048 2103 msgstr "" 2049 2104 2050 #: includes/register-kinds.php:8 372105 #: includes/register-kinds.php:846 2051 2106 msgid "Drank" 2052 2107 msgstr "" 2053 2108 2054 #: includes/register-kinds.php:84 02109 #: includes/register-kinds.php:849 2055 2110 msgid "what you are drinking, perhaps for a food dairy" 2056 2111 msgstr "" 2057 2112 2058 #: includes/register-kinds.php:8 522113 #: includes/register-kinds.php:861 2059 2114 msgid "Follow" 2060 2115 msgstr "" 2061 2116 2062 #: includes/register-kinds.php:8 532117 #: includes/register-kinds.php:862 2063 2118 msgid "Follows" 2064 2119 msgstr "" 2065 2120 2066 #: includes/register-kinds.php:8 542121 #: includes/register-kinds.php:863 2067 2122 msgid "Followed" 2068 2123 msgstr "" 2069 2124 2070 #: includes/register-kinds.php:8 572125 #: includes/register-kinds.php:866 2071 2126 msgid "indicating you are now following or subscribing to another person`s activities online" 2072 2127 msgstr "" 2073 2128 2074 #: includes/register-kinds.php:8 692129 #: includes/register-kinds.php:878 2075 2130 msgid "Jam" 2076 2131 msgstr "" 2077 2132 2078 #: includes/register-kinds.php:87 02133 #: includes/register-kinds.php:879 2079 2134 msgid "Jams" 2080 2135 msgstr "" 2081 2136 2082 #: includes/register-kinds.php:8 712137 #: includes/register-kinds.php:880 2083 2138 msgid "Listened to" 2084 2139 msgstr "" 2085 2140 2086 #: includes/register-kinds.php:8 742141 #: includes/register-kinds.php:883 2087 2142 msgid "a particularly personally meaningful song (a listen with added emphasis)" 2088 2143 msgstr "" 2089 2144 2090 #: includes/register-kinds.php:8 862091 #: includes/register-kinds.php:8 882145 #: includes/register-kinds.php:895 2146 #: includes/register-kinds.php:897 2092 2147 msgid "Read" 2093 2148 msgstr "" 2094 2149 2095 #: includes/register-kinds.php:8 872150 #: includes/register-kinds.php:896 2096 2151 msgid "Reads" 2097 2152 msgstr "" 2098 2153 2099 #: includes/register-kinds.php: 8912154 #: includes/register-kinds.php:900 2100 2155 msgid "reading a book, magazine, newspaper, other physical document, or online post" 2101 2156 msgstr "" 2102 2157 2103 #: includes/register-kinds.php:9 032158 #: includes/register-kinds.php:912 2104 2159 msgid "Quote" 2105 2160 msgstr "" 2106 2161 2107 #: includes/register-kinds.php:9 042162 #: includes/register-kinds.php:913 2108 2163 msgid "Quotes" 2109 2164 msgstr "" 2110 2165 2111 #: includes/register-kinds.php:9 052166 #: includes/register-kinds.php:914 2112 2167 msgid "Quoted" 2113 2168 msgstr "" 2114 2169 2115 #: includes/register-kinds.php:9 082170 #: includes/register-kinds.php:917 2116 2171 msgid "quoted content" 2117 2172 msgstr "" 2118 2173 2119 #: includes/register-kinds.php:92 02174 #: includes/register-kinds.php:929 2120 2175 msgid "Mood" 2121 2176 msgstr "" 2122 2177 2123 #: includes/register-kinds.php:9 212178 #: includes/register-kinds.php:930 2124 2179 msgid "Moods" 2125 2180 msgstr "" 2126 2181 2127 #: includes/register-kinds.php:9 222182 #: includes/register-kinds.php:931 2128 2183 msgid "Felt" 2129 2184 msgstr "" 2130 2185 2131 #: includes/register-kinds.php:9 252186 #: includes/register-kinds.php:934 2132 2187 msgid "how you are feeling (example: happy, sad, indifferent, etc.)" 2133 2188 msgstr "" 2134 2189 2135 #: includes/register-kinds.php:9 372190 #: includes/register-kinds.php:946 2136 2191 msgid "Recipe" 2137 2192 msgstr "" 2138 2193 2139 #: includes/register-kinds.php:9 382194 #: includes/register-kinds.php:947 2140 2195 msgid "Recipes" 2141 2196 msgstr "" 2142 2197 2143 #: includes/register-kinds.php:9 392198 #: includes/register-kinds.php:948 2144 2199 msgid "Cooked" 2145 2200 msgstr "" 2146 2201 2147 #: includes/register-kinds.php:9 422202 #: includes/register-kinds.php:951 2148 2203 msgid "list of ingredients and directions for making food or drink" 2149 2204 msgstr "" 2150 2205 2151 #: includes/register-kinds.php:9 542206 #: includes/register-kinds.php:963 2152 2207 msgid "Issue" 2153 2208 msgstr "" 2154 2209 2155 #: includes/register-kinds.php:9 552210 #: includes/register-kinds.php:964 2156 2211 msgid "Issues" 2157 2212 msgstr "" 2158 2213 2159 #: includes/register-kinds.php:9 562214 #: includes/register-kinds.php:965 2160 2215 msgid "Filed an Issue" 2161 2216 msgstr "" 2162 2217 2163 #: includes/register-kinds.php:9 592218 #: includes/register-kinds.php:968 2164 2219 msgid "Issue is a special kind of article post that is a reply to typically some source code, though potentially anything at a source control repository." 2165 2220 msgstr "" 2166 2221 2167 #: includes/register-kinds.php:9 712222 #: includes/register-kinds.php:980 2168 2223 msgid "Question" 2169 2224 msgstr "" 2170 2225 2171 #: includes/register-kinds.php:9 722226 #: includes/register-kinds.php:981 2172 2227 msgid "Questions" 2173 2228 msgstr "" 2174 2229 2175 #: includes/register-kinds.php:9 732230 #: includes/register-kinds.php:982 2176 2231 msgid "Asked a question" 2177 2232 msgstr "" 2178 2233 2179 #: includes/register-kinds.php:9 762234 #: includes/register-kinds.php:985 2180 2235 msgid "Question is a post type for soliciting answer replies, which are then typically up/down voted by others and then displayed underneath the question post ordered by highest positive vote count rather than time ordered." 2181 2236 msgstr "" 2182 2237 2183 #: includes/register-kinds.php:9 882238 #: includes/register-kinds.php:997 2184 2239 msgid "Sleep" 2185 2240 msgstr "" 2186 2241 2187 #: includes/register-kinds.php:9 892242 #: includes/register-kinds.php:998 2188 2243 msgid "Sleeps" 2189 2244 msgstr "" 2190 2245 2191 #: includes/register-kinds.php:99 02246 #: includes/register-kinds.php:999 2192 2247 msgid "Slept" 2193 2248 msgstr "" 2194 2249 2195 #: includes/register-kinds.php: 9932250 #: includes/register-kinds.php:1002 2196 2251 msgid "Sleep is a passive metrics post type that indicates how much time (and often a graph of how deeply) a person has slept." 2197 2252 msgstr "" 2198 2253 2199 #: includes/register-kinds.php:10 052254 #: includes/register-kinds.php:1014 2200 2255 msgid "Event" 2201 2256 msgstr "" 2202 2257 2203 #: includes/register-kinds.php:10 062258 #: includes/register-kinds.php:1015 2204 2259 msgid "Events" 2205 2260 msgstr "" 2206 2261 2207 #: includes/register-kinds.php:10 072262 #: includes/register-kinds.php:1016 2208 2263 msgid "Planned" 2209 2264 msgstr "" 2210 2265 2211 #: includes/register-kinds.php:101 02266 #: includes/register-kinds.php:1019 2212 2267 msgid "An event is a type of post that in addition to a post name (event title) has a start datetime (likely end datetime), and a location." 2213 2268 msgstr "" 2214 2269 2215 #: includes/register-kinds.php:10 212270 #: includes/register-kinds.php:1030 2216 2271 msgid "Review" 2217 2272 msgstr "" 2218 2273 2219 #: includes/register-kinds.php:10 222274 #: includes/register-kinds.php:1031 2220 2275 msgid "Reviews" 2221 2276 msgstr "" 2222 2277 2223 #: includes/register-kinds.php:10 232278 #: includes/register-kinds.php:1032 2224 2279 msgid "Reviewed" 2225 2280 msgstr "" 2226 2281 2227 #: includes/register-kinds.php:10 262282 #: includes/register-kinds.php:1035 2228 2283 msgid "A review is a post evaluating a product or service, usually involving a written description, sometimes with summary numerical evaluations, also known as just a rating." 2229 2284 msgstr "" 2230 2285 2231 #: includes/register-kinds.php:10 382286 #: includes/register-kinds.php:1047 2232 2287 msgid "Acquisition" 2233 2288 msgstr "" 2234 2289 2235 #: includes/register-kinds.php:10 392290 #: includes/register-kinds.php:1048 2236 2291 msgid "Acquisitions" 2237 2292 msgstr "" 2238 2293 2239 #: includes/register-kinds.php:104 02294 #: includes/register-kinds.php:1049 2240 2295 msgid "Acquired" 2241 2296 msgstr "" 2242 2297 2243 #: includes/register-kinds.php:10 432298 #: includes/register-kinds.php:1052 2244 2299 msgid "Purchases, gifts, found things, or objects donated" 2245 2300 msgstr "" 2246 2301 2247 #: includes/register-kinds.php:10 542302 #: includes/register-kinds.php:1063 2248 2303 msgid "Craft" 2249 2304 msgstr "" 2250 2305 2251 #: includes/register-kinds.php:10 552306 #: includes/register-kinds.php:1064 2252 2307 msgid "Crafts" 2253 2308 msgstr "" 2254 2309 2255 #: includes/register-kinds.php:10 562310 #: includes/register-kinds.php:1065 2256 2311 msgid "Crafted" 2257 2312 msgstr "" 2258 2313 2259 #: includes/register-kinds.php:10 592314 #: includes/register-kinds.php:1068 2260 2315 msgid "Activities like knitting, crocheting, cross stitch, wood working, restoration, 3d printing...the activity of building something." 2261 2316 msgstr "" 2262 2317 2263 #: indieweb-post-kinds.php: 652318 #: indieweb-post-kinds.php:87 2264 2319 msgid "Parse This is not installed. Please advise the developer" 2265 2320 msgstr "" 2266 2321 2267 #: indieweb-post-kinds.php:98 2322 #. translators: %s: Name of the plugin, or path of the file, that loaded the older copy of Parse This. 2323 #: indieweb-post-kinds.php:109 2324 #, php-format 2325 msgid "Post Kinds: %s is loading a copy of Parse This older than version 2.0. Post Kinds will keep working, but URL lookups in the post editor use that older copy. Updating that plugin, or installing the Parse This plugin (version 2.0 or later), resolves this." 2326 msgstr "" 2327 2328 #: indieweb-post-kinds.php:209 2268 2329 msgid "Classic Editor or Disable Gutenberg Plugin is not active. The Post Kinds plugin will not function correctly at this time without using the Classic Editor." 2269 2330 msgstr "" 2270 2331 2271 #: indieweb-post-kinds.php: 2112332 #: indieweb-post-kinds.php:353 2272 2333 msgid "Settings" 2273 2334 msgstr "" 2274 2335 2275 #: indieweb-post-kinds.php: 2332336 #: indieweb-post-kinds.php:384 2276 2337 msgid "For responses to URLs, such as responding to a post or article, this site allows the storage of data around the post/article in order to generate a rich citation. Items such as author name and image, summary of the text, embed provided by third-party site, etc may be stored and are solely to provide this context. We will remove any of this on request." 2277 2338 msgstr "" 2278 2339 2279 #: lib/parse-this/includes/class-parse-this-discovery.php:57 2280 #: lib/parse-this/includes/class-parse-this-opml.php:17 2281 #: lib/parse-this/includes/class-parse-this.php:231 2282 #: lib/parse-this/includes/class-parse-this.php:265 2283 msgid "A valid URL was not provided." 2284 msgstr "" 2285 2286 #: lib/parse-this/includes/class-rest-parse-this.php:19 2287 #: lib/parse-this/includes/class-rest-parse-this.php:20 2288 msgid "Parse This" 2289 msgstr "" 2290 2291 #: lib/parse-this/includes/class-rest-parse-this.php:35 2292 msgid "Parse This Debugger" 2293 msgstr "" 2294 2295 #: lib/parse-this/includes/class-rest-parse-this.php:55 2296 msgid "MF2" 2297 msgstr "" 2298 2299 #: lib/parse-this/includes/class-rest-parse-this.php:63 2300 msgid "Feed Discovery" 2301 msgstr "" 2302 2303 #: lib/parse-this/includes/class-rest-parse-this.php:71 2304 msgid "References" 2305 msgstr "" 2306 2307 #: lib/parse-this/includes/class-rest-parse-this.php:79 2308 msgid "Clean up Location" 2309 msgstr "" 2310 2311 #: lib/parse-this/includes/class-rest-parse-this.php:87 2312 msgid "Return Type" 2313 msgstr "" 2314 2315 #: lib/parse-this/includes/class-rest-parse-this.php:91 2316 msgid "Single" 2317 msgstr "" 2318 2319 #: lib/parse-this/includes/class-rest-parse-this.php:92 2320 msgid "Feed" 2321 msgstr "" 2322 2323 #: lib/parse-this/includes/class-rest-parse-this.php:98 2324 msgid "Follow Author Links" 2325 msgstr "" 2326 2327 #: lib/parse-this/includes/class-rest-parse-this.php:107 2328 msgid "Parse" 2329 msgstr "" 2330 2331 #: templates/reply-author.php:2 2340 #: templates/reply-author.php:16 2332 2341 msgid "Information on the Author or Artist of the Piece" 2333 2342 msgstr "" 2334 2343 2335 #: templates/reply-author.php: 32344 #: templates/reply-author.php:17 2336 2345 msgid "(Multiple Entries separated by semicolon)" 2337 2346 msgstr "" 2338 2347 2339 #: templates/reply-author.php: 182348 #: templates/reply-author.php:32 2340 2349 msgid "Author Photo URL" 2341 2350 msgstr "" 2342 2351 2343 #: templates/reply-details.php: 22352 #: templates/reply-details.php:17 2344 2353 msgid "Information on what you are responding to" 2345 2354 msgstr "" 2346 2355 2347 #: templates/reply-details.php: 42356 #: templates/reply-details.php:19 2348 2357 msgid "Summary/Quote/Caption" 2349 2358 msgstr "" 2350 2359 2351 #: templates/reply-details.php: 92360 #: templates/reply-details.php:24 2352 2361 msgid "Site Name/Publication/Album" 2353 2362 msgstr "" 2354 2363 2355 #: templates/reply-details.php: 142364 #: templates/reply-details.php:29 2356 2365 msgid "Published/Released" 2357 2366 msgstr "" 2358 2367 2359 #: templates/reply-details.php: 152368 #: templates/reply-details.php:30 2360 2369 msgid "Updated" 2361 2370 msgstr "" 2362 2371 2363 #: templates/reply-details.php: 182372 #: templates/reply-details.php:33 2364 2373 msgid "Tags (semicolon separated)" 2365 2374 msgstr "" 2366 2375 2367 #: templates/reply-details.php: 232376 #: templates/reply-details.php:38 2368 2377 msgid "Featured Image" 2369 2378 msgstr "" 2370 2379 2371 #: templates/reply-metabox.php: 302380 #: templates/reply-metabox.php:44 2372 2381 msgid "Details" 2373 2382 msgstr "" 2374 2383 2375 #: templates/reply-metabox.php:33 2384 #: templates/reply-metabox.php:46 2385 msgid "Upload or Attach Media" 2386 msgstr "" 2387 2388 #: templates/reply-metabox.php:47 2376 2389 msgid "Clear" 2377 2390 msgstr "" 2378 2391 2379 #: templates/reply-time.php: 42392 #: templates/reply-time.php:18 2380 2393 msgid "Duration" 2381 2394 msgstr "" 2382 2395 2383 #: templates/reply-time.php: 72396 #: templates/reply-time.php:21 2384 2397 msgid "Years" 2385 2398 msgstr "" 2386 2399 2387 #: templates/reply-time.php: 112400 #: templates/reply-time.php:25 2388 2401 msgid "Months" 2389 2402 msgstr "" 2390 2403 2391 #: templates/reply-time.php: 152404 #: templates/reply-time.php:29 2392 2405 msgid "Days" 2393 2406 msgstr "" 2394 2407 2395 #: templates/reply-time.php: 212408 #: templates/reply-time.php:35 2396 2409 msgid "Hours" 2397 2410 msgstr "" 2398 2411 2399 #: templates/reply-time.php: 252412 #: templates/reply-time.php:39 2400 2413 msgid "Minutes" 2401 2414 msgstr "" 2402 2415 2403 #: templates/reply-time.php: 292416 #: templates/reply-time.php:43 2404 2417 msgid "Seconds" 2405 2418 msgstr "" 2406 2419 2407 #: templates/reply-time.php: 332420 #: templates/reply-time.php:47 2408 2421 msgid "Start Time and End Time will be Used to Calculate Duration if Duration Not Set." 2409 2422 msgstr "" 2410 2423 2411 #: templates/reply-time.php: 352424 #: templates/reply-time.php:49 2412 2425 msgid "Start Time" 2413 2426 msgstr "" 2414 2427 2415 #: templates/reply-time.php: 362428 #: templates/reply-time.php:50 2416 2429 msgid "End Time" 2417 2430 msgstr "" 2418 2431 2419 #: views/kind-bookmark.php: 252420 #: views/kind-favorite.php: 242421 #: views/kind-issue.php: 242422 #: views/kind-jam.php: 282423 #: views/kind-like.php: 252424 #: views/kind-listen.php: 302425 #: views/kind-read.php: 282426 #: views/kind-video.php: 452427 #: views/kind-watch.php: 292428 #: views/kind.php: 352432 #: views/kind-bookmark.php:39 2433 #: views/kind-favorite.php:38 2434 #: views/kind-issue.php:38 2435 #: views/kind-jam.php:46 2436 #: views/kind-like.php:39 2437 #: views/kind-listen.php:48 2438 #: views/kind-read.php:42 2439 #: views/kind-video.php:63 2440 #: views/kind-watch.php:47 2441 #: views/kind.php:53 2429 2442 msgid "by" 2430 2443 msgstr "" 2431 2444 2432 #: views/kind-event.php: 222445 #: views/kind-event.php:39 2433 2446 msgid "Start" 2434 2447 msgstr "" 2435 2448 2436 #: views/kind-event.php: 252449 #: views/kind-event.php:42 2437 2450 msgid "End" 2438 2451 msgstr "" 2439 2452 2440 #: views/kind-itinerary.php: 332453 #: views/kind-itinerary.php:56 2441 2454 msgid "Departs: " 2442 2455 msgstr "" 2443 2456 2444 #: views/kind-itinerary.php: 382457 #: views/kind-itinerary.php:61 2445 2458 msgid "Arrives: " 2446 2459 msgstr "" 2447 2460 2448 #: views/kind-jam.php: 312449 #: views/kind-listen.php: 332450 #: views/kind-watch.php: 322461 #: views/kind-jam.php:49 2462 #: views/kind-listen.php:51 2463 #: views/kind-watch.php:50 2451 2464 msgid " from " 2452 2465 msgstr "" -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/includes/autoload.php
r2332001 r3738687 1 1 <?php 2 /** 3 * Class autoloader for Parse This. 4 * 5 * @package Parse_This 6 */ 2 7 3 // autoloader for Parse This 8 defined( 'ABSPATH' ) || exit; 9 10 /* 11 * Maps classes in the ParseThis namespace to files in this directory, for 12 * example ParseThis\MF2_Utils to class-mf2-utils.php. 13 */ 4 14 spl_autoload_register( 5 15 function ( $class ) { 6 $base_dir = trailingslashit( __DIR__ ); 7 $bases = array( 'Parse_This' ); 8 foreach ( $bases as $base ) { 9 if ( strncmp( $class, $base, strlen( $base ) ) === 0 ) { 10 $filename = 'class-' . strtolower( str_replace( '_', '-', $class ) ); 11 $file = $base_dir . $filename . '.php'; 12 if ( file_exists( $file ) ) { 13 require $file; 14 } 15 } 16 $prefix = 'ParseThis\\'; 17 if ( 0 !== strncmp( $class, $prefix, strlen( $prefix ) ) ) { 18 return; 19 } 20 $name = substr( $class, strlen( $prefix ) ); 21 $file = __DIR__ . '/class-' . strtolower( str_replace( '_', '-', $name ) ) . '.php'; 22 if ( file_exists( $file ) ) { 23 require $file; 16 24 } 17 25 } 18 26 ); 27 28 // Deprecated global names still used by other plugins. 29 require_once __DIR__ . '/aliases.php'; -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/includes/functions.php
r2757240 r3738687 1 1 <?php 2 // Parse This Global Functions 3 4 5 if ( ! function_exists( 'jf2_to_mf2' ) ) { 2 /** 3 * Helper functions. 4 * 5 * All functions are in the ParseThis namespace. Each is wrapped in 6 * function_exists() because several plugins bundle their own copy of Parse 7 * This; the first copy loaded wins. Deprecated global names for some of them 8 * are defined in aliases.php. 9 * 10 * @package Parse_This 11 */ 12 13 namespace ParseThis; 14 15 defined( 'ABSPATH' ) || exit; 16 17 if ( ! function_exists( __NAMESPACE__ . '\\jf2_to_mf2' ) ) { 18 /** 19 * Converts jf2 into microformats2 JSON. 20 * 21 * The inverse of mf2_to_jf2(). Empty values and the _raw key are dropped. 22 * 23 * @since 1.0.0 24 * 25 * @link https://jf2.spec.indieweb.org/ 26 * 27 * @param array|mixed $jf2 jf2 object, or array( 'items' => ... ) for a list. 28 * @return array|mixed mf2 object with type and properties. Values that are not 29 * jf2 objects are returned unchanged. 30 */ 6 31 function jf2_to_mf2( $jf2 ) { 7 32 if ( ! $jf2 || ! is_array( $jf2 ) ) { … … 10 35 if ( 1 === count( $jf2 ) && array_key_exists( 'items', $jf2 ) ) { 11 36 return array( 12 'items' => array_map( 'jf2_to_mf2', $jf2['items'] ),37 'items' => array_map( __NAMESPACE__ . '\\jf2_to_mf2', $jf2['items'] ), 13 38 ); 14 39 } … … 24 49 } 25 50 if ( array_key_exists( 'children', $jf2 ) ) { 26 $mf2['children'] = array_map( 'jf2_to_mf2', $jf2['children'] );51 $mf2['children'] = array_map( __NAMESPACE__ . '\\jf2_to_mf2', $jf2['children'] ); 27 52 unset( $jf2['children'] ); 28 53 } … … 31 56 32 57 foreach ( $jf2 as $key => $value ) { 33 // Exclude values 58 // Exclude values. 34 59 if ( empty( $value ) || ( '_raw' === $key ) ) { 35 60 continue; … … 38 63 $value = array( jf2_to_mf2( $value ) ); 39 64 } elseif ( wp_is_numeric_array( $value ) ) { 40 $value = array_map( 'jf2_to_mf2', $value );65 $value = array_map( __NAMESPACE__ . '\\jf2_to_mf2', $value ); 41 66 } elseif ( ! wp_is_numeric_array( $value ) ) { 42 67 $value = array( $value ); … … 48 73 } 49 74 50 if ( ! function_exists( 'mf2_to_jf2' ) ) { 51 75 if ( ! function_exists( __NAMESPACE__ . '\\mf2_to_jf2' ) ) { 76 77 /** 78 * Converts microformats2 JSON into jf2. 79 * 80 * Single-value property arrays are collapsed to the value, the h- prefix is 81 * removed from types, and nested microformats are converted recursively. 82 * 83 * @since 1.0.0 84 * 85 * @link https://jf2.spec.indieweb.org/ 86 * 87 * @param array|mixed $mf2 mf2 object, a parsed mf2 document with items, or a 88 * list of mf2 objects. 89 * @return array|mixed jf2. A list of one is returned as the single item. 90 * Strings and objects are returned unchanged. 91 */ 52 92 function mf2_to_jf2( $mf2 ) { 53 93 if ( empty( $mf2 ) || is_string( $mf2 ) || is_object( $mf2 ) ) { … … 57 97 $jf2 = array(); 58 98 59 // If it is a numeric array, run this function through each item 99 // If it is a numeric array, run this function through each item. 60 100 if ( wp_is_numeric_array( $mf2 ) ) { 61 $jf2 = array_map( 'mf2_to_jf2', $mf2 );101 $jf2 = array_map( __NAMESPACE__ . '\\mf2_to_jf2', $mf2 ); 62 102 if ( 1 === count( $jf2 ) ) { 63 103 return array_pop( $jf2 ); … … 67 107 68 108 if ( isset( $mf2['items'] ) ) { 69 $jf2['items'] = array_map( 'mf2_to_jf2', $mf2['items'] );109 $jf2['items'] = array_map( __NAMESPACE__ . '\\mf2_to_jf2', $mf2['items'] ); 70 110 } 71 111 72 112 if ( isset( $mf2['children'] ) ) { 73 $jf2['children'] = array_map( 'mf2_to_jf2', $mf2['children'] );113 $jf2['children'] = array_map( __NAMESPACE__ . '\\mf2_to_jf2', $mf2['children'] ); 74 114 } 75 115 … … 82 122 if ( is_array( $value ) ) { 83 123 if ( wp_is_numeric_array( $value ) ) { 84 $value = array_map( 'mf2_to_jf2', $value );124 $value = array_map( __NAMESPACE__ . '\\mf2_to_jf2', $value ); 85 125 if ( is_countable( $value ) && 1 === count( $value ) ) { 86 126 $value = array_pop( $value ); … … 98 138 99 139 100 if ( ! function_exists( 'jf2_location' ) ) { 101 /* 102 Flatten nested location properties. 103 */ 140 if ( ! function_exists( __NAMESPACE__ . '\\jf2_location' ) ) { 141 /** 142 * Flattens a nested jf2 location. 143 * 144 * The latitude, longitude and altitude are copied onto the object, and location 145 * becomes the place's label or name (or is removed). Any check-in on the 146 * object also receives the location's properties. 147 * 148 * @since 1.0.1 149 * 150 * @param array $data jf2 object. 151 * @return array The updated object. 152 */ 104 153 function jf2_location( $data ) { 105 154 if ( ! array_key_exists( 'location', $data ) ) { 106 155 return $data; 107 156 } 108 $location = $data['location']; 109 if ( is_string( $location ) ) { 157 // jf2_references() may have moved the location or check-in to refs, 158 // leaving its URL (as a one-item list) in its place. 159 $from_refs = function ( $value ) use ( $data ) { 160 if ( is_array( $value ) && wp_is_numeric_array( $value ) && 1 === count( $value ) ) { 161 $value = $value[0]; 162 } 163 if ( is_string( $value ) && isset( $data['refs'][ $value ] ) && is_array( $data['refs'][ $value ] ) ) { 164 return $data['refs'][ $value ]; 165 } 166 return $value; 167 }; 168 $location = $from_refs( $data['location'] ); 169 if ( ! is_array( $location ) || wp_is_numeric_array( $location ) ) { 110 170 return $data; 111 171 } … … 122 182 unset( $data['location'] ); 123 183 } 184 if ( isset( $data['checkin'] ) ) { 185 $data['checkin'] = $from_refs( $data['checkin'] ); 186 } 124 187 if ( array_key_exists( 'checkin', $data ) && is_array( $data['checkin'] ) ) { 125 188 foreach ( $location as $key => $value ) { … … 134 197 135 198 136 if ( ! function_exists( 'jf2_references' ) ) { 137 /* 138 Turns nested properties into references per the jf2 spec 139 */ 199 if ( ! function_exists( __NAMESPACE__ . '\\jf2_author_to_card' ) ) { 200 /** 201 * Converts an author value into a jf2 card. 202 * 203 * Microsub requires author to always be a card, and jf2 consumers generally 204 * expect one. A URL string becomes a card with that url, any other string a 205 * card with that name, and an object without a type gets type card. A list of 206 * authors is converted item by item; a list of one becomes that card. 207 * 208 * @since 2.0.0 209 * 210 * @param mixed $author Author: a string, an object or a list of either. 211 * @return array|null The card (or list of cards), or null if there is no author. 212 */ 213 function jf2_author_to_card( $author ) { 214 if ( is_string( $author ) ) { 215 $author = trim( $author ); 216 if ( '' === $author ) { 217 return null; 218 } 219 $key = ( preg_match( '#^https?://#i', $author ) && filter_var( $author, FILTER_VALIDATE_URL ) ) ? 'url' : 'name'; 220 return array( 221 'type' => 'card', 222 $key => $author, 223 ); 224 } 225 if ( ! is_array( $author ) || empty( $author ) ) { 226 return null; 227 } 228 if ( wp_is_numeric_array( $author ) ) { 229 $cards = array_values( array_filter( array_map( __NAMESPACE__ . '\\jf2_author_to_card', $author ) ) ); 230 if ( empty( $cards ) ) { 231 return null; 232 } 233 return ( 1 === count( $cards ) ) ? $cards[0] : $cards; 234 } 235 if ( ! isset( $author['type'] ) ) { 236 $author = array( 'type' => 'card' ) + $author; 237 } 238 return $author; 239 } 240 } 241 242 if ( ! function_exists( __NAMESPACE__ . '\\jf2_references' ) ) { 243 /** 244 * Moves nested objects into refs, per the jf2 spec. 245 * 246 * Any nested typed object with an http or https URL (an h-cite, h-card, 247 * h-event, h-product or any other type) is replaced by its URL, and stored 248 * in refs keyed by that URL, so the result is one level deep. The property 249 * becomes a list of values, as before. Objects without such a URL stay in place, authors stay 250 * cards, and a feed's items are left alone. References that a nested object 251 * carries are merged into the same refs. 252 * 253 * @since 1.0.0 254 * @since 2.0.0 Moves any typed object with a URL, not only citations, and 255 * merges nested refs. 256 * 257 * @link https://jf2.spec.indieweb.org/#references 258 * 259 * @param array $data jf2 object. 260 * @return array The updated object. 261 */ 140 262 function jf2_references( $data ) { 263 if ( ! is_array( $data ) ) { 264 return $data; 265 } 266 // Authors stay cards, and a feed's entries are not references. 267 $skip = array( 'refs', 'author', 'items', 'children' ); 141 268 foreach ( $data as $key => $val ) { 142 if ( ! is_array( $val ) ) {269 if ( ! is_array( $val ) || in_array( $key, $skip, true ) || ( is_string( $key ) && '_' === substr( $key, 0, 1 ) ) ) { 143 270 continue; 144 271 } 145 if ( ! wp_is_numeric_array( $val ) ) { 146 $val = array( $val ); 147 } 148 if ( wp_is_numeric_array( $val ) ) { 149 foreach ( $val as $value ) { 150 // Indicates nested type 151 if ( is_array( $value ) && array_key_exists( 'type', $value ) && 'cite' === $value['type'] ) { 152 if ( ! isset( $data['refs'] ) ) { 153 $data['refs'] = array(); 154 } 155 if ( isset( $value['url'] ) ) { 156 $data['refs'][ $value['url'] ] = $value; 157 $data[ $key ] = array( $value['url'] ); 158 } 272 $values = wp_is_numeric_array( $val ) ? $val : array( $val ); 273 $changed = false; 274 foreach ( $values as $i => $value ) { 275 if ( ! is_array( $value ) ) { 276 continue; 277 } 278 // A nested object may carry references of its own: keep one flat refs. 279 if ( isset( $value['refs'] ) && is_array( $value['refs'] ) ) { 280 foreach ( $value['refs'] as $ref_url => $ref ) { 281 $data['refs'][ $ref_url ] = isset( $data['refs'][ $ref_url ] ) ? $data['refs'][ $ref_url ] + $ref : $ref; 159 282 } 160 if ( 'category' === $key ) { 161 foreach ( $value as $k => $v ) { 162 if ( is_array( $v ) && array_key_exists( 'type', $v ) ) { 163 if ( ! isset( $data['refs'] ) ) { 164 $data['refs'] = array(); 165 } 166 if ( isset( $v['url'] ) ) { 167 $data['refs'][ $v['url'] ] = $v; 168 $data['category'][ $k ] = $v['url']; 169 } 170 } 171 } 172 } 173 } 283 unset( $value['refs'] ); 284 $values[ $i ] = $value; 285 $changed = true; 286 } 287 $url = $value['url'] ?? null; 288 if ( is_array( $url ) ) { 289 $url = reset( $url ); 290 } 291 // Any typed object with an http(s) URL is a reference. One without 292 // stays in place, where sanitizing can drop a bad URL and keep the rest. 293 if ( empty( $value['type'] ) || ! is_string( $value['type'] ) || ! is_string( $url ) || ! preg_match( '#^https?://#i', trim( $url ) ) ) { 294 continue; 295 } 296 $data['refs'][ $url ] = isset( $data['refs'][ $url ] ) ? $data['refs'][ $url ] + $value : $value; 297 $values[ $i ] = $url; 298 $changed = true; 299 } 300 if ( $changed ) { 301 $data[ $key ] = $values; 174 302 } 175 303 } … … 178 306 } 179 307 180 if ( ! function_exists( 'url_to_author' ) ) { 181 /** 182 * Examine a url and try to determine the author ID it represents. 183 * 184 * @param string $url Permalink to check. 185 * 186 * @return WP_User, or null on failure. 308 if ( ! function_exists( __NAMESPACE__ . '\\url_to_author' ) ) { 309 /** 310 * Returns the local user that an author archive URL belongs to. 311 * 312 * Handles ?author=N URLs and pretty author permalinks. 313 * 314 * @since 1.0.0 315 * 316 * @global WP_Rewrite $wp_rewrite WordPress rewrite component. 317 * 318 * @param string $url URL to check. 319 * @return WP_User|false|null The user, false if no user has that ID or slug, or 320 * null if the URL is not on this site or not an 321 * author URL. 187 322 */ 188 323 function url_to_author( $url ) { 189 324 global $wp_rewrite; 190 // check if url hase the same host 325 // check if url hase the same host. 191 326 if ( wp_parse_url( site_url(), PHP_URL_HOST ) !== wp_parse_url( $url, PHP_URL_HOST ) ) { 192 327 return null; 193 328 } 194 // first, check to see if there is a 'author=N' to match against 329 // first, check to see if there is a 'author=N' to match against. 195 330 if ( preg_match( '/[?&]author=(\d+)/i', $url, $values ) ) { 196 331 $id = absint( $values[1] ); … … 199 334 } 200 335 } 201 // check to see if we are using rewrite rules 336 // check to see if we are using rewrite rules. 202 337 $rewrite = $wp_rewrite->wp_rewrite_rules(); 203 // not using rewrite rules, and 'author=N' method failed, so we're out of options 338 // not using rewrite rules, and 'author=N' method failed, so we're out of options. 204 339 if ( empty( $rewrite ) ) { 205 340 return null; 206 341 } 207 // generate rewrite rule for the author url 342 // generate rewrite rule for the author url. 208 343 $author_rewrite = $wp_rewrite->get_author_permastruct(); 209 344 $author_regexp = str_replace( '%author%', '', $author_rewrite ); 210 // match the rewrite rule with the passed url 345 // match the rewrite rule with the passed url. 211 346 if ( preg_match( '/https?:\/\/(.+)' . preg_quote( $author_regexp, '/' ) . '([^\/]+)/i', $url, $match ) ) { 212 347 $user = get_user_by( 'slug', $match[2] ); … … 219 354 } 220 355 221 if ( ! function_exists( 'url_to_user' ) ) { 222 /** 223 * Get the user associated with a URL. 224 * 225 * @param string $url url to match 226 * @return WP_User $user Associated user, or null if no associated user 356 if ( ! function_exists( __NAMESPACE__ . '\\url_to_user' ) ) { 357 /** 358 * Returns the local user associated with a URL. 359 * 360 * The site's home URL maps to the IndieWeb plugin's default author when the 361 * IndieWeb plugin is active, otherwise to the only author if there is just 362 * one. Author archive URLs map to their author, and other URLs are matched 363 * against users' website field. 364 * 365 * @since 1.0.0 366 * 367 * @param string $url URL to check. 368 * @return WP_User|false|null The user, or null (or false) if none matches. 227 369 */ 228 370 function url_to_user( $url ) { … … 230 372 return null; 231 373 } 232 // Ensure has trailing slash 374 // Ensure has trailing slash. 233 375 $url = trailingslashit( $url ); 234 376 if ( ( 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ) ) && ( wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( $url, PHP_URL_HOST ) ) ) { 235 377 $url = set_url_scheme( $url, 'https' ); 236 378 } 237 // Try to save the expense of a search query if the URL is the site URL 379 // Try to save the expense of a search query if the URL is the site URL. 238 380 if ( home_url( '/' ) === $url ) { 239 // Use the Indieweb settings to set the default author 381 // Use the Indieweb settings to set the default author. 240 382 if ( class_exists( 'Indieweb_Plugin' ) && ( get_option( 'iw_single_author' ) || ! is_multi_author() ) ) { 241 383 return get_user_by( 'id', get_option( 'iw_default_author' ) ); 242 384 } 243 $users = get_users( array( ' who' => 'authors') );385 $users = get_users( array( 'capability' => array( 'edit_posts' ) ) ); 244 386 if ( 1 === count( $users ) ) { 245 387 return $users[0]; … … 247 389 return null; 248 390 } 249 // Check if this is a author post URL 391 // Check if this is a author post URL. 250 392 $user = url_to_author( $url ); 251 if ( $user instanceof WP_User ) {393 if ( $user instanceof \WP_User ) { 252 394 return $user; 253 395 } … … 257 399 ); 258 400 $users = get_users( $args ); 259 // check result 401 // check result. 260 402 if ( ! empty( $users ) ) { 261 403 return $users[0]; … … 265 407 } 266 408 267 if ( ! function_exists( 'ifset' ) ) { 268 /** 269 * If set, return otherwise false. 270 * 271 * @param type $var Check if set. 272 * @return $var|false Return either $var or $return. 273 */ 274 function ifset( &$var, $return = false ) { 275 276 return isset( $var ) ? $var : $return; 277 } 278 } 279 280 281 /* 282 Inverse of wp_parse_url 283 * 284 * Slightly modified from p3k-utils (https://github.com/aaronpk/p3k-utils) 285 * Copyright 2017 Aaron Parecki, used with permission under MIT License 286 * 287 * @link http://php.net/parse_url 288 * @param string $parsed_url the parsed URL (wp_parse_url) 289 * @return string the final URL 290 */ 291 if ( ! function_exists( 'build_url' ) ) { 409 if ( ! function_exists( __NAMESPACE__ . '\\build_url' ) ) { 410 /** 411 * Builds a URL from its parts; the inverse of wp_parse_url(). 412 * 413 * Slightly modified from p3k-utils. Copyright 2017 Aaron Parecki, used with 414 * permission under the MIT License. 415 * 416 * @since 1.0.0 417 * 418 * @link https://github.com/aaronpk/p3k-utils 419 * @link https://www.php.net/parse_url 420 * 421 * @param array $parsed_url URL components as returned by wp_parse_url(). 422 * @return string The URL. 423 */ 292 424 function build_url( $parsed_url ) { 293 425 $scheme = ! empty( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : ''; … … 306 438 307 439 308 if ( ! function_exists( 'normalize_url' ) ) { 309 // Adds slash if no path is in the URL, and convert hostname to lowercase 440 if ( ! function_exists( __NAMESPACE__ . '\\normalize_url' ) ) { 441 /** 442 * Normalizes a URL for comparison. 443 * 444 * Lowercases the host and adds a / path if there is none. 445 * 446 * @since 1.0.0 447 * 448 * @param string $url URL to normalize. 449 * @param bool $strip Optional. Whether to remove the query string. Default false. 450 * @return string|null The normalized URL, or null if it has no host. 451 */ 310 452 function normalize_url( $url, $strip = false ) { 311 453 $parts = wp_parse_url( $url ); … … 323 465 } 324 466 325 if ( ! function_exists( 'normalize_iso8601' ) ) { 326 // Tries to normalizes dates to a standard iso8601 string 467 if ( ! function_exists( __NAMESPACE__ . '\\normalize_iso8601' ) ) { 468 /** 469 * Normalizes a date string to W3C (ISO 8601) format. 470 * 471 * @since 1.0.0 472 * 473 * @param string|mixed $string Date string. 474 * @return string|null The date in W3C format, the original string if it cannot 475 * be parsed, or null if it is empty or not a string. 476 */ 327 477 function normalize_iso8601( $string ) { 328 $date = new DateTime( $string ); 329 if ( $date ) { 330 return $date->format( DATE_W3C ); 331 } 332 return $string; 333 } 334 } 335 336 if ( ! function_exists( 'post_type_discovery' ) ) { 478 if ( empty( $string ) || ! is_string( $string ) ) { 479 return null; 480 } 481 try { 482 $date = new \DateTime( $string ); 483 } catch ( \Exception $e ) { 484 return $string; 485 } 486 return $date->format( DATE_W3C ); 487 } 488 } 489 490 if ( ! function_exists( __NAMESPACE__ . '\\pt_find_fragment_element' ) ) { 491 /** 492 * Finds the element a URL fragment points to. 493 * 494 * @since 2.0.0 495 * 496 * @param DOMDocument $doc Parsed page. 497 * @param string $id Fragment, without the #. 498 * @return DOMElement|null The element with that id, or null. 499 */ 500 function pt_find_fragment_element( $doc, $id ) { 501 if ( ! $doc instanceof \DOMDocument || ! is_string( $id ) || '' === $id ) { 502 return null; 503 } 504 $id = rawurldecode( $id ); 505 foreach ( $doc->getElementsByTagName( '*' ) as $element ) { 506 if ( $element->getAttribute( 'id' ) === $id ) { 507 return $element; 508 } 509 } 510 return null; 511 } 512 } 513 514 if ( ! function_exists( __NAMESPACE__ . '\\pt_effective_url' ) ) { 515 /** 516 * Returns the URL a response finally came from, after any redirects. 517 * 518 * A fragment on the requested URL (which is never sent to the server) is 519 * kept, so that it can still pick out part of the page. 520 * 521 * @since 2.0.0 522 * 523 * @param string $url Requested URL. 524 * @param array $response Response from pt_remote_get(). 525 * @return string The final URL, or $url if it isn't known. 526 */ 527 function pt_effective_url( $url, $response ) { 528 $object = ( is_array( $response ) && isset( $response['http_response'] ) && is_object( $response['http_response'] ) && method_exists( $response['http_response'], 'get_response_object' ) ) ? $response['http_response']->get_response_object() : null; 529 if ( ! $object || empty( $object->url ) || ! is_string( $object->url ) || ! wp_http_validate_url( $object->url ) ) { 530 return $url; 531 } 532 $final = $object->url; 533 $fragment = wp_parse_url( $url, PHP_URL_FRAGMENT ); 534 if ( $fragment && ! wp_parse_url( $final, PHP_URL_FRAGMENT ) ) { 535 $final .= '#' . $fragment; 536 } 537 return $final; 538 } 539 } 540 541 if ( ! function_exists( __NAMESPACE__ . '\\pt_sniff_content_type' ) ) { 542 /** 543 * Works out a response's real format when its content type is missing, 544 * generic or wrong. 545 * 546 * Feeds are often served as text/plain or text/html, and JSON as 547 * text/plain or application/octet-stream. A body whose root element is 548 * <rss>, <rdf:RDF> or <feed> is a feed whatever the header says (an HTML 549 * page can't have those roots). A body that is a JSON object is checked 550 * for JSON Feed and mf2 JSON when the header is missing or generic. 551 * 552 * @since 2.0.0 553 * 554 * @param string $content_type Content type from the response, without 555 * parameters. 556 * @param string $body Response body. 557 * @return string The content type to use: application/rss+xml, 558 * application/atom+xml, application/feed+json or 559 * application/mf2+json when sniffed, otherwise 560 * $content_type unchanged. 561 */ 562 function pt_sniff_content_type( $content_type, $body ) { 563 if ( ! is_string( $body ) || '' === $body ) { 564 return $content_type; 565 } 566 $known = array( 'application/rss+xml', 'application/atom+xml', 'application/feed+json', 'application/mf2+json', 'application/jf2+json', 'application/jf2feed+json' ); 567 if ( in_array( $content_type, $known, true ) ) { 568 return $content_type; 569 } 570 // Skip a byte order mark, the XML declaration, processing instructions, comments and a doctype. 571 $start = ltrim( substr( $body, 0, 4096 ), "\xEF\xBB\xBF \t\r\n" ); 572 $start = preg_replace( '/^(\s*(<\?[^>]*\?>|<!--.*?-->|<!DOCTYPE[^>]*>))+\s*/is', '', $start ); 573 if ( preg_match( '/^<(rss|rdf:RDF)[\s>]/i', $start ) ) { 574 return 'application/rss+xml'; 575 } 576 if ( preg_match( '/^<feed[\s>]/i', $start ) ) { 577 return 'application/atom+xml'; 578 } 579 $generic = array( '', 'text/plain', 'application/octet-stream', 'text/html' ); 580 if ( in_array( $content_type, $generic, true ) && '{' === substr( $start, 0, 1 ) ) { 581 $json = json_decode( $body, true ); 582 if ( is_array( $json ) && isset( $json['version'] ) && is_string( $json['version'] ) && 0 === strpos( $json['version'], 'https://jsonfeed.org/version/' ) ) { 583 return 'application/feed+json'; 584 } 585 if ( is_array( $json ) && isset( $json['items'][0]['type'], $json['items'][0]['properties'] ) ) { 586 return 'application/mf2+json'; 587 } 588 } 589 return $content_type; 590 } 591 } 592 593 if ( ! function_exists( __NAMESPACE__ . '\\name_is_content_prefix' ) ) { 594 /** 595 * Checks whether a name only repeats the start of the content. 596 * 597 * Ignores a trailing ellipsis (... or …) on the name, as on truncated 598 * titles, and ignores whitespace in both. 599 * 600 * @since 2.0.0 601 * 602 * @param mixed $name Name. 603 * @param mixed $content Content text. 604 * @return bool True if both are non-empty strings and the content starts 605 * with the name. 606 */ 607 function name_is_content_prefix( $name, $content ) { 608 if ( ! is_string( $name ) || ! is_string( $content ) ) { 609 return false; 610 } 611 $name = preg_replace( '/\\s+/u', '', preg_replace( '/\\s*(\\.\\.\\.|…)\\s*$/u', '', $name ) ); 612 $content = preg_replace( '/\\s+/u', '', $content ); 613 return '' !== $name && '' !== $content && 0 === strpos( $content, $name ); 614 } 615 } 616 617 if ( ! function_exists( __NAMESPACE__ . '\\post_type_discovery' ) ) { 618 /** 619 * Determines the IndieWeb post type of a jf2 or mf2 entry. 620 * 621 * Events, reviews and recipes are their own type. For an entry, response 622 * properties decide the type first (review-of, rsvp, checkin, like-of, 623 * in-reply-to, ...), then media (video, photo, audio). An entry with a name 624 * that is not a prefix of its content (or summary) is an article; anything 625 * else is a note. 626 * 627 * @since 1.0.0 628 * @since 2.0.0 Adds review (from review-of, or the review type) and recipe, 629 * and follows the spec for a name without content. 630 * 631 * @link https://indieweb.org/post-type-discovery 632 * 633 * @param array|mixed $jf2 jf2 object, or an mf2 object (converted first). 634 * @return string The post type, or an empty string for other types. 635 */ 337 636 function post_type_discovery( $jf2 ) { 338 637 if ( ! is_array( $jf2 ) ) { … … 345 644 return ''; 346 645 } 347 if ( 'event' === $jf2['type'] ) { 348 return 'event'; 646 // Events (as in the spec), and reviews and recipes (as XRay does), are their own type. 647 if ( in_array( $jf2['type'], array( 'event', 'review', 'recipe' ), true ) ) { 648 return $jf2['type']; 349 649 } 350 650 if ( 'entry' === $jf2['type'] ) { 351 651 $map = array( 652 'review' => array( 'review-of' ), 352 653 'rsvp' => array( 'rsvp' ), 353 654 'checkin' => array( 'checkin' ), … … 377 678 } 378 679 } 379 if ( isset( $jf2['name'] ) && ! empty( $jf2['name'] ) ) { 380 $jf2['name'] = $jf2['name']; 381 $content = ifset( $jf2['content'] ); 382 if ( ! $content ) { 383 $content = ifset( $jf2['summary'] ); 384 } 385 if ( is_array( $content ) && array_key_exists( 'text', $content ) ) { 386 $content = $content['text']; 387 } 388 if ( is_string( $content ) ) { 389 $content = trim( $content ); 390 if ( 0 !== strpos( $content, $jf2['name'] ) ) { 391 return 'article'; 392 } 393 } 394 } 395 return 'note'; 680 // https://www.w3.org/TR/post-type-discovery/#algorithm: a name that is not 681 // a prefix of the content (or summary, or nothing) makes an article. A 682 // truncated name (ending in an ellipsis) is compared without it. 683 $name = ( isset( $jf2['name'] ) && is_string( $jf2['name'] ) ) ? trim( preg_replace( '/\s+/u', ' ', $jf2['name'] ) ) : ''; 684 if ( '' !== $name ) { 685 $content = $jf2['content'] ?? null; 686 if ( is_array( $content ) ) { 687 $content = $content['text'] ?? ( $content['value'] ?? null ); 688 } 689 if ( ! is_string( $content ) || '' === trim( $content ) ) { 690 $content = $jf2['summary'] ?? ''; 691 } 692 $content = is_string( $content ) ? trim( preg_replace( '/\s+/u', ' ', $content ) ) : ''; 693 if ( ! name_is_content_prefix( $name, $content ) ) { 694 return 'article'; 695 } 696 } 697 return 'note'; 396 698 } 397 699 return ''; … … 399 701 } 400 702 401 if ( ! function_exists( 'seconds_to_iso8601' ) ) { 703 if ( ! function_exists( __NAMESPACE__ . '\\seconds_to_iso8601' ) ) { 704 /** 705 * Converts a number of seconds into an ISO 8601 duration. 706 * 707 * For example 3725 becomes PT1H2M5S. 708 * 709 * @since 1.0.0 710 * 711 * @param int|float $second Duration in seconds. 712 * @return string ISO 8601 duration. 713 */ 402 714 function seconds_to_iso8601( $second ) { 403 715 $h = intval( $second / 3600 ); … … 418 730 } 419 731 420 if ( ! function_exists( 'pt_load_domdocument' ) ) { 732 if ( ! function_exists( __NAMESPACE__ . '\\pt_load_domdocument' ) ) { 733 /** 734 * Parses HTML into a DOMDocument. 735 * 736 * On PHP 8.4 and later, uses PHP's own HTML5 parser (Dom\HTMLDocument), 737 * which builds the same tree a browser does and is several times faster 738 * than masterminds/html5 (see pt_native_html_document()). It can be turned 739 * off with the parse_this_native_html_parser filter. Otherwise, or if that 740 * fails, uses the bundled masterminds/html5 parser, and failing that PHP's 741 * DOMDocument with errors suppressed. 742 * 743 * @since 1.0.0 744 * @since 2.0.0 Uses PHP's HTML5 parser when available. 745 * 746 * @param string $content HTML. 747 * @return DOMDocument The parsed document. 748 */ 421 749 function pt_load_domdocument( $content ) { 750 $content = (string) $content; 751 /** 752 * Filters whether to parse HTML with PHP's own HTML5 parser (PHP 8.4+). 753 * 754 * Return false to use masterminds/html5 even when PHP's parser is 755 * available. The two can differ on invalid markup, where PHP's parser 756 * follows the HTML5 algorithm (as browsers do). 757 * 758 * @since 2.0.0 759 * 760 * @param bool $enabled Whether to use it. Default true. 761 */ 762 if ( class_exists( 'Dom\HTMLDocument' ) && apply_filters( 'parse_this_native_html_parser', true ) ) { 763 $doc = pt_native_html_document( $content ); 764 if ( $doc ) { 765 return $doc; 766 } 767 } 422 768 if ( ! class_exists( '\Masterminds\HTML5', false ) ) { 423 769 $file = plugin_dir_path( __DIR__ ) . 'lib/html5/autoloader.php'; … … 430 776 $doc = $doc->loadHTML( $content ); 431 777 } else { 432 $doc = new DOMDocument(); 778 $doc = new \DOMDocument(); 779 // DOMDocument::loadHTML() throws on an empty string in PHP 8. 780 if ( '' === trim( $content ) ) { 781 return $doc; 782 } 433 783 libxml_use_internal_errors( true ); 434 if ( function_exists( 'mb_convert_encoding' ) ) { 435 $content = mb_convert_encoding( $content, 'HTML-ENTITIES', mb_detect_encoding( $content ) ); 784 if ( function_exists( 'mb_encode_numericentity' ) ) { 785 // DOMDocument assumes ISO-8859-1, so convert to UTF-8 and encode non-ASCII characters as entities. 786 $encoding = mb_detect_encoding( $content, array( 'UTF-8', 'ISO-8859-1' ), true ); 787 if ( $encoding && 'UTF-8' !== $encoding ) { 788 $content = mb_convert_encoding( $content, 'UTF-8', $encoding ); 789 } 790 $content = mb_encode_numericentity( $content, array( 0x80, 0x10FFFF, 0, 0x1FFFFF ), 'UTF-8' ); 436 791 } 437 792 $doc->loadHTML( $content ); … … 441 796 } 442 797 } 443 if ( ! function_exists( 'pt_secure_rewrite' ) ) { 798 799 if ( ! function_exists( __NAMESPACE__ . '\\pt_native_html_document' ) ) { 800 /** 801 * Parses HTML with PHP's HTML5 parser and returns it as a DOMDocument. 802 * 803 * Dom\HTMLDocument (PHP 8.4+) belongs to PHP's new DOM API, while php-mf2 804 * and the rest of Parse This work with DOMDocument. The parsed tree is 805 * passed across as XML, which reproduces it exactly without parsing the 806 * HTML twice. Comments (which may contain "--") and attributes whose names 807 * aren't valid XML (@click, x-on:click, :class) are removed first, since 808 * XML can't hold them and nothing here reads them. Elements are kept out of 809 * the XHTML namespace, so XPath queries such as //meta work as before. 810 * 811 * @since 2.0.0 812 * 813 * @param string $content HTML. 814 * @return DOMDocument|null The parsed document, or null if PHP's parser 815 * isn't available or the document can't be 816 * passed across. 817 */ 818 function pt_native_html_document( $content ) { 819 if ( ! class_exists( 'Dom\HTMLDocument' ) ) { 820 return null; 821 } 822 try { 823 $html = \Dom\HTMLDocument::createFromString( (string) $content, LIBXML_NOERROR | \Dom\HTML_NO_DEFAULT_NS ); 824 $xpath = new \Dom\XPath( $html ); 825 foreach ( iterator_to_array( $xpath->query( '//comment()' ) ) as $comment ) { 826 $comment->remove(); 827 } 828 foreach ( iterator_to_array( $xpath->query( '//@*' ) ) as $attribute ) { 829 if ( ! preg_match( '/^[A-Za-z_][A-Za-z0-9_.\-]*$/', $attribute->name ) ) { 830 $attribute->ownerElement->removeAttribute( $attribute->name ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- PHP DOM property. 831 } 832 } 833 $xml = $html->saveXml(); 834 } catch ( \Throwable $e ) { 835 return null; 836 } 837 $doc = new \DOMDocument(); 838 $errors = libxml_use_internal_errors( true ); 839 $loaded = $doc->loadXML( $xml, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_PARSEHUGE ); 840 libxml_clear_errors(); 841 libxml_use_internal_errors( $errors ); 842 return $loaded ? $doc : null; 843 } 844 } 845 if ( ! function_exists( __NAMESPACE__ . '\\pt_remote_get' ) ) { 846 /** 847 * Retrieves a remote URL, retrying once with a browser user agent if the site rejects the request. 848 * 849 * Fetched documents decide which further URLs are requested (author pages, 850 * alternates, REST API links), so this must stay on wp_safe_remote_get(), 851 * which refuses local and private addresses. Don't switch it to 852 * wp_remote_get(). 853 * 854 * @param string $url URL to retrieve. 855 * @param array $args Optional. Arguments passed to wp_safe_remote_get(). 856 * @param array $retry_codes Optional. Response codes that trigger the retry. 857 * @return array|WP_Error The response, or WP_Error on failure. 858 */ 859 function pt_remote_get( $url, $args = array(), $retry_codes = array( 403, 415 ) ) { 860 $args = wp_parse_args( 861 $args, 862 array( 863 'timeout' => 15, 864 'limit_response_size' => 1048576, 865 'redirection' => 5, 866 ) 867 ); 868 869 $response = wp_safe_remote_get( $url, $args ); 870 if ( is_wp_error( $response ) ) { 871 return $response; 872 } 873 if ( ! in_array( (int) wp_remote_retrieve_response_code( $response ), $retry_codes, true ) ) { 874 return $response; 875 } 876 877 $args['user-agent'] = 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:57.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 Parse This/WP'; 878 $response = wp_safe_remote_get( $url, $args ); 879 if ( is_wp_error( $response ) ) { 880 return $response; 881 } 882 $code = (int) wp_remote_retrieve_response_code( $response ); 883 if ( in_array( $code, $retry_codes, true ) ) { 884 return new \WP_Error( 'source_error', __( 'Unable to retrieve the URL.', 'parse-this' ), array( 'response_code' => $code ) ); 885 } 886 return $response; 887 } 888 } 889 890 if ( ! function_exists( __NAMESPACE__ . '\\pt_secure_rewrite' ) ) { 891 /** 892 * Upgrades http:// URLs to https:// for hosts known to support HTTPS. 893 * 894 * @since 1.0.1 895 * 896 * @param string $url URL. 897 * @return string The URL, with https:// if its domain is on the list. 898 */ 444 899 function pt_secure_rewrite( $url ) { 445 900 $host = wp_parse_url( $url, PHP_URL_HOST ); … … 460 915 'google.com', 461 916 'gravatar.com', 462 'gstatic.com', 917 'gstatic.com', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent -- A domain to rewrite to https, not content loaded from it. 463 918 'kernel.org', 464 919 'lwn.net', 465 920 'tumblr.com', 466 921 'twitter.com', 922 'x.com', 467 923 'vimeo.com', 468 924 'wikipedia.org', … … 470 926 'youtube.com', 471 927 ); 928 /** 929 * Filters the domains whose http:// URLs are upgraded to https://. 930 * 931 * Matching is on the registrable domain, so listing example.com also 932 * covers www.example.com. 933 * 934 * @since 1.0.1 935 * 936 * @param string[] $secure Domains known to support HTTPS. 937 */ 472 938 $secure = apply_filters( 'pt_rewrite_secure', $secure ); 473 939 if ( in_array( $host, $secure, true ) ) { … … 479 945 480 946 481 /** 482 * Parses Link Headers 483 * 484 * 485 * @return array 486 */ 487 488 if ( ! function_exists( 'pt_parse_header_links' ) ) { 947 if ( ! function_exists( __NAMESPACE__ . '\\pt_parse_header_links' ) ) { 948 /** 949 * Parses HTTP Link headers. 950 * 951 * @since 2.0.0 952 * 953 * @link https://www.rfc-editor.org/rfc/rfc8288 954 * 955 * @param string|string[] $links One Link header value, or several. A value may 956 * hold multiple comma-separated links. 957 * @return array[] One entry per link and rel value, with 'uri', 'rel' and any 958 * other parameters (type, title, ...). 959 */ 489 960 function pt_parse_header_links( $links ) { 490 961 $items = array(); 491 962 963 if ( is_string( $links ) ) { 964 $links = array( $links ); 965 } 966 492 967 if ( is_array( $links ) && 1 <= count( $links ) ) { 968 // A header may hold several comma-separated links. Only split on commas that start a new <uri>, as URIs may contain commas. 969 $split = array(); 493 970 foreach ( $links as $link ) { 494 $item = array(); 495 $pieces = explode( ';', $link ); 496 $uri = array_shift( $pieces ); 971 $split = array_merge( $split, preg_split( '/,(?=\s*<)/', $link ) ); 972 } 973 foreach ( $split as $link ) { 974 $item = array(); 975 if ( preg_match( '/^\s*<([^>]*)>(.*)$/s', $link, $match ) ) { 976 $uri = $match[1]; 977 $pieces = explode( ';', $match[2] ); 978 } else { 979 $pieces = explode( ';', $link ); 980 $uri = array_shift( $pieces ); 981 } 497 982 foreach ( $pieces as $p ) { 498 $elements = explode( '=', $p ); 499 500 $item[ trim( $elements[0] ) ] = trim( $elements[1], '"\'' ); 983 $elements = explode( '=', $p, 2 ); 984 $name = trim( $elements[0] ); 985 if ( '' === $name ) { 986 continue; 987 } 988 989 $item[ $name ] = isset( $elements[1] ) ? trim( $elements[1], " \t\"'" ) : ''; 501 990 } 502 991 … … 519 1008 } 520 1009 521 /** 522 * Find WordPress REST Alternate 523 * 524 * 525 * @return string|false 526 */ 527 528 if ( ! function_exists( 'pt_find_rest_alternate' ) ) { 1010 if ( ! function_exists( __NAMESPACE__ . '\\pt_find_rest_alternate' ) ) { 1011 /** 1012 * Finds the application/json alternate link among parsed Link headers. 1013 * 1014 * WordPress sends this for each post, pointing at its REST API URL. 1015 * 1016 * @since 2.0.0 1017 * 1018 * @param array[] $links Links from pt_parse_header_links(). 1019 * @return string|false The URL, or false if there is none. 1020 */ 529 1021 function pt_find_rest_alternate( $links ) { 530 1022 foreach ( $links as $link ) { 531 if ( 'alternate' === ifset( $link['rel'] ) && 'application/json' === ifset( $link['type']) ) {1023 if ( 'alternate' === ( $link['rel'] ?? null ) && 'application/json' === ( $link['type'] ?? null ) ) { 532 1024 return $link['uri']; 533 1025 } … … 538 1030 } 539 1031 540 /** 541 * Find WordPress REST Endpoint 542 * 543 * 544 * @return string|false 545 */ 546 547 if ( ! function_exists( 'pt_find_rest_endpoint' ) ) { 1032 if ( ! function_exists( __NAMESPACE__ . '\\pt_find_rest_endpoint' ) ) { 1033 /** 1034 * Finds the WordPress REST API root among parsed Link headers. 1035 * 1036 * @since 2.0.0 1037 * 1038 * @param array[] $links Links from pt_parse_header_links(). 1039 * @return string|false The REST API root URL, or false if there is none. 1040 */ 548 1041 function pt_find_rest_endpoint( $links ) { 549 1042 foreach ( $links as $link ) { 550 if ( 'https://api.w.org/' === ifset( $link['rel']) ) {1043 if ( 'https://api.w.org/' === ( $link['rel'] ?? null ) ) { 551 1044 return $link['uri']; 552 1045 } … … 556 1049 } 557 1050 558 if ( ! function_exists( 'pt_make_absolute_url' ) ) { 1051 if ( ! function_exists( __NAMESPACE__ . '\\pt_absolute_urls_in_html' ) ) { 1052 /** 1053 * Makes the links and media URLs in an HTML fragment absolute. 1054 * 1055 * Resolves href, src and poster attributes against a base URL, for content 1056 * taken from a document (such as a JSON Feed item) whose own URL differs 1057 * from where it will be shown. 1058 * 1059 * @since 2.0.0 1060 * 1061 * @param string $html HTML fragment. 1062 * @param string $base Base URL. 1063 * @return string The HTML with absolute URLs, or $html unchanged if it has 1064 * none to resolve or $base is not a URL. 1065 */ 1066 function pt_absolute_urls_in_html( $html, $base ) { 1067 if ( ! is_string( $html ) || '' === trim( $html ) || ! wp_http_validate_url( $base ) || ! preg_match( '/\s(href|src|poster)\s*=/i', $html ) ) { 1068 return $html; 1069 } 1070 $doc = pt_load_domdocument( '<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>' . $html . '</body></html>' ); 1071 $body = $doc->getElementsByTagName( 'body' )->item( 0 ); 1072 if ( ! $body ) { 1073 return $html; 1074 } 1075 foreach ( ( new \DOMXPath( $doc ) )->query( '//body//*[@href or @src or @poster]' ) as $element ) { 1076 foreach ( array( 'href', 'src', 'poster' ) as $attribute ) { 1077 $value = $element->getAttribute( $attribute ); 1078 if ( '' !== $value && '#' !== $value[0] ) { 1079 $element->setAttribute( $attribute, pt_make_absolute_url( $value, $base ) ); 1080 } 1081 } 1082 } 1083 $out = ''; 1084 foreach ( $body->childNodes as $node ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- PHP DOM property. 1085 $out .= $doc->saveHTML( $node ); 1086 } 1087 return $out; 1088 } 1089 } 1090 1091 if ( ! function_exists( __NAMESPACE__ . '\\pt_make_absolute_url' ) ) { 1092 /** 1093 * Resolves a relative URL against a base URL. 1094 * 1095 * @since 2.0.0 1096 * 1097 * @param string $maybe_relative_path URL or path to resolve. 1098 * @param string $url Base URL. 1099 * @return string The absolute URL, or $maybe_relative_path unchanged if it is 1100 * already absolute or either value cannot be parsed. 1101 */ 559 1102 function pt_make_absolute_url( $maybe_relative_path, $url ) { 560 1103 if ( empty( $url ) ) { … … 592 1135 } 593 1136 } 594 1137 595 1138 // Start off with the absolute URL path. 596 1139 $path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/'; … … 600 1143 $path = $relative_url_parts['path']; 601 1144 602 // Else it's a relative path.1145 // Else it's a relative path. 603 1146 } elseif ( ! empty( $relative_url_parts['path'] ) ) { 604 1147 // Strip off any file components from the absolute path. … … 627 1170 } 628 1171 629 630 1172 return $absolute_path . '/' . ltrim( $path, '/' ); 631 1173 } -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/lib/html5/HTML5.php
r2351228 r3738687 147 147 * 148 148 * @param string $input 149 * @param array $options150 149 * 151 150 * @return \DOMDocument -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/lib/html5/HTML5/Elements.php
r2110745 r3738687 71 71 */ 72 72 const BLOCK_ONLY_INLINE = 128; 73 74 /** 75 * Elements with optional end tags that cause auto-closing of previous and parent tags, 76 * as example most of the table related tags, see https://www.w3.org/TR/html401/struct/tables.html 77 * Structure is as follows: 78 * TAG-NAME => [PARENT-TAG-NAME-TO-CLOSE1, PARENT-TAG-NAME-TO-CLOSE2, ...]. 79 * 80 * Order is important, after auto-closing one parent with might have to close also their parent. 81 * 82 * @var array<string, string[]> 83 */ 84 public static $optionalEndElementsParentsToClose = array( 85 'tr' => array('td', 'tr'), 86 'td' => array('td', 'th'), 87 'th' => array('td', 'th'), 88 'tfoot' => array('td', 'th', 'tr', 'tbody', 'thead'), 89 'tbody' => array('td', 'th', 'tr', 'thead'), 90 ); 73 91 74 92 /** … … 186 204 'ul' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG 187 205 'var' => 1, 188 'video' => 65, // NORMAL | BLOCK_TAG206 'video' => 1, 189 207 'wbr' => 9, // NORMAL | VOID_TAG 190 208 … … 293 311 'feDisplacementMap' => 1, 294 312 'feDistantLight' => 1, 313 'feDropShadow' => 1, 295 314 'feFlood' => 1, 296 315 'feFuncA' => 1, … … 446 465 'fedisplacementmap' => 'feDisplacementMap', 447 466 'fedistantlight' => 'feDistantLight', 467 'fedropshadow' => 'feDropShadow', 448 468 'feflood' => 'feFlood', 449 469 'fefunca' => 'feFuncA', -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/CharacterReference.php
r2110745 r3738687 49 49 50 50 /** 51 * Given a hex idecimal number, return the UTF-8 character.51 * Given a hexadecimal number, return the UTF-8 character. 52 52 * 53 53 * @param $hexdec -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/DOMTreeBuilder.php
r2623705 r3738687 232 232 * This is used for handling Processor Instructions as they are 233 233 * inserted. If omitted, PI's are inserted directly into the DOM tree. 234 *235 * @param InstructionProcessor $proc236 234 */ 237 235 public function setInstructionProcessor(InstructionProcessor $proc) … … 360 358 } 361 359 360 // some elements as table related tags might have optional end tags that force us to auto close multiple tags 361 // https://www.w3.org/TR/html401/struct/tables.html 362 if ($this->current instanceof \DOMElement && isset(Elements::$optionalEndElementsParentsToClose[$lname])) { 363 foreach (Elements::$optionalEndElementsParentsToClose[$lname] as $parentElName) { 364 if ($this->current instanceof \DOMElement && $this->current->tagName === $parentElName) { 365 $this->autoclose($parentElName); 366 } 367 } 368 } 369 362 370 try { 363 371 $prefix = ($pos = strpos($lname, ':')) ? substr($lname, 0, $pos) : ''; … … 390 398 if ($pushes > 0 && !Elements::isA($name, Elements::VOID_TAG)) { 391 399 // PHP tends to free the memory used by DOM, 392 // to avoid spl_object_ hash collisions whe have to avoid garbage collection of $ele storing it into $pushes400 // to avoid spl_object_id collisions we have to avoid garbage collection of $ele storing it into $pushes 393 401 // see https://bugs.php.net/bug.php?id=67459 394 $this->pushes[spl_object_ hash($ele)] = array($pushes, $ele);402 $this->pushes[spl_object_id($ele)] = array($pushes, $ele); 395 403 } 396 404 … … 514 522 } 515 523 516 $cid = spl_object_ hash($this->current);524 $cid = spl_object_id($this->current); 517 525 518 526 // XXX: HTML has no parent. What do we do, though, -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/StringInputStream.php
r2110745 r3738687 184 184 * @return string The current character. 185 185 */ 186 #[\ReturnTypeWillChange] 186 187 public function current() 187 188 { … … 193 194 * This is part of the Iterator interface. 194 195 */ 196 #[\ReturnTypeWillChange] 195 197 public function next() 196 198 { … … 201 203 * Rewind to the start of the string. 202 204 */ 205 #[\ReturnTypeWillChange] 203 206 public function rewind() 204 207 { … … 211 214 * @return bool Whether the current pointer location is valid. 212 215 */ 216 #[\ReturnTypeWillChange] 213 217 public function valid() 214 218 { … … 325 329 } 326 330 331 #[\ReturnTypeWillChange] 327 332 public function key() 328 333 { -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/Tokenizer.php
r2839509 r3738687 132 132 $tok = $this->scanner->next(); 133 133 134 if ('!' === $tok) { 134 if (false === $tok) { 135 // end of string 136 $this->parseError('Illegal tag opening'); 137 } elseif ('!' === $tok) { 135 138 $this->markupDeclaration(); 136 139 } elseif ('/' === $tok) { … … 138 141 } elseif ('?' === $tok) { 139 142 $this->processingInstruction(); 140 } elseif ( ctype_alpha($tok)) {143 } elseif ($this->is_alpha($tok)) { 141 144 $this->tagName(); 142 145 } else { … … 348 351 // EOF -> parse error 349 352 // -> parse error 350 if (! ctype_alpha($tok)) {353 if (!$this->is_alpha($tok)) { 351 354 $this->parseError("Expected tag name, got '%s'", $tok); 352 355 if ("\0" == $tok || false === $tok) { … … 505 508 506 509 $val = $this->attributeValue(); 507 if ($isValidAttribute ) {510 if ($isValidAttribute && !array_key_exists($name, $attributes)) { 508 511 $attributes[$name] = $val; 509 512 } … … 727 730 if ('!' == $this->scanner->current() && '>' == $this->scanner->peek()) { 728 731 $this->scanner->consume(); // Consume the last '>' 732 729 733 return true; 730 734 } … … 1125 1129 } 1126 1130 1127 // Hex idecimal encoding.1131 // Hexadecimal encoding. 1128 1132 // X[0-9a-fA-F]+; 1129 1133 // x[0-9a-fA-F]+; … … 1195 1199 return '&'; 1196 1200 } 1201 1202 /** 1203 * Checks whether a (single-byte) character is an ASCII letter or not. 1204 * 1205 * @param string $input A single-byte string 1206 * 1207 * @return bool True if it is a letter, False otherwise 1208 */ 1209 protected function is_alpha($input) 1210 { 1211 if (!is_string($input) || 1 !== strlen($input)) { 1212 return false; 1213 } 1214 1215 $code = ord($input); 1216 1217 return ($code >= 97 && $code <= 122) || ($code >= 65 && $code <= 90); 1218 } 1197 1219 } -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/TreeBuildingRules.php
r2110745 r3738687 81 81 case 'tfoot': 82 82 case 'table': // Spec isn't explicit about this, but it's necessary. 83 84 83 return $this->closeIfCurrentMatches($new, $current, array( 85 84 'thead', -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/lib/html5/HTML5/Parser/UTF8Utils.php
r2110745 r3738687 39 39 /** 40 40 * Count the number of characters in a string. 41 * UTF-8 aware. This will try (in order) iconv, MB, libxml,and finally a custom counter.41 * UTF-8 aware. This will try (in order) iconv, MB, and finally a custom counter. 42 42 * 43 43 * @param string $string … … 54 54 if (function_exists('iconv_strlen')) { 55 55 return iconv_strlen($string, 'utf-8'); 56 }57 58 if (function_exists('utf8_decode')) {59 // MPB: Will this work? Won't certain decodes lead to two chars60 // extrapolated out of 2-byte chars?61 return strlen(utf8_decode($string));62 56 } 63 57 -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/lib/html5/HTML5/Serializer/OutputRules.php
r2351228 r3738687 17 17 class OutputRules implements RulesInterface 18 18 { 19 /**20 * Defined in http://www.w3.org/TR/html51/infrastructure.html#html-namespace-0.21 */22 19 const NAMESPACE_HTML = 'http://www.w3.org/1999/xhtml'; 23 20 … … 50 47 51 48 const IM_IN_MATHML = 3; 52 53 /**54 * Used as cache to detect if is available ENT_HTML5.55 *56 * @var bool57 */58 private $hasHTML5 = false;59 49 60 50 protected $traverser; … … 168 158 $this->outputMode = static::IM_IN_HTML; 169 159 $this->out = $output; 170 $this->hasHTML5 = defined('ENT_HTML5');171 160 } 172 161 … … 207 196 } 208 197 198 /** 199 * @param \DOMElement $ele 200 */ 209 201 public function element($ele) 210 202 { … … 228 220 229 221 $this->openTag($ele); 222 // The tag is already self-closed (`<svg />` or `<math />`) in `openTag` if there are no child nodes. 223 $handledAsVoidTag = $this->outputMode !== static::IM_IN_HTML && !$ele->hasChildNodes(); 224 230 225 if (Elements::isA($name, Elements::TEXT_RAW)) { 231 226 foreach ($ele->childNodes as $child) { … … 249 244 250 245 // If not unary, add a closing tag. 251 if (! Elements::isA($name, Elements::VOID_TAG)) {246 if (!$handledAsVoidTag && !Elements::isA($name, Elements::VOID_TAG)) { 252 247 $this->closeTag($ele); 253 248 } … … 449 444 protected function wr($text) 450 445 { 451 fwrite($this->out, $text);446 fwrite($this->out, (string) $text); 452 447 453 448 return $this; … … 484 479 * encoding here will just escape &'<>". 485 480 * 486 * Note, PHP 5.4+ has better html5 encoding.487 *488 * @todo Use the Entities class in php 5.3 to have html5 entities.489 *490 481 * @param string $text Text to encode. 491 * @param bool $attribute True if we are encoding an attr ubute, false otherwise.482 * @param bool $attribute True if we are encoding an attribute, false otherwise. 492 483 * 493 484 * @return string The encoded text. … … 500 491 } 501 492 502 // If we are in PHP 5.4+ we can use the native html5 entity functionality to 503 // convert the named character references. 504 505 if ($this->hasHTML5) { 506 return htmlentities($text, ENT_HTML5 | ENT_SUBSTITUTE | ENT_QUOTES, 'UTF-8', false); 507 } // If a version earlier than 5.4 html5 entities are not entirely handled. 508 // This manually handles them. 509 else { 510 return strtr($text, HTML5Entities::$map); 511 } 493 return htmlentities($text, ENT_HTML5 | ENT_SUBSTITUTE | ENT_QUOTES, 'UTF-8', false); 512 494 } 513 495 -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/lib/mf2/Parser.php
r2839509 r3738687 146 146 147 147 /** 148 * Registered with the XPath object and used within XPaths for finding root elements.149 * @param string $class150 * @return bool151 */152 function classHasMf2RootClassname($class) {153 return count(mfNamesFromClass($class, 'h-')) > 0;154 }155 156 /**157 148 * Get Nested µf Property Name From Class 158 149 * … … 365 356 */ 366 357 public function __construct($input, $url = null, $jsonMode = false) { 367 $emptyDocDefault = '<html><body></body></html>';368 358 libxml_use_internal_errors(true); 369 359 if (is_string($input)) { 370 if (empty($input)) {371 $input = $emptyDocDefault;372 }373 374 360 if (class_exists('Masterminds\\HTML5')) { 375 361 $doc = new \Masterminds\HTML5(array('disable_html_ns' => true)); … … 383 369 } else { 384 370 $doc = new DOMDocument(); 385 @$doc->loadHTML($emptyDocDefault); 386 } 387 388 // Create an XPath object and allow some PHP functions to be used within XPath queries. 371 @$doc->loadHTML(''); 372 } 373 389 374 $this->xpath = new DOMXPath($doc); 390 $this->xpath->registerNamespace('php', 'http://php.net/xpath');391 $this->xpath->registerPhpFunctions('\\Mf2\\classHasMf2RootClassname');392 375 393 376 $baseurl = $url; … … 1182 1165 'properties' => $return 1183 1166 ); 1184 1167 1185 1168 if(trim($e->getAttribute('id')) !== '') { 1186 1169 $parsed['id'] = trim($e->getAttribute("id")); … … 1524 1507 // start with mf2 root class name xpath 1525 1508 $xpaths = array( 1526 ' (php:function("\\Mf2\\classHasMf2RootClassname", normalize-space(@class)))'1509 'contains(concat(" ",normalize-space(@class)), " h-")' 1527 1510 ); 1528 1511 … … 1579 1562 break; 1580 1563 1581 case 'hfeed':1582 $this->upgradeRelTagToCategory($el);1583 break;1584 1585 case 'hproduct':1586 $review_and_hreview_aggregate = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " review ") and contains(concat(" ", normalize-space(@class), " "), " hreview-aggregate ")]', $el);1587 1588 if ( $review_and_hreview_aggregate->length ) {1589 foreach ( $review_and_hreview_aggregate as $tempEl ) {1590 if ( !$this->hasRootMf2($tempEl) ) {1591 $this->backcompat($tempEl, 'hreview-aggregate');1592 $this->addMfClasses($tempEl, 'p-review h-review-aggregate');1593 $this->addUpgraded($tempEl, array('review hreview-aggregate'));1594 }1595 }1596 }1597 1598 $review_and_hreview = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " review ") and contains(concat(" ", normalize-space(@class), " "), " hreview ")]', $el);1599 1600 if ( $review_and_hreview->length ) {1601 foreach ( $review_and_hreview as $tempEl ) {1602 if ( !$this->hasRootMf2($tempEl) ) {1603 $this->backcompat($tempEl, 'hreview');1604 $this->addMfClasses($tempEl, 'p-review h-review');1605 $this->addUpgraded($tempEl, array('review hreview'));1606 }1607 }1608 }1609 1610 break;1611 1612 case 'hreview-aggregate':1613 1564 case 'hreview': 1614 1565 $item_and_vcard = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " item ") and contains(concat(" ", normalize-space(@class), " "), " vcard ")]', $el); … … 1652 1603 1653 1604 case 'vevent': 1654 $location _and_vcard = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " location ") and contains(concat(" ", normalize-space(@class), " "), " vcard")]', $el);1655 1656 if ( $location _and_vcard->length ) {1657 foreach ( $location _and_vcardas $tempEl ) {1605 $location = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " location ")]', $el); 1606 1607 if ( $location->length ) { 1608 foreach ( $location as $tempEl ) { 1658 1609 if ( !$this->hasRootMf2($tempEl) ) { 1659 $this->addMfClasses($tempEl, ' p-locationh-card');1610 $this->addMfClasses($tempEl, 'h-card'); 1660 1611 $this->backcompat($tempEl, 'vcard'); 1661 1612 } … … 1737 1688 public function hasRootMf2(\DOMElement $el) { 1738 1689 $class = str_replace(array("\t", "\n"), ' ', $el->getAttribute('class')); 1739 1740 // Check for valid mf2 root classnames, not just any classname with a h- prefix. 1741 return count(mfNamesFromClass($class, 'h-')) > 0; 1690 $classes = array_filter(explode(' ', $class)); 1691 1692 foreach ( $classes as $classname ) { 1693 if ( strpos($classname, 'h-') === 0 ) { 1694 return true; 1695 } 1696 } 1697 1698 return false; 1742 1699 } 1743 1700 … … 1799 1756 'vevent' => 'h-event', 1800 1757 'hreview' => 'h-review', 1801 'hreview-aggregate' => 'h-review-aggregate',1802 1758 'hproduct' => 'h-product', 1803 1759 'adr' => 'h-adr', 1804 'geo' => 'h-geo'1805 1760 ); 1806 1761 … … 1919 1874 ), 1920 1875 'hfeed' => array( 1921 'author' => array( 1922 'replace' => 'p-author h-card', 1923 'context' => 'vcard' 1924 ), 1925 'url' => array( 1926 'replace' => 'u-url' 1927 ), 1928 'photo' => array( 1929 'replace' => 'u-photo' 1930 ), 1931 'category' => array( 1932 'replace' => 'p-category' 1933 ), 1876 # nothing currently 1934 1877 ), 1935 1878 'hentry' => array( … … 2041 1984 ), 2042 1985 'location' => array( 2043 'replace' => 'p-location', 1986 'replace' => 'h-card', 1987 'context' => 'vcard' 2044 1988 ), 2045 1989 'geo' => array( 2046 1990 'replace' => 'p-location h-geo' 2047 1991 ), 2048 'attendee' => array(2049 'replace' => 'p-attendee h-card',2050 'context' => 'vcard'2051 )2052 1992 ), 2053 1993 'hreview' => array( … … 2085 2025 ), 2086 2026 ), 2087 'hreview-aggregate' => array(2088 'summary' => array(2089 'replace' => 'p-name'2090 ),2091 # fn: see item.fn below2092 # photo: see item.photo below2093 # url: see item.url below2094 'item' => array(2095 'replace' => 'p-item h-item',2096 'context' => 'item'2097 ),2098 'rating' => array(2099 'replace' => 'p-rating'2100 ),2101 'best' => array(2102 'replace' => 'p-best'2103 ),2104 'worst' => array(2105 'replace' => 'p-worst'2106 ),2107 'average' => array(2108 'replace' => 'p-average'2109 ),2110 'count' => array(2111 'replace' => 'p-count'2112 ),2113 'votes' => array(2114 'replace' => 'p-votes'2115 ),2116 ),2117 2027 'hproduct' => array( 2118 2028 'fn' => array( … … 2137 2047 'replace' => 'u-url', 2138 2048 ), 2139 // review is handled in the special processing section to allow for 'review hreview-aggregate' 2049 'review' => array( 2050 'replace' => 'p-review h-review', 2051 ), 2140 2052 'price' => array( 2141 2053 'replace' => 'p-price' -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/parse-this.php
r2507837 r3738687 3 3 * Plugin Name: Parse This 4 4 * Plugin URI: https://github.com/dshanske/parse-this 5 * Description: 6 * Version: 1.0.15 * Description: Turns URLs into structured jf2 data from microformats2, JSON-LD, meta tags and RSS, Atom and JSON feeds. 6 * Version: 2.0.0 7 7 * Author: David Shanske 8 8 * Author URI: https://david.shanske.com 9 * Requires at least: 6.2 10 * Requires PHP: 7.4 11 * License: GPLv2 or later 12 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 13 * Text Domain: parse-this 10 * Domain Path: /languages 14 * 15 * @package Parse_This 11 16 */ 12 17 13 14 /* 15 Parse This Load 16 */ 18 defined( 'ABSPATH' ) || exit; 17 19 18 20 if ( ! function_exists( 'parse_this_loader' ) ) { 21 /** 22 * Loads Parse This. 23 * 24 * Runs on plugins_loaded at priority 9, so that the standalone plugin loads 25 * ahead of copies bundled in other plugins (Post Kinds loads its copy at 26 * priority 11). Functions are guarded by function_exists() and the first 27 * autoloader registered serves the classes, so whichever copy loads first 28 * wins. It also registers the deprecated global names in aliases.php, so 29 * that plugins bundling an older copy use this one. 30 * 31 * Defines PARSE_THIS_VERSION for the copy that loads, and fires 32 * parse_this_loaded once everything is available. A copy older than 2.0.0 33 * defines neither, so a plugin can tell when an older bundled copy won. 34 * 35 * @since 1.0.0 36 * @since 2.0.0 Defines PARSE_THIS_VERSION and fires parse_this_loaded. 37 */ 19 38 function parse_this_loader() { 39 if ( ! defined( 'PARSE_THIS_VERSION' ) ) { 40 /** 41 * Version of the copy of Parse This that loaded. Keep in step with the 42 * Version header. 43 * 44 * @since 2.0.0 45 * @var string 46 */ 47 define( 'PARSE_THIS_VERSION', '2.0.0' ); 48 } 20 49 require_once plugin_dir_path( __FILE__ ) . 'includes/autoload.php'; 50 require_once plugin_dir_path( __FILE__ ) . 'includes/functions.php'; 21 51 22 // Functions Not Available in Earlier Versions of WordPress 23 require_once plugin_dir_path( __FILE__ ) . 'includes/compat-functions.php'; 52 new ParseThis\REST_Endpoint(); 24 53 25 require_once plugin_dir_path( __FILE__ ) . 'includes/functions.php'; 26 // Parse This REST Endpoint 27 require_once plugin_dir_path( __FILE__ ) . 'includes/class-rest-parse-this.php'; 28 54 /** 55 * Fires once Parse This has loaded: its classes, functions and REST route 56 * are available. 57 * 58 * Plugins that use Parse This can hook here, or check PARSE_THIS_VERSION, 59 * rather than depend on load order. 60 * 61 * @since 2.0.0 62 * 63 * @param string $version Version of the copy of Parse This that loaded. 64 */ 65 do_action( 'parse_this_loaded', PARSE_THIS_VERSION ); 29 66 } 30 67 add_action( 'plugins_loaded', 'parse_this_loader', 9 ); 31 68 } 32 -
indieweb-post-kinds/tags/4.0.0/lib/parse-this/readme.txt
r2507837 r3738687 1 1 === Parse This === 2 2 Contributors: dshanske 3 Tags: indieweb 4 Stable tag: trunk5 Requires at least: 4.96 Requires PHP: 5.67 Tested up to: 5.63 Tags: indieweb, microformats, jf2, json-ld, feeds 4 Stable tag: 2.0.0 5 Requires at least: 6.2 6 Requires PHP: 7.4 7 Tested up to: 7.1 8 8 License: GPLv2 or later 9 License URI: http://www.gnu.org/licenses/gpl-2.0.html 10 11 Parse This turns URLs into structured jf2 data 12 13 == Description == 14 15 Parse This is based on a variety of projects including the parsing code from Press This, which was removed from WordPress. 16 17 * It supports parsing from MF2 if present 18 * For sites that are not marked up with Microformats 2(MF2) it will fall back onto parsing JSON-LD, then HTML/OpenGraph/Dublin Core Tags/etc. 19 * It supports parsing of JSONFeed and RSS/Atom feeds 20 * It supports parsing of WordPress REST API endpoints to generate a site feed 21 22 The goal is to produce structured jf2 data that can be used for previewing links as well as feed readers and other options. It is also bundled in the Post Kinds and Yarns Microsub plugins as a library. 23 24 It can be installed as a standalone plugin which will provide the necessary libraries and functionality as well as the REST API endpoint for getting JF2 data from an arbitrary URL or a WordPress Post. 25 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 11 Parse This turns URLs into structured jf2 data. 12 13 == Description == 14 15 Parse This fetches a URL and turns it into [jf2](https://jf2.spec.indieweb.org/), a simple JSON format for posts, people and feeds. The result can be used for link previews, feed readers, replies and likes, and similar features. 16 17 It started from the parsing code in Press This, which was removed from WordPress, and has grown from there. It runs as a standalone plugin that other plugins, such as Post Kinds and Yarns Microsub, can require or bundle. 18 19 It also runs on ClassicPress 2.x. 20 21 = What it parses = 22 23 * **Microformats2.** When a page is marked up with microformats, they are used first: entries, cards, events, reviews (including h-entry reviews with `review-of`), recipes, products and any other type. On a busy page it picks the item the page is about, and it follows the IndieWeb authorship algorithm. 24 * **Other metadata.** Other sources only fill in what microformats don't provide: a site-specific parser for YouTube or X (Twitter), then JSON-LD (articles, events, reviews, products, recipes, media and people) and Open Graph, Dublin Core and other meta tags. If there is still no content, the page's WordPress REST API version is fetched, when the site advertises one. 25 * **Parts of pages.** A link with a fragment (`#comment-12`) is parsed as just that part of the page. A WordPress comment link whose page has no microformats for it is read from the site's REST API as a reply. 26 * **Feeds.** RSS and Atom (through WordPress's SimplePie), JSON Feed 1 and 1.1, jf2 and mf2 JSON, and WordPress REST API post collections, including feeds served with the wrong content type. 27 * **Feed discovery.** It lists the feeds a page offers: alternates, `rel=feed` links, h-feeds and the WordPress REST API. 28 29 On PHP 8.4 and later, HTML is parsed with PHP's own HTML5 parser; on older PHP, with the bundled masterminds/html5 parser. 30 31 = Using it from PHP = 32 33 $parse = new ParseThis\Parser( 'https://example.com/a-post/' ); 34 $result = $parse->fetch(); 35 if ( ! is_wp_error( $result ) ) { 36 $parse->parse( array( 'return' => 'single' ) ); 37 $jf2 = $parse->get(); // jf2 array. 38 $mf2 = $parse->get( 'mf2' ); // Or the same result as mf2. 39 } 40 41 `ParseThis\Parser::parse()` accepts these arguments: 42 43 * `return`: `single` for one item (default) or `feed` for a list of items. 44 * `follow`: fetch and parse external author pages. Default false. 45 * `limit`: maximum number of feed items. Default 150. 46 * `jsonld`: try JSON-LD. Default true. 47 * `html`: fall back to meta tags. Default true. 48 * `references`: move nested objects with a URL (citations, cards, events, ...) into `refs`, as the jf2 spec describes. Default true. 49 * `location`: flatten a nested location into `latitude`, `longitude` and `altitude` properties, with `location` as a plain string. Default false. 50 * `alternate`: use a `rel=alternate` jf2 or mf2 version of the page if it has one. Default false. 51 * `require_content`: whether a summary alone isn't enough, so the page's WordPress REST API version is fetched for full content. Default: true for feeds, false otherwise. 52 * `always_arrays`: always return `category`, `photo`, `video`, `audio`, `syndication`, `like-of`, `repost-of`, `bookmark-of` and `in-reply-to` as arrays, as Microsub does. Default false, which follows jf2: a single value is not wrapped in an array. 53 * `debug`: include the raw data each source was read from (`_meta`, `_jsonld`, `_json`, `_yt`, `_ombed`, `_rest`). Default false. Before 2.0.0 this was added whenever `WP_DEBUG` was on. 54 55 To list a page's feeds instead, use `( new ParseThis\Discovery() )->fetch( $url )`. 56 57 = Output format = 58 59 Results follow [jf2](https://jf2.spec.indieweb.org/), with a few deliberate differences: 60 61 * A feed's entries are in `items`, as in Microsub timelines, rather than the JF2 Feed profile's `children`. 62 * Nested objects with a URL (citations, cards, events, products and any other type) are stored in `refs`, keyed by URL, as XRay does, rather than jf2's `references`. The property holds the URL. Authors stay cards. 63 * Microformats properties that Parse This doesn't know are passed through as they are. 64 * Entries get a `post-type` property from [Post Type Discovery](https://www.w3.org/TR/post-type-discovery/). 65 * Reviews can be an h-review, or an h-entry with `review-of` (a URL, or a nested h-cite, h-card, h-event, h-item or h-product) and `rating`, `best` and `worst`, as proposed in [microformats/h-entry#32](https://github.com/microformats/h-entry/issues/32) and published by Post Kinds. Both, and an h-entry that is also an h-review, get the post type `review`. 66 * `author` is always a card (or a list of cards), never a plain string. 67 * Properties starting with an underscore (`_links`, `_rest`, ...) are internal or debugging data. 68 * A fetched result has `_code`, the HTTP status (or the status the page declares with `<meta http-equiv="Status">`, such as 410 for a deleted post's stub), and `_source_format`, what it was read from: `mf2+html` (microformats), `html` (meta tags only), `mf2+json`, `jf2+json`, `feed+json` (JSON Feed), `xml` (RSS or Atom) or `wordpress` (REST API). After redirects, the document is read from, and relative links resolve against, the address it ended up at. 69 * When a URL can't be parsed, `fetch()` returns a `WP_Error`: `invalid-url`, `not_found` (404), `unauthorized` (401), `forbidden` (403), `http_error` (any other 4xx or 5xx, with the status as `response_code` in the error data), `content-type` (a format Parse This doesn't read), or the request error itself (a timeout, for example). 70 71 Values from fetched pages are sanitized: URL properties only contain `http` and `https` URLs, plain-text properties (`name`, `summary`, `category`, `content`'s `text`) have no HTML tags, and `content`'s `html` is limited to a safe set of tags. Escape values when you output them all the same, as with any remote data. Debugging data (`_jsonld`, `_meta`, ...) is not sanitized. 72 73 = REST API = 74 75 `GET /wp-json/parse-this/1.0/parse?url=https://example.com/` 76 77 * `url` (required): the URL to parse. 78 * `return`, `follow`, `references`, `location`, `require_content`, `always_arrays`, `debug`: as for `parse()` above. 79 * `mf2`: return mf2 instead of jf2. 80 * `discovery`: list the URL's feeds instead of parsing it. 81 * `nocache`: fetch the URL again instead of using a cached result. 82 83 Results are cached for 15 minutes per URL and set of parameters, so pasting the same link again doesn't fetch it again. Requests with `debug` are never cached. 84 85 The endpoint requires the `edit_posts` capability (Contributors and above); the `parse_this_rest_capability` filter changes it. Anyone who can use it can also try it from **Tools > Parse This**. 86 87 = Filters = 88 89 * `pt_rewrite_secure`: the list of domains whose `http://` URLs are upgraded to `https://` before fetching. 90 * `parse_this_img_filters`: an image URL found in a page, after the built-in exclusions (ads, spinners, tracking pixels and so on). Return an empty string to drop it. 91 * `parse_this_url_shorteners`: the hosts treated as link shorteners. Links to them in a summary are expanded to where they redirect; other links are left as they are, without a request. 92 * `parse_this_native_html_parser`: whether to parse HTML with PHP's own HTML5 parser on PHP 8.4 and later. Default true. Return false to use the bundled masterminds/html5 parser instead; the two only differ on invalid markup, where PHP's parser does what browsers do. 93 * `parse_this_max_requests`: how many further requests one parse may make for followed author pages and short-link expansion. Default 10. Receives the URL. 94 * `parse_this_cache_lifetime`: how long REST endpoint results are cached, in seconds. Default 15 minutes. Return 0 to turn caching off. Receives the URL. 95 * `parse_this_rest_api_jf2_type`: the jf2 type for a post read through a site's WordPress REST API. Default `entry`. Receives the REST API post object, which includes its WordPress post type, and the site's REST API root URL. 96 97 = Helper functions = 98 99 All classes and functions are in the `ParseThis` namespace. 100 101 * `ParseThis\mf2_to_jf2()` and `ParseThis\jf2_to_mf2()` convert between the two formats. 102 * `ParseThis\post_type_discovery()` returns the IndieWeb post type (note, article, reply, like, photo, ...) of a jf2 or mf2 entry. 103 * `ParseThis\jf2_references()` and `ParseThis\jf2_location()` apply the `references` and `location` transformations to any jf2 object. 104 105 Every function and class is documented in the source. 106 107 = Upgrading from 1.x = 108 109 Version 2.0.0 moved everything into the `ParseThis` namespace and dropped the `Parse_This_` prefix from class names (for example `Parse_This_MF2` is now `ParseThis\MF2`, and `Parse_This` is `ParseThis\Parser`). The old names that Post Kinds and Yarns use still work for now, as deprecated aliases: 110 111 * Classes: `Parse_This`, `Parse_This_Discovery`, `Parse_This_MF2`, `Parse_This_MF2_Utils` and `REST_Parse_This`. 112 * Functions: `mf2_to_jf2()`, `jf2_to_mf2()`, `post_type_discovery()`, `pt_load_domdocument()` and `seconds_to_iso8601()`. 113 114 Other old global names are no longer defined. Please switch to the namespaced names. 115 116 = Using Parse This from another plugin = 117 118 The recommended way is to require the Parse This plugin rather than bundle a copy. On WordPress 6.5 and later, add `Requires Plugins: parse-this` to your plugin's header. On older WordPress and ClassicPress, which don't read that header, check that Parse This is loaded and new enough before using it: 119 120 add_action( 'plugins_loaded', function () { 121 if ( ! defined( 'PARSE_THIS_VERSION' ) || version_compare( PARSE_THIS_VERSION, '2.0.0', '<' ) ) { 122 // Show a notice asking for Parse This 2.0.0 or later, and skip the features that need it. 123 return; 124 } 125 // Use ParseThis\Parser and the rest here. 126 }, 11 ); 127 128 `PARSE_THIS_VERSION` is the version of the copy of Parse This that loaded. It isn't defined by copies older than 2.0.0, so an older copy bundled in another plugin shows up as missing. The `parse_this_loaded` action fires, with the version, once Parse This has loaded. 129 130 = Bundling Parse This in another plugin = 131 132 If you do bundle it, copy the plugin into your plugin (for example under `lib/parse-this/`) and load it only if the standalone plugin hasn't already, from `plugins_loaded` at priority 10 or later: 133 134 add_action( 'plugins_loaded', function () { 135 if ( ! function_exists( 'parse_this_loader' ) ) { 136 require_once __DIR__ . '/lib/parse-this/parse-this.php'; 137 parse_this_loader(); 138 } 139 }, 11 ); 140 141 The standalone plugin loads at priority 9, so when it is active it is used instead of your copy. Every function is also wrapped in `function_exists()` and classes are loaded by an autoloader, so if two copies do load, the first one wins. 142 143 Copies of Parse This 1.x bundled in other plugins keep working alongside 2.0: when the standalone plugin is active, their calls to the old names listed under "Upgrading from 1.x" are served by the standalone plugin. 26 144 27 145 == Frequently Asked Questions == 28 146 147 = What is jf2? = 148 149 jf2 is a JSON format derived from microformats2, but simpler: single values aren't wrapped in arrays, and types drop the `h-` prefix. See the [jf2 specification](https://jf2.spec.indieweb.org/). 150 151 = Why does a page return only a title and a URL? = 152 153 That page has no microformats, JSON-LD or useful meta tags, or it blocks automated requests. The **Tools > Parse This** page shows exactly what was parsed for a URL. 154 155 = Which copy is used when several plugins bundle Parse This? = 156 157 The first one loaded. If the standalone plugin is active, it is that one. See "Bundling Parse This in another plugin" above. 158 159 = Does it work with ClassicPress? = 160 161 Yes. It is tested with ClassicPress 2.7 on PHP 7.4 to 8.3. 162 29 163 == Changelog == 164 165 = 2.0.0 ( 2026-10-03 ) = 166 167 A major update. Please read "Upgrading from 1.x" if another plugin of yours uses Parse This. 168 169 Requirements: 170 171 * Requires PHP 7.4 and WordPress 6.2, or ClassicPress 2.x. Tested up to WordPress 7.1 and PHP 8.5. 172 173 Changes to be aware of: 174 175 * Everything is in the `ParseThis` namespace, and class names drop the `Parse_This_` prefix (`Parse_This` is now `ParseThis\Parser`). The old names Post Kinds and Yarns use still work, as deprecated aliases. 176 * Output: nested objects with a URL move to `refs`; authors are always cards; RSS and Atom items use `post-type` (not `post_type`); unknown microformats properties are kept; and fetched results carry `_code` and `_source_format`. 177 * `fetch()` returns a `WP_Error` for HTTP error pages (`not_found`, `unauthorized`, `forbidden`, `http_error`) instead of parsing them; a 410 Gone page is still parsed. 178 * The REST endpoint and the Tools > Parse This page require the `edit_posts` capability (filterable with `parse_this_rest_capability`), and its parameters are validated. 179 * Raw source data (`_meta`, `_jsonld` and so on) is only included with the new `debug` argument, not whenever `WP_DEBUG` is on. 180 * Plugins that use Parse This should require it rather than bundle it: see "Using Parse This from another plugin". `PARSE_THIS_VERSION` and the `parse_this_loaded` action help with that. 181 * The Instagram parser and the `ifset()` helper are removed. 182 183 New: 184 185 * Microformats always come first; JSON-LD, meta tags and the REST API only fill gaps. The `require_content` argument controls when a summary is enough. 186 * Microformats: choose the item a busy page is about; the full authorship algorithm, with feed entries inheriting the feed's author; reviews as an h-entry with `review-of`, `rating`, `best` and `worst` (as Post Kinds publishes them); complete h-event, h-review and h-recipe parsing; `follow-of`; and every value of repeated properties. 187 * Links with a fragment parse just that element; WordPress comment links fall back to the site's REST API. 188 * JSON-LD: reviews, products, recipes, and Article and Event subtypes, preferred over the WebPage node SEO plugins add. 189 * Feeds and JSON served with the wrong content type are recognized. JSON Feed items get plain text, a `post-type` and absolute URLs. 190 * Discovery finds mf2 JSON alternates, `rel=feed` links, RSS 1.0, multi-value `rel` attributes and JSON Feeds advertised as `application/json`, and follows permanent redirects. 191 * PHP's own HTML5 parser is used on PHP 8.4 and later: several times faster, and it handles invalid markup as browsers do (`parse_this_native_html_parser` turns it off). 192 * The `always_arrays` argument returns Microsub-style arrays. 193 * New filters: `parse_this_rest_api_jf2_type`, `parse_this_url_shorteners`, `parse_this_max_requests`, `parse_this_cache_lifetime`, `parse_this_rest_capability` and `parse_this_native_html_parser`. 194 195 Fixes: 196 197 * More than 50 bugs, many of them fatal errors on PHP 8, across RSS and Atom, the WordPress REST API, JSON-LD, meta tags, microformats, JSON Feed, X (Twitter), YouTube and feed discovery. 198 * Content HTML no longer loses the text before its first tag, and escaped text (`<code>`) no longer turns into markup. 199 * REST API posts are dated from their GMT dates; YouTube pages are read in full; titles that only repeat the content are dropped; names and ratings of "0" are kept; post types follow Post Type Discovery. 200 201 Performance: 202 203 * Feeds are downloaded once rather than twice; only link-shortener URLs in summaries are expanded; REST API tags and site details need far fewer requests; followed author pages are fetched once per request; REST endpoint results are cached for 15 minutes (`nocache` and `parse_this_cache_lifetime`). 204 205 Security: 206 207 * Output from fetched pages is sanitized (URLs limited to http and https, tags stripped from plain text, a safe set of HTML tags in content); one parse makes at most 10 further requests; the debug page sends its nonce in a header; plugin files exit when loaded outside WordPress; the OPML class handles invalid input safely. 208 209 Developers: 210 211 * Core's `fetch_feed()` is used for RSS and Atom; all requests go through `ParseThis\pt_remote_get()`. 212 * The bundled php-mf2 (0.5.0) and masterminds/html5 (2.11.0) are updated, and polyfills WordPress 6.2 provides are removed. 213 * Every function, class and hook is documented, and the plugin is tested on WordPress 6.2, the latest WordPress and ClassicPress 2.7 with PHP 7.4 to 8.5. 30 214 31 215 = 1.0.1 ( 2021-04-02 ) = … … 35 219 = 1.0.0 ( 2020-12-15 ) = 36 220 * First Official Release. Prior to this point it was in a point release. 221 222 == Upgrade Notice == 223 224 = 2.0.0 = 225 A major update: classes and functions move to the ParseThis namespace (the old names Post Kinds and Yarns use still work), the output format changes in places, and the REST endpoint now requires the edit_posts capability. Requires PHP 7.4 and WordPress 6.2. -
indieweb-post-kinds/tags/4.0.0/readme.txt
r3067251 r3738687 2 2 Contributors: dshanske 3 3 Tags: indieweb, interaction, posts, webmention, share, like, scrobble 4 Stable tag: 3.7.35 Requires at least: 4.9.96 Requires PHP: 7. 07 Tested up to: 6.54 Stable tag: 4.0.0 5 Requires at least: 6.2 6 Requires PHP: 7.4 7 Tested up to: 7.1 8 8 License: GPLv2 or later 9 9 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 21 21 Many sites will not need all of the kinds set up. What kinds of posts you wish to publish are specific to your needs. 22 22 23 Post Kinds requires PHP 7.4 or later and WordPress 6.2 or later. It also supports ClassicPress 2.7.3 or later. 24 23 25 24 26 == Screenshots == … … 42 44 43 45 == Upgrade Notice == 46 47 = 4.0.0 = 48 49 Requires PHP 7.4 and WordPress 6.2 or ClassicPress 2.7.3. Response data is now escaped, changing some display helpers; stored post data upgrades in the background. Posts from apps and the REST API now get the Default Kind (Note unless changed), not Article. See the changelog. 44 50 45 51 = 3.7.0 = … … 118 124 either a response to something, such as a URL, or a more passive type of post where you are recording/logging something you did, for 119 125 example, watched a movie. 126 127 When you write a post in the Classic Editor, choose its kind in the Kind box. For kinds that respond to something, fill in the Response Properties box: 128 129 * **URL** - the page you are responding to. When you leave this field, the plugin fetches the page and fills in the other fields from it where it can. You can edit or clear anything it fills in. 130 * **Name** - the title of what you are responding to. 131 * **RSVP** and **Rating** - shown only for the kinds that use them: RSVP for RSVPs, and a rating for reviews, listens, watches, reads, plays and similar. 132 * **Details** - more about what you are responding to: a summary, quote or caption (plain text), the site name, publication or album, when it was published and updated, its tags (separated by semicolons) and a featured image URL. For listens, watches, photos and similar, it also has the duration, or a start and end time. 133 * **Author** - the name, URL and photo of the author or artist. Separate several authors with semicolons. 134 * **Upload or Attach Media** - for photo, video and audio posts, attaches a file from the media library. 135 * **Clear** - empties every field. 136 137 The plugin uses these fields to display the response above your post's content, marked up with [microformats](https://microformats.org/wiki/h-entry) so other sites can read it. 120 138 121 139 = How do I interact with other sites? = … … 203 221 However if you are interested in creating your own there is functionality around it. 204 222 205 register_post_kind(206 'reply',223 register_post_kind( 224 'reply', 207 225 array( 208 'singular_name' => __( 'Reply', 'indieweb-post-kinds' ), // Name for one instance of the kind 209 'name' => __( 'Replies', 'indieweb-post-kinds' ), // General name for the kind plural 210 'verb' => __( 'Replied to', 'indieweb-post-kinds' ), // The string for the verb or action (liked this) 211 'property' => 'in-reply-to', // microformats 2 property 212 'format' => 'link', // Post Format that maps to this 213 'description' => __( 'a reply to content typically on another site', 'indieweb-post-kinds' ), 214 'description-url' => 'http://indieweb.org/reply', 215 'title' => false, // Should this kind have an explicit title 216 'show' => true, // Show in Settings 217 ) 226 'singular_name' => __( 'Reply', 'indieweb-post-kinds' ), // Name for one instance of the kind. 227 'name' => __( 'Replies', 'indieweb-post-kinds' ), // General name for the kind, plural. 228 'verb' => __( 'Replied to', 'indieweb-post-kinds' ), // The verb or action, such as "Liked". 229 'property' => 'in-reply-to', // Microformats 2 property. 230 'format' => 'link', // Post format that maps to this kind. 231 'description' => __( 'a reply to content typically on another site', 'indieweb-post-kinds' ), 232 'description_url' => 'http://indieweb.org/reply', 233 'title' => false, // Whether this kind should have an explicit title. 234 'show' => true, // Whether to show the kind in Settings. 218 235 ) 219 );220 221 Add a function with your kind in the above format, hooking it in the init hook and it will add the Kind to the system.236 ); 237 238 Call it from a function hooked to `init` and it will add the Kind to the system. The [developer notes](https://github.com/dshanske/indieweb-post-kinds/blob/trunk/develop.txt) list the other arguments. 222 239 223 240 = Can I enable one of the Kinds you plan to offer in future? = … … 225 242 `set_post_kind_visibility( $slug, $show = true )` - If you add this function in early on, it will change the visibility of a kind. 226 243 227 = Can I create archives for each kind? = =244 = Can I create archives for each kind? = 228 245 229 246 Post Kinds automatically handles the display of archives of individual types. So to view all the posts marked as "note", for example, one could visit the URL http://www.YOURSITE.COM/kind/note/. 230 247 Simply replace YOURSITE.COM with your particular site name and the particular post kind name to access the others. 231 248 249 To use another word than "kind" in these URLs, for example in your own language, set the Kind base on Settings > Permalinks. Links to the old URLs then stop working, as when the category base changes. 250 232 251 You can also add the date /kind/note/2018/12/24 to see date-based archives. 233 252 234 For archives if you add exclude_kind as a query variable it will exclude specific kinds from the query `?exclude=kind&exclude_terms=note`. You can also do this as /exclude/kind/note,checkin as it accepts multiple values 235 236 There is also a special photo photo included, using ?kind_photos=1 or /photos or /photos/yyyy or /photos/yyyy/mm or /photos/kind/note or any other taxonomy. This will use the photo enhancements introduced in 3.4.0 to only show photos from253 To leave specific kinds out of an archive, use `?exclude=kind&exclude_terms=note` or /exclude/kind/note,checkin, which accepts several kinds. 254 255 There is also a special photo archive, using ?kind_photos=1 or /photos or /photos/yyyy or /photos/yyyy/mm or /photos/kind/note or any other taxonomy. This will use the photo enhancements introduced in 3.4.0 to only show photos from 237 256 all types of posts. 238 257 … … 270 289 == Theme Support == 271 290 272 Post Kinds automatically adds information to `the_content` and `the_excerpt` filter. Being as this is inside the content block, which may or may not be desirable, you may remove these filters as noted 273 below and call `kind_display` directly. This will allow it to appear outside the content block. To remove the automatic display, add the following to your theme. 274 * `add_filter( 'kind_content_display', '__return_false' );` 275 276 The functions `has_post_kind`, `set_post_kind`, and `set_post_kind` will allow you to manipulate the kind settings in a post. `get_post_kind_string` will return the display name of a kind. 277 278 If you want to customize the look of the display, you can create a directory in your theme called `kind_views`, copy the file from the views directory of the plugin, and modify it. This will persist 279 through future plugin updates. 291 Post Kinds works with most themes without changes. It adds the response (what you replied to, liked, listened to and so on) to the post's content and excerpt, and to feeds. 292 293 To place the response somewhere else in your theme, turn off the automatic display and call `kind_display()` in your template where you want it: 294 295 add_filter( 'kind_content_display', '__return_false' ); 296 297 Many kinds have no title. In templates that list posts, `kind_get_the_title()` returns the post's title, or generates one from the kind and what it responds to. 298 299 To change how a kind looks, create a `kind_views` folder in your theme, copy the view from the plugin's `views` folder into it, and edit the copy. It persists through plugin updates, but it doesn't receive fixes: compare it with the plugin's views after upgrading. 300 301 To style responses, target the `.response` wrapper that each view uses, and `.kind-embed` around embedded content. 302 303 The template functions, hooks and view variables are listed in the [developer notes](https://github.com/dshanske/indieweb-post-kinds/blob/trunk/develop.txt). 280 304 281 305 282 306 == Changelog == 283 307 284 * 3.7.3 ( 2024-04-09 ) = 308 = 4.0.0 ( 2026-10-10 ) = 309 * Raise the minimum requirements to PHP 7.4 and WordPress 6.2, and declare ClassicPress 2.7.3 support 310 * The `kind` field in the REST API now has a description and lists the valid kinds; an unknown kind is rejected with `rest_invalid_param` 311 * When the old IndieWeb Taxonomy plugin is active, activation now stops with a translatable message and deactivates Post Kinds itself, instead of a file that isn't a plugin 312 * The Default Kind setting applies to every new post without a kind, including posts from apps, the REST API, Quick Draft and scheduled drafts, which used to get Article. Set Default Kind to Article to keep the old behaviour. A translated site no longer gets a stray copy of the Article kind 313 * Escape response data in views, the editor, widgets and settings; summaries are displayed as plain text 314 * Sanitize input from the editor, the media modal's artist fields, settings and widgets; citation summaries are saved as plain text 315 * An RSVP can now be cleared in the editor 316 * Fix the Response Properties box disappearing for every kind after another box was collapsed while editing a note or article; it can no longer be hidden through Screen Options, since the editor shows it only for kinds that use it 317 * Kind icons can be resized from a theme with any rule, such as `.svg-icon { width: 1rem; height: 1rem; }`. The inline style is gone, the plugin's styles have no specificity, and icons have a default size where the stylesheet doesn't load, such as feed readers 318 * Deleting the plugin removes its settings and scheduled events, on every site of a multisite network. Posts, their kinds and their response data are kept 319 * Several authors are saved as one h-card each, pairing the semicolon-separated names, URLs and photos by position, and every author is displayed. Lists of authors from Micropub or link previews are shown and filled in too. Authors saved by earlier versions are read the same way. An author with a URL but no photo is linked, and clearing the author fields removes the authors 320 * Add a Kind base setting to Settings > Permalinks, to change the `kind` in kind archive URLs such as /kind/like/, and the `kind_base_slug` filter 321 * Fix durations calculated from the start and end times never being shown; `calculate_duration()` now also accepts date objects 322 * Require the edit_posts capability for the post-kinds/1.0/fields REST route; an unknown kind returns a 404 323 * Add a background upgrade that moves and repairs stored citations and records the media in each post; `wp post-kinds upgrade` runs it on demand (`--dry-run`, `--post=<id>`) 324 * Fix citations losing their type when viewed (a checkin lost its h-card, for example) and nesting inside themselves when saved again; the upgrade repairs existing posts 325 * Fix old citations stored as a plain URL being lost when viewed, and a PHP 8 fatal error when MF2_Post read very old response data 326 * Reading a post no longer writes to the database or downloads remote photos 327 * Performance: the media scan on save covers posts only and reads the content once; attachment lookups are cached; the photos archive and the home page kind filter use simpler queries; the settings page only writes kind terms that changed; icons, time zone offsets and permalink kinds are cached; the media fragment script loads only where there is audio or video, deferred 328 * The Kind Post widget prints nothing when it has no posts, instead of an unclosed wrapper 329 * New kind terms are named after the kind rather than its slug 330 * Feed change: The RSS and Atom feeds use WordPress's own templates instead of copies, and other plugins' feed handlers are no longer removed. Posts without a title have an empty title element instead of none, so the Atom feed is valid; turn on "Automatically add the Kind to the Title" to fill it in. Dates are written in UTC. In comment feeds, comments on untitled posts name the post. 331 * Deprecated: The global functions `display_formatted_datetime()`, `divide_datetime()`, `build_datetime()`, `get_datetime_offset()`, `divide_interval()` and `build_interval()`. Use the `Kind_Time` methods of the same names. The global names still work but report a deprecation notice when debugging is on, and they will be removed in the next major release (5.0.0). Custom views copied into a theme may call them, so update those to the `Kind_Time` methods. 332 * Display change: Custom views copied into a theme's `kind_views` folder keep their old code. Copy them again from the plugin's `views` folder, or escape their output. 333 * Display change: Citation summaries are plain text, marked up as `p-summary` instead of `e-summary`. `Kind_View::get_summary()` returns the summary markup. 334 * Display change: `Kind_View::get_hcard()`, `get_cite_title()`, `get_site_name()`, `get_embed()`, `get_url_link()` and `display_duration()` return escaped markup; HTML passed as a name is shown as text. 335 * Display change: `kind_get_the_title()` escapes the content before the `kind_get_the_title_content` filter. 336 * Display change: `Kind_View::rsvp_text()` takes optional URL and name arguments and returns the full sentence. Called with only a type, it still returns a printf-ready string. The RSVP strings have changed, so translations need updating. 337 * Display change: `Kind_View::rating_text()` ignores non-numeric ratings and shows at most 10 stars. 338 * Upgrade change: Published posts with no kind are given one, based on their stored response properties, or Article. If the home page shows only selected kinds, Article is added to them so those posts stay; posts given another kind, such as Like, follow the selection. 339 * Upgrade change: Until the background upgrade reaches them, older posts may be missing from the photos archive; `wp post-kinds upgrade` finishes it at once. 340 * Removed: The `kind_photo_shortcode_exclude` filter, which never ran. 341 * Behaviour change: `MF2_Post::get_images()` no longer imports remote photos into the media library; it returns media library attachments only. 342 * Deprecated: The `MF2_Post` class. Use `Kind_Post`. Creating an `MF2_Post` reports a deprecation notice when debugging is on; the class will be removed in a future major version. 343 344 = 3.7.3 ( 2024-04-09 ) = 285 345 * Ensure widget array is instantiated 286 346 -
indieweb-post-kinds/tags/4.0.0/templates/reply-author.php
r2399112 r3738687 1 <?php 2 /** 3 * Author tab of the Post Kinds metabox. 4 * 5 * Included by reply-metabox.php. 6 * 7 * @package Post_Kinds 8 * @since 3.0.0 9 * 10 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(). 11 */ 12 13 defined( 'ABSPATH' ) || exit; 14 ?> 1 15 <div id="kind-author" class="hide-if-js"> 2 <h4> <?php _e( 'Information on the Author or Artist of the Piece', 'indieweb-post-kinds' ); ?></h4>3 <?php _e( '(Multiple Entries separated by semicolon)', 'indieweb-post-kinds' ); ?><BR />16 <h4> <?php esc_html_e( 'Information on the Author or Artist of the Piece', 'indieweb-post-kinds' ); ?></h4> 17 <?php esc_html_e( '(Multiple Entries separated by semicolon)', 'indieweb-post-kinds' ); ?><BR /> 4 18 <p class="field-row"> 5 19 <label for="cite_author_name" class="three-quarters"> 6 <?php _e( 'Author', 'indieweb-post-kinds' ); ?>7 <input type="text" name="cite_author_name" id="cite_author_name" class="widefat" value="<?php echo $cite['author']['name']; ?>" />20 <?php esc_html_e( 'Author', 'indieweb-post-kinds' ); ?> 21 <input type="text" name="cite_author_name" id="cite_author_name" class="widefat" value="<?php echo esc_attr( $cite['author']['name'] ); ?>" /> 8 22 </label> 9 23 </p> 10 24 <p class="field-row"> 11 25 <label for="cite_author_url" class="three-quarters"> 12 <?php _e( 'Author URL', 'indieweb-post-kinds' ); ?>13 <input type="text" name="cite_author_url" id="cite_author_url" class="widefat" value="<?php echo $cite['author']['url']; ?>" />26 <?php esc_html_e( 'Author URL', 'indieweb-post-kinds' ); ?> 27 <input type="text" name="cite_author_url" id="cite_author_url" class="widefat" value="<?php echo esc_attr( $cite['author']['url'] ); ?>" /> 14 28 </label> 15 29 </p> 16 30 <p class="field-row"> 17 31 <label for="cite_author_photo" class="three-quarters"> 18 <?php _e( 'Author Photo URL', 'indieweb-post-kinds' ); ?>19 <input type="text" name="cite_author_photo" id="cite_author_photo" class="widefat" value="<?php echo $cite['author']['photo']; ?>" />32 <?php esc_html_e( 'Author Photo URL', 'indieweb-post-kinds' ); ?> 33 <input type="text" name="cite_author_photo" id="cite_author_photo" class="widefat" value="<?php echo esc_attr( $cite['author']['photo'] ); ?>" /> 20 34 </label> 21 35 </p> -
indieweb-post-kinds/tags/4.0.0/templates/reply-details.php
r2399112 r3738687 1 <?php 2 /** 3 * Details tab of the Post Kinds metabox. 4 * 5 * Included by reply-metabox.php; includes reply-time.php. 6 * 7 * @package Post_Kinds 8 * @since 3.0.0 9 * 10 * @var Kind_Post $kind_post The post being edited. 11 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(). 12 */ 13 14 defined( 'ABSPATH' ) || exit; 15 ?> 1 16 <div id="kind-details" class="hide-if-js"> 2 <h4><?php_e( 'Information on what you are responding to', 'indieweb-post-kinds' ); ?></h4>17 <h4><?php esc_html_e( 'Information on what you are responding to', 'indieweb-post-kinds' ); ?></h4> 3 18 <label for="cite_summary"> 4 <?php _e( 'Summary/Quote/Caption', 'indieweb-post-kinds' ); ?>5 <textarea name="cite_summary" id="cite_summary" data-role="none" class="widefat"><?php echo $cite['summary']; ?></textarea>19 <?php esc_html_e( 'Summary/Quote/Caption', 'indieweb-post-kinds' ); ?> 20 <textarea name="cite_summary" id="cite_summary" data-role="none" class="widefat"><?php echo esc_textarea( html_entity_decode( wp_strip_all_tags( (string) $cite['summary'] ), ENT_QUOTES, get_bloginfo( 'charset' ) ) ); ?></textarea> 6 21 </label> 7 22 <p class="field-row"> 8 23 <label for="cite_publication" class="three-quarters"> 9 <?php _e( 'Site Name/Publication/Album', 'indieweb-post-kinds' ); ?>10 <input type="text" name="cite_publication" id="cite_publication" class="widefat" value="<?php echo $cite['publication']; ?>" />24 <?php esc_html_e( 'Site Name/Publication/Album', 'indieweb-post-kinds' ); ?> 25 <input type="text" name="cite_publication" id="cite_publication" class="widefat" value="<?php echo esc_attr( $cite['publication'] ); ?>" /> 11 26 </label> 12 27 </p> 13 28 <p class="field-row"> 14 <?php echo Kind_Metabox::kind_the_time( 'cite_published', __( 'Published/Released', 'indieweb-post-kinds' ), $cite['published'], 'published' ); ?>15 <?php echo Kind_Metabox::kind_the_time( 'cite_updated', __( 'Updated', 'indieweb-post-kinds' ), $cite['updated'], 'updated' ); ?>29 <?php echo Kind_Metabox::kind_the_time( 'cite_published', __( 'Published/Released', 'indieweb-post-kinds' ), $cite['published'], 'published' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::kind_the_time() escapes its output. ?> 30 <?php echo Kind_Metabox::kind_the_time( 'cite_updated', __( 'Updated', 'indieweb-post-kinds' ), $cite['updated'], 'updated' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::kind_the_time() escapes its output. ?> 16 31 </p> 17 32 <label for="cite_tags"> 18 <?php _e( 'Tags (semicolon separated)', 'indieweb-post-kinds' ); ?>19 <textarea name="cite_tags" id="cite_tags" data-role="none" class="widefat"><?php echo $cite['category']?></textarea>33 <?php esc_html_e( 'Tags (semicolon separated)', 'indieweb-post-kinds' ); ?> 34 <textarea name="cite_tags" id="cite_tags" data-role="none" class="widefat"><?php echo esc_textarea( $cite['category'] ); ?></textarea> 20 35 </label> 21 36 <p class="field-row"> 22 37 <label for="cite_featured" class="three-quarters"> 23 <?php _e( 'Featured Image', 'indieweb-post-kinds' ); ?>24 <input type="text" name="cite_featured" id="cite_featured" class="widefat" value="<?php echo $cite['featured']; ?>" />38 <?php esc_html_e( 'Featured Image', 'indieweb-post-kinds' ); ?> 39 <input type="text" name="cite_featured" id="cite_featured" class="widefat" value="<?php echo esc_attr( $cite['featured'] ); ?>" /> 25 40 </label> 26 41 </p> -
indieweb-post-kinds/tags/4.0.0/templates/reply-metabox.php
r3001401 r3738687 1 1 <?php 2 /** 3 * Post Kinds metabox for the Classic Editor. 4 * 5 * Loaded by Kind_Metabox::reply_metabox(). It sets up $kind_post, $kind, $type 6 * and $cite, then includes reply-details.php and reply-author.php, which use them. 7 * 8 * @package Post_Kinds 9 * @since 3.0.0 10 */ 11 12 defined( 'ABSPATH' ) || exit; 13 2 14 wp_nonce_field( 'replykind_metabox', 'replykind_metabox_nonce' ); 3 15 $kind_post = new Kind_Post( get_the_ID() ); … … 10 22 } 11 23 12 if ( empty( $cite['url'] ) && array_key_exists( 'kindurl', $_GET ) ) { 13 $cite['url'] = esc_url_raw( $_GET['kindurl'] ); 24 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Prefills the URL on the new post screen; nothing is saved. 25 if ( empty( $cite['url'] ) && isset( $_GET['kindurl'] ) && is_string( $_GET['kindurl'] ) ) { 26 $cite['url'] = sanitize_url( wp_unslash( $_GET['kindurl'] ) ); 14 27 } 28 // phpcs:enable WordPress.Security.NonceVerification.Recommended 15 29 16 30 $attachment = 0; … … 18 32 19 33 if ( in_array( $kind, array( 'audio', 'video', 'photo' ) ) ) { 20 $attachment = attachment_url_to_postid( $cite['url'] );34 $attachment = empty( $cite['url'] ) ? 0 : Kind_Post::get_attachment_id( $cite['url'] ); 21 35 if ( $attachment ) { 22 36 $attachment_post = new Kind_Post( $attachment ); … … 28 42 29 43 ?> 30 <a href="#kind-details" class="show-kind-details button hide-if-no-js"><?php _e( 'Details', 'indieweb-post-kinds' ); ?></a>31 <a href="#kind-author" class="show-kind-author-details button hide-if-no-js"><?php _e( 'Author', 'indieweb-post-kinds' ); ?></a>32 <a id="add-kind-media" class="button hide-if-no-js hidden" href="javascript:;"> Upload or Attach Media</a>33 <button class="clear-kindmeta-button button hide-if-no-js"><?php _e( 'Clear', 'indieweb-post-kinds' ); ?></button>44 <a href="#kind-details" class="show-kind-details button hide-if-no-js"><?php esc_html_e( 'Details', 'indieweb-post-kinds' ); ?></a> 45 <a href="#kind-author" class="show-kind-author-details button hide-if-no-js"><?php esc_html_e( 'Author', 'indieweb-post-kinds' ); ?></a> 46 <a id="add-kind-media" class="button hide-if-no-js hidden" href="javascript:;"><?php esc_html_e( 'Upload or Attach Media', 'indieweb-post-kinds' ); ?></a> 47 <button class="clear-kindmeta-button button hide-if-no-js"><?php esc_html_e( 'Clear', 'indieweb-post-kinds' ); ?></button> 34 48 <p class="field-row"> 35 49 <label for="cite_url" class="three-quarters"> 36 <?php _e( 'URL', 'indieweb-post-kinds' ); ?>37 <input type="text" name="cite_url" id="cite_url" class="widefat" value="<?php echo $cite['url']; ?>" />50 <?php esc_html_e( 'URL', 'indieweb-post-kinds' ); ?> 51 <input type="text" name="cite_url" id="cite_url" class="widefat" value="<?php echo esc_attr( $cite['url'] ); ?>" /> 38 52 </label> 39 53 </p> 40 54 <p class="field-row"> 41 55 <label for="cite_name" class="three-quarters"> 42 <?php _e( 'Name', 'indieweb-post-kinds' ); ?>43 <input type="text" name="cite_name" id="cite_name" class="widefat" value="<?php echo $cite['name']; ?>" />56 <?php esc_html_e( 'Name', 'indieweb-post-kinds' ); ?> 57 <input type="text" name="cite_name" id="cite_name" class="widefat" value="<?php echo esc_attr( $cite['name'] ); ?>" /> 44 58 </label> 45 59 </p> 46 60 <p class="field-row hide-if-js" id="rsvp-option"> 47 <?php echo Kind_Metabox::rsvp_select( $kind_post->get( 'rsvp' ) ); ?>61 <?php echo Kind_Metabox::rsvp_select( $kind_post->get( 'rsvp' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::rsvp_select() escapes its output. ?> 48 62 </p> 49 63 <p class="field-row hide-if-js" id="rating-option"> 50 <?php echo Kind_Metabox::rating_select( $kind_post->get( 'rating' ) ); ?>64 <?php echo Kind_Metabox::rating_select( $kind_post->get( 'rating' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::rating_select() escapes its output. ?> 51 65 </p> 52 66 <p id="kind-media hide-if-no-js"> … … 59 73 } elseif ( wp_attachment_is( 'audio', $attachment ) ) { 60 74 $view = new Kind_Media_View( $attachment, 'audio' ); 61 echo $view->get(); 75 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 62 76 } elseif ( wp_attachment_is( 'video', $attachment ) ) { 63 77 $view = new Kind_Media_View( $attachment, 'video' ); 64 echo $view->get(); 78 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 65 79 } 66 80 } 67 81 ?> 68 82 </div> 69 <input type="hidden" id="cite_media" name="cite_media" value="<?php echo $attachment; ?>" >83 <input type="hidden" id="cite_media" name="cite_media" value="<?php echo absint( $attachment ); ?>" > 70 84 </p> 71 85 -
indieweb-post-kinds/tags/4.0.0/templates/reply-time.php
r2399112 r3738687 1 <?php $duration = divide_interval( $kind_post->get_duration() ); ?> 1 <?php 2 /** 3 * Duration fields of the Post Kinds metabox. 4 * 5 * Included by reply-details.php. 6 * 7 * @package Post_Kinds 8 * @since 3.0.0 9 * 10 * @var Kind_Post $kind_post The post being edited. 11 */ 12 13 defined( 'ABSPATH' ) || exit; 14 15 $duration = Kind_Time::divide_interval( $kind_post->get_duration() ); ?> 2 16 3 17 <div id="kind-time" class="hide-if-js"> 4 <h4><?php _e( 'Duration', 'indieweb-post-kinds' ); ?></h4>18 <h4><?php esc_html_e( 'Duration', 'indieweb-post-kinds' ); ?></h4> 5 19 <p class="field-row"> 6 20 <label for="duration" class="eighth"> 7 <?php _e( 'Years', 'indieweb-post-kinds' ); ?>8 <input type="number" name="duration_years" id="duration_years" size="3" value="<?php echo $duration['Y']; ?>" />21 <?php esc_html_e( 'Years', 'indieweb-post-kinds' ); ?> 22 <input type="number" name="duration_years" id="duration_years" size="3" value="<?php echo absint( $duration['Y'] ); ?>" /> 9 23 </label> 10 24 <label for="duration" class="eighth"> 11 <?php _e( 'Months', 'indieweb-post-kinds' ); ?>12 <input type="number" name="duration_months" id="duration_months" size="2" min="0" max="12" value="<?php echo $duration['M']; ?>" />25 <?php esc_html_e( 'Months', 'indieweb-post-kinds' ); ?> 26 <input type="number" name="duration_months" id="duration_months" size="2" min="0" max="12" value="<?php echo absint( $duration['M'] ); ?>" /> 13 27 </label> 14 28 <label for="duration" class="eighth"> 15 <?php _e( 'Days', 'indieweb-post-kinds' ); ?>16 <input type="number" name="duration_days" id="duration_days" size="2" min="0" max="30" value="<?php echo $duration['D']; ?>" />29 <?php esc_html_e( 'Days', 'indieweb-post-kinds' ); ?> 30 <input type="number" name="duration_days" id="duration_days" size="2" min="0" max="30" value="<?php echo absint( $duration['D'] ); ?>" /> 17 31 </label> 18 32 <p> 19 33 <p class="field-row"> 20 34 <label for="duration" class="eighth"> 21 <?php _e( 'Hours', 'indieweb-post-kinds' ); ?>22 <input type="number" name="duration_hours" id="duration_hours" size="2" min="0" max="12" value="<?php echo $duration['H']; ?>" />35 <?php esc_html_e( 'Hours', 'indieweb-post-kinds' ); ?> 36 <input type="number" name="duration_hours" id="duration_hours" size="2" min="0" max="12" value="<?php echo absint( $duration['H'] ); ?>" /> 23 37 </label> 24 38 <label for="duration" class="eighth"> 25 <?php _e( 'Minutes', 'indieweb-post-kinds' ); ?>26 <input type="number" name="duration_minutes" id="duration_minutes" size="2" min="0" max="60" value="<?php echo $duration['I']; ?>" />39 <?php esc_html_e( 'Minutes', 'indieweb-post-kinds' ); ?> 40 <input type="number" name="duration_minutes" id="duration_minutes" size="2" min="0" max="60" value="<?php echo absint( $duration['I'] ); ?>" /> 27 41 </label> 28 42 <label for="duration" class="eighth"> 29 <?php _e( 'Seconds', 'indieweb-post-kinds' ); ?>30 <input type="number" name="duration_seconds" id="duration_seconds" size="2" min="0" max="60" value="<?php echo $duration['S']; ?>" />43 <?php esc_html_e( 'Seconds', 'indieweb-post-kinds' ); ?> 44 <input type="number" name="duration_seconds" id="duration_seconds" size="2" min="0" max="60" value="<?php echo absint( $duration['S'] ); ?>" /> 31 45 </label> 32 46 </p> 33 <h4> <?php _e( 'Start Time and End Time will be Used to Calculate Duration if Duration Not Set.', 'indieweb-post-kinds' ); ?> </h4>47 <h4> <?php esc_html_e( 'Start Time and End Time will be Used to Calculate Duration if Duration Not Set.', 'indieweb-post-kinds' ); ?> </h4> 34 48 <p class="field-row"> 35 <?php echo Kind_Metabox::kind_the_time( 'mf2_start', __( 'Start Time', 'indieweb-post-kinds' ), $kind_post->get_datetime_property( 'start' ), 'start' );?>36 <?php echo Kind_Metabox::kind_the_time( 'mf2_end', __( 'End Time', 'indieweb-post-kinds' ), $kind_post->get_datetime_property( 'end' ), 'end' );?>37 </p>49 <?php echo Kind_Metabox::kind_the_time( 'mf2_start', __( 'Start Time', 'indieweb-post-kinds' ), $kind_post->get_datetime_property( 'start' ), 'start' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::kind_the_time() escapes its output. ?> 50 <?php echo Kind_Metabox::kind_the_time( 'mf2_end', __( 'End Time', 'indieweb-post-kinds' ), $kind_post->get_datetime_property( 'end' ), 'end' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::kind_the_time() escapes its output. ?> 51 </p> 38 52 </div> -
indieweb-post-kinds/tags/4.0.0/views/kind-article.php
r1828595 r3738687 1 1 <?php 2 /* 3 * Article Template2 /** 3 * Article view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.4.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; -
indieweb-post-kinds/tags/4.0.0/views/kind-audio.php
r2406063 r3738687 1 1 <?php 2 /* 3 * Audio Template2 /** 3 * Audio view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 6 $audios = $kind_post->get_audio(); 7 $duration = null; 22 23 defined( 'ABSPATH' ) || exit; 24 25 $audios = $kind_post->get_audio(); 26 $duration = null; 8 27 $publication = null; 9 28 … … 11 30 if ( 1 === count( $audios ) && 0 !== $audios[0] ) { 12 31 $audio_attachment = new Kind_Post( $audios[0] ); 13 $cite =mf2_to_jf2( $audio_attachment->get_cite() );32 $cite = \ParseThis\mf2_to_jf2( $audio_attachment->get_cite() ); 14 33 if ( ! $cite ) { 15 34 $cite = array(); … … 18 37 $author = Kind_View::get_hcard( $cite['author'] ); 19 38 } else { 20 $author = null;39 $author = null; 21 40 } 22 41 if ( array_key_exists( 'duration', $cite ) ) { … … 30 49 <section class="response"> 31 50 <header> 32 <?php echo Kind_Taxonomy::get_before_kind( 'audio' ); 51 <?php 52 echo Kind_Taxonomy::get_before_kind( 'audio' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 33 53 if ( isset( $cite['name'] ) ) { 34 echo sprintf( '<span>%1s</a>', $cite['name']);54 printf( '<span class="p-name">%1$s</span>', esc_html( $cite['name'] ) ); 35 55 } 36 56 if ( $duration ) { 37 printf( '(%1$s)', $duration ); 57 printf( '(%1$s)', $duration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 38 58 } 39 59 ?> … … 42 62 <?php 43 63 if ( $embed ) { 44 printf( '<blockquote class="e-summary">%1 s</blockquote>', $embed );64 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 45 65 } elseif ( $audios ) { 46 66 $view = new Kind_Media_View( $audios, 'audio' ); 47 echo $view->get(); 67 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 48 68 } -
indieweb-post-kinds/tags/4.0.0/views/kind-bookmark.php
r2467905 r3738687 1 1 <?php 2 /* 3 * Bookmark Template2 /** 3 * Bookmark view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 15 33 <header> 16 34 <?php 17 echo Kind_Taxonomy::get_before_kind( 'bookmark' ); 35 echo Kind_Taxonomy::get_before_kind( 'bookmark' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 18 36 if ( ! $embed ) { 19 if ( ! empty( $cite['url'] ) ) { 20 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $cite['url'], $cite['name'] ); 21 } else { 22 echo sprintf( '<span class="p-name">%1s</span>', esc_html( $cite['name'] ) ); 37 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 38 if ( $author ) { 39 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 23 40 } 24 if ( $author ) { 25 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author; 26 } 27 if ( ! empty( $cite['publication'] ) ) { 28 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', esc_html( $cite['publication'] ) ); 41 $site_name = Kind_View::get_site_name( $cite ); 42 if ( $site_name ) { 43 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 29 44 } 30 45 } … … 33 48 <?php 34 49 if ( ! empty( $embed ) ) { 35 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );36 } else if ( ! empty( $cite['summary'] ) ){37 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );50 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 51 } else { 52 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 38 53 } 39 54 40 // Close Response 55 // Close Response. 41 56 ?> 42 57 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-checkin.php
r2459401 r3738687 1 1 <?php 2 /* 3 * Checkin Template2 /** 3 * Checkin view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 14 32 <header> 15 33 <?php 16 echo Kind_Taxonomy::get_before_kind( 'checkin' ); 34 echo Kind_Taxonomy::get_before_kind( 'checkin' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 17 35 if ( ! $embed ) { 18 36 if ( empty( $cite['name'] ) ) { … … 20 38 } 21 39 if ( ! empty( $url ) ) { 22 echo sprintf( '<a href="%1s" class="u-checkin h-card">%2s</a>', $url, $cite['name']);40 printf( '<a href="%1$s" class="u-checkin h-card">%2$s</a>', esc_url( $url ), esc_html( $cite['name'] ) ); 23 41 } else { 24 echo sprintf( '<span class="h-card p-checkin">%1s</span>', $cite['name']);42 printf( '<span class="h-card p-checkin">%1$s</span>', esc_html( $cite['name'] ) ); 25 43 } 26 44 } … … 30 48 if ( $cite ) { 31 49 if ( $embed ) { 32 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );50 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 33 51 } 34 52 } … … 36 54 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 37 55 $view = new Kind_Media_View( $photos, 'photo' ); 38 echo $view->get(); 56 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 39 57 } 40 // Close Response 58 // Close Response. 41 59 ?> 42 60 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-drink.php
r2459401 r3738687 1 1 <?php 2 /* 3 * Drink Template2 /** 3 * Drink view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 13 31 <header> 14 32 <?php 15 echo Kind_Taxonomy::get_before_kind( 'drink' ); 33 echo Kind_Taxonomy::get_before_kind( 'drink' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 16 34 if ( ! $embed ) { 17 if ( ! empty( $url ) ) { 18 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 19 } else { 20 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 21 } 22 if ( array_key_exists( 'publication', $cite ) ) { 23 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 35 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 36 $site_name = Kind_View::get_site_name( $cite ); 37 if ( $site_name ) { 38 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 24 39 } 25 40 } … … 29 44 if ( $cite ) { 30 45 if ( $embed ) { 31 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );32 } else if ( array_key_exists( 'summary', $cite ) ){33 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );46 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 47 } else { 48 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 34 49 } 35 50 } … … 37 52 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 38 53 $view = new Kind_Media_View( $photos, 'photo' ); 39 echo $view->get(); 54 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 40 55 } 41 // Close Response 56 // Close Response. 42 57 ?> 43 58 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-eat.php
r3001401 r3738687 1 1 <?php 2 /* 3 * Eat Template2 /** 3 * Eat view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 9 27 } 10 28 11 $rating = $kind_post->get( 'rating', true );29 $rating = $kind_post->get( 'rating', true ); 12 30 ?> 13 31 … … 16 34 <header> 17 35 <?php 18 echo Kind_Taxonomy::get_before_kind( 'eat' ); 36 echo Kind_Taxonomy::get_before_kind( 'eat' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 19 37 if ( ! $embed ) { 20 if ( ! empty( $url ) ) { 21 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 22 } else { 23 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 24 } 25 if ( ! empty( $cite['publication'] ) ) { 26 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 38 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 39 $site_name = Kind_View::get_site_name( $cite ); 40 if ( $site_name ) { 41 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 27 42 } 28 43 } … … 32 47 if ( $cite ) { 33 48 if ( $embed ) { 34 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );35 } else if ( array_key_exists( 'summary', $cite ) ){36 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );49 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 50 } else { 51 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 37 52 } 38 53 } 39 54 40 55 if ( $rating ) { 41 echo '<data class="p-rating" value="' . $rating . '">' . sprintf( Kind_View::rating_text( $rating ), $url, $title) . '</data>';42 } 56 echo '<data class="p-rating" value="' . esc_attr( $rating ) . '">' . esc_html( Kind_View::rating_text( $rating ) ) . '</data>'; 57 } 43 58 44 59 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 45 60 $view = new Kind_Media_View( $photos, 'photo' ); 46 echo $view->get(); 61 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 47 62 } 48 // Close Response 63 // Close Response. 49 64 ?> 50 65 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-event.php
r2459401 r3738687 1 1 <?php 2 /* 3 * Event Template2 /** 3 * Event view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 3.2.0 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 ?> … … 10 28 <header> 11 29 <?php 12 echo Kind_Taxonomy::get_before_kind( 'event' ); 13 echo get_the_title();30 echo Kind_Taxonomy::get_before_kind( 'event' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 31 the_title(); 14 32 ?> 15 33 </header> 16 34 <?php 17 35 $start = $kind_post->get( 'start' ); 18 $end = $kind_post->get( 'end' ); 19 $timestring = '<p>%1$s: <time class="%2$s" datetime="%3$s">%4$s</time></p>'; 36 $end = $kind_post->get( 'end' ); 20 37 21 38 if ( $start ) { 22 printf( $timestring, __( 'Start', 'indieweb-post-kinds' ), 'dt-start', $start->format( DATE_W3C ), display_formatted_datetime( $start) );23 } 39 printf( '<p>%1$s: <time class="dt-start" datetime="%2$s">%3$s</time></p>', esc_html__( 'Start', 'indieweb-post-kinds' ), esc_attr( $start->format( DATE_W3C ) ), esc_html( Kind_Time::display_formatted_datetime( $start ) ) ); 40 } 24 41 if ( $end ) { 25 printf( $timestring, __( 'End', 'indieweb-post-kinds' ), 'dt-end', $end->format( DATE_W3C ), display_formatted_datetime( $end) );26 } 42 printf( '<p>%1$s: <time class="dt-end" datetime="%2$s">%3$s</time></p>', esc_html__( 'End', 'indieweb-post-kinds' ), esc_attr( $end->format( DATE_W3C ) ), esc_html( Kind_Time::display_formatted_datetime( $end ) ) ); 43 } 27 44 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 28 45 $view = new Kind_Media_View( $photos, 'photo' ); 29 echo $view->get(); 46 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 30 47 } 31 // Close Response 48 // Close Response. 32 49 ?> 33 50 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-favorite.php
r2455262 r3738687 1 1 <?php 2 /* 3 * Favorite Template2 /** 3 * Favorite view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 14 32 <header> 15 33 <?php 16 echo Kind_Taxonomy::get_before_kind( 'favorite' ); 34 echo Kind_Taxonomy::get_before_kind( 'favorite' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 17 35 if ( ! $embed ) { 18 if ( ! empty( $url ) ) { 19 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 20 } else { 21 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 36 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 37 if ( $author ) { 38 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 22 39 } 23 if ( $author ) { 24 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author; 25 } 26 if ( ! empty( $cite['publication'] ) ) { 27 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 40 $site_name = Kind_View::get_site_name( $cite ); 41 if ( $site_name ) { 42 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 28 43 } 29 44 } … … 33 48 if ( $cite ) { 34 49 if ( $embed ) { 35 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );36 } else if ( array_key_exists( 'summary', $cite ) ){37 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );50 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 51 } else { 52 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 38 53 } 39 54 } 40 55 41 // Close Response 56 // Close Response. 42 57 ?> 43 58 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-issue.php
r2467905 r3738687 1 1 <?php 2 /* 3 * Issue Template2 /** 3 * Issue view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 14 32 <header> 15 33 <?php 16 echo Kind_Taxonomy::get_before_kind( 'issue' ); 34 echo Kind_Taxonomy::get_before_kind( 'issue' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 17 35 if ( ! $embed ) { 18 if ( ! empty( $url ) ) { 19 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 20 } else { 21 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 36 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 37 if ( $author ) { 38 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 22 39 } 23 if ( $author ) { 24 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author; 25 } 26 if ( ! empty( $cite['publication'] ) ) { 27 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 40 $site_name = Kind_View::get_site_name( $cite ); 41 if ( $site_name ) { 42 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 28 43 } 29 44 } … … 33 48 if ( $cite ) { 34 49 if ( $embed ) { 35 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );36 } else if ( array_key_exists( 'summary', $cite ) ){37 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );50 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 51 } else { 52 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 38 53 } 39 54 } 40 55 41 // Close Response 56 // Close Response. 42 57 ?> 43 58 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-itinerary.php
r2387393 r3738687 1 1 <?php 2 /* 3 * Event Template2 /** 3 * Itinerary view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 3.2.0 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 6 22 23 defined( 'ABSPATH' ) || exit; 7 24 8 25 $itineraries = $kind_post->get( 'itinerary', false ); 26 $itineraries = is_array( $itineraries ) ? $itineraries : array(); 9 27 10 foreach( $itineraries as $key => $value ) { 11 $itineraries [ $key ] = mf2_to_jf2( $value ); 28 foreach ( $itineraries as $key => $value ) { 29 $leg = \ParseThis\mf2_to_jf2( $value ); 30 // Each field is shown as text; fill in any that are missing. 31 foreach ( array( 'operator', 'number', 'transit-type', 'origin', 'destination', 'departure', 'arrival' ) as $field ) { 32 $leg[ $field ] = ( isset( $leg[ $field ] ) && is_scalar( $leg[ $field ] ) ) ? (string) $leg[ $field ] : ''; 33 } 34 $itineraries[ $key ] = $leg; 12 35 } 13 36 … … 18 41 <header> 19 42 <?php 20 echo Kind_Taxonomy::get_before_kind( 'itinerary' ); 21 echo get_the_title();43 echo Kind_Taxonomy::get_before_kind( 'itinerary' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 44 the_title(); 22 45 ?> 23 46 </header> 24 47 <?php 25 foreach ( $itineraries as $itinerary ) {26 ?>27 <div class="h-leg p-itinerary">28 <h3><span class="p-operator"><?php echo $itinerary['operator']; ?></span>29 <span class="p-number"><?php echo $itinerary['number']; ?></span></h3>30 <data class="p-transit-type" value="<?php echo $itinerary['transit-type']; ?>">31 <ul>48 foreach ( $itineraries as $itinerary ) { 49 ?> 50 <div class="h-leg p-itinerary"> 51 <h3><span class="p-operator"><?php echo esc_html( $itinerary['operator'] ); ?></span> 52 <span class="p-number"><?php echo esc_html( $itinerary['number'] ); ?></span></h3> 53 <data class="p-transit-type" value="<?php echo esc_attr( $itinerary['transit-type'] ); ?>"></data> 54 <ul> 32 55 <li> 33 <?php _e( 'Departs: ', 'indieweb-post-kinds' ); ?>34 <span class="p-origin"><?php echo $itinerary['origin']; ?></span>35 <time class="dt-departure" datetime="<?php echo $itinerary['departure']; ?>"><?php echo display_formatted_datetime( $itinerary['departure']); ?></time>56 <?php esc_html_e( 'Departs: ', 'indieweb-post-kinds' ); ?> 57 <span class="p-origin"><?php echo esc_html( $itinerary['origin'] ); ?></span> 58 <time class="dt-departure" datetime="<?php echo esc_attr( $itinerary['departure'] ); ?>"><?php echo esc_html( Kind_Time::display_formatted_datetime( $itinerary['departure'] ) ); ?></time> 36 59 </li> 37 60 <li> 38 <?php _e( 'Arrives: ', 'indieweb-post-kinds' ); ?>39 <span class="p-destination"><?php echo $itinerary['destination']; ?></span>40 <time class="dt-arrival" datetime="<?php echo $itinerary['arrival']; ?>"><?php echo display_formatted_datetime( $itinerary['arrival']); ?></time>61 <?php esc_html_e( 'Arrives: ', 'indieweb-post-kinds' ); ?> 62 <span class="p-destination"><?php echo esc_html( $itinerary['destination'] ); ?></span> 63 <time class="dt-arrival" datetime="<?php echo esc_attr( $itinerary['arrival'] ); ?>"><?php echo esc_html( Kind_Time::display_formatted_datetime( $itinerary['arrival'] ) ); ?></time> 41 64 </li> 42 </ul>43 </div>44 <?php65 </ul> 66 </div> 67 <?php 45 68 } 46 69 47 70 48 // Close Response 71 // Close Response. 49 72 ?> 50 73 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-jam.php
r2467905 r3738687 1 1 <?php 2 /* 3 Jam Template2 /** 3 * Jam view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 20 38 <header> 21 39 <?php 22 echo Kind_Taxonomy::get_before_kind( 'jam' ); 40 echo Kind_Taxonomy::get_before_kind( 'jam' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 23 41 if ( ! $embed ) { 24 42 if ( $title ) { 25 echo $title; 43 echo $title; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 26 44 } 27 45 if ( $author ) { 28 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author;46 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 29 47 } 30 48 if ( $site_name ) { 31 echo __( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>';49 echo esc_html__( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 32 50 } 33 51 if ( $duration ) { 34 echo Kind_View::display_duration( $duration ); 52 echo Kind_View::display_duration( $duration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 35 53 } 36 54 } … … 40 58 if ( $cite ) { 41 59 if ( $embed ) { 42 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );43 } else if ( array_key_exists( 'summary', $cite ) ){44 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );60 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 61 } else { 62 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 45 63 } 46 64 } … … 48 66 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 49 67 $view = new Kind_Media_View( $photos, 'photo' ); 50 echo $view->get(); 68 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 51 69 } 52 // Close Response 70 // Close Response. 53 71 ?> 54 72 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-like.php
r2467905 r3738687 1 1 <?php 2 /* 3 * Like Template2 /** 3 * Like view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 15 33 <header> 16 34 <?php 17 echo Kind_Taxonomy::get_before_kind( 'like' ); 35 echo Kind_Taxonomy::get_before_kind( 'like' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 18 36 if ( ! $embed ) { 19 if ( ! empty( $url ) ) { 20 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 21 } else { 22 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 37 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 38 if ( $author ) { 39 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 23 40 } 24 if ( $author ) { 25 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author; 26 } 27 if ( ! empty( $cite['publication'] ) ) { 28 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 41 $site_name = Kind_View::get_site_name( $cite ); 42 if ( $site_name ) { 43 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 29 44 } 30 45 } … … 34 49 if ( $cite ) { 35 50 if ( $embed ) { 36 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );37 } else if ( array_key_exists( 'summary', $cite ) ){38 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );51 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 52 } else { 53 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 39 54 } 40 55 } 41 56 42 // Close Response 57 // Close Response. 43 58 ?> 44 59 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-listen.php
r3001401 r3738687 1 1 <?php 2 /* 3 Listen Template2 /** 3 * Listen view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 11 29 $title = Kind_View::get_cite_title( $cite ); 12 30 $duration = $kind_post->get( 'duration', true ); 13 $rating = $kind_post->get( 'rating', true );31 $rating = $kind_post->get( 'rating', true ); 14 32 if ( ! $duration ) { 15 33 $duration = calculate_duration( $kind_post->get( 'start' ), $kind_post->get( 'end' ) ); … … 22 40 <header> 23 41 <?php 24 echo Kind_Taxonomy::get_before_kind( 'listen' ); 42 echo Kind_Taxonomy::get_before_kind( 'listen' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 25 43 if ( ! $embed ) { 26 44 if ( $title ) { 27 echo $title; 45 echo $title; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 28 46 } 29 47 if ( $author ) { 30 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author;48 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 31 49 } 32 50 if ( $site_name ) { 33 echo __( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>';51 echo esc_html__( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 34 52 } 35 53 if ( $duration ) { 36 echo '(' . Kind_View::display_duration( $duration ) . ')'; 54 echo '(' . Kind_View::display_duration( $duration ) . ')'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 37 55 } 38 56 } … … 42 60 if ( $cite ) { 43 61 if ( $embed ) { 44 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );45 } else if ( array_key_exists( 'summary', $cite ) ){46 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );62 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 63 } else { 64 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 47 65 } 48 66 } 49 67 50 // Close Response 68 // Close Response. 51 69 ?> 52 70 </section> 53 71 54 <?php if ( $rating ) { 55 echo '<data class="p-rating" value="' . $rating . '">' . sprintf( Kind_View::rating_text( $rating ), $url, $title ) . '</data>'; 56 } ?> 72 <?php 73 if ( $rating ) { 74 echo '<data class="p-rating" value="' . esc_attr( $rating ) . '">' . esc_html( Kind_View::rating_text( $rating ) ) . '</data>'; 75 } 76 ?> 57 77 58 78 <?php 59 79 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 60 80 $view = new Kind_Media_View( $photos, 'photo' ); 61 echo $view->get(); 81 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 62 82 } 63 83 ?> -
indieweb-post-kinds/tags/4.0.0/views/kind-note.php
r1828595 r3738687 1 1 <?php 2 /* 3 * Note Template2 /** 3 * Note view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.4.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; -
indieweb-post-kinds/tags/4.0.0/views/kind-photo.php
r2459401 r3738687 1 1 <?php 2 /* 3 * Photo Template2 /** 3 * Photo view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 24 6 25 if ( $photos ) { 7 26 $embed = null; … … 11 30 if ( 1 === count( $photos ) ) { 12 31 $photos_attachment = new Kind_Post( $photos[0] ); 13 $cite = $photos_attachment->get_cite();14 $cite = $photos_attachment->normalize_cite( $cite );15 $author = Kind_View::get_hcard( $cite['author'] );32 $cite = $photos_attachment->get_cite(); 33 $cite = $photos_attachment->normalize_cite( $cite ); 34 $author = Kind_View::get_hcard( $cite['author'] ); 16 35 } 17 36 } … … 21 40 <header> 22 41 <?php 23 echo Kind_Taxonomy::get_before_kind( 'photo' ); 42 echo Kind_Taxonomy::get_before_kind( 'photo' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 24 43 if ( ! empty( $cite['name'] ) ) { 25 echo sprintf( '<span>%1s</span>', $cite['name']);44 printf( '<span class="p-name">%1$s</span>', esc_html( $cite['name'] ) ); 26 45 } 27 46 ?> … … 29 48 <?php 30 49 if ( $embed ) { 31 printf( '<blockquote class="e-summary">%1 s</blockquote>', $embed );50 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 32 51 } elseif ( $photos ) { 33 52 $view = new Kind_Media_View( $photos, 'photo' ); 34 echo $view->get(); 35 } ?> 53 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 54 } 55 ?> 36 56 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-read.php
r3001401 r3738687 1 1 <?php 2 /* 3 * Read Template2 /** 3 * Read view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 9 27 } 10 28 $read = $kind_post->get( 'read-status', true ); 11 $rating = $kind_post->get( 'rating', true );29 $rating = $kind_post->get( 'rating', true ); 12 30 13 31 ?> … … 15 33 <header> 16 34 <?php 17 echo Kind_Taxonomy::get_before_kind( 'read' ); 35 echo Kind_Taxonomy::get_before_kind( 'read' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 18 36 if ( ! $embed ) { 19 37 if ( $read ) { 20 echo sprintf( ' - <span class="p-read-status">%1s</span>', Kind_View::read_text( $read) );38 printf( ' - <span class="p-read-status">%1$s</span>', esc_html( Kind_View::read_text( $read ) ) ); 21 39 } 22 if ( ! empty( $url ) ) { 23 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 24 } else { 25 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 40 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 41 if ( $author ) { 42 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 26 43 } 27 if ( $author ) { 28 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author; 29 } 30 if ( empty( $cite['publication'] ) ) { 31 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 44 $site_name = Kind_View::get_site_name( $cite ); 45 if ( $site_name ) { 46 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 32 47 } 33 48 } … … 37 52 if ( $cite ) { 38 53 if ( $embed ) { 39 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );40 } else if ( array_key_exists( 'summary', $cite ) && ! empty( $cite['summary'] ) ){41 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );54 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 55 } else { 56 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 42 57 } 43 58 } 44 59 45 60 if ( $rating ) { 46 echo '<data class="p-rating" value="' . $rating . '">' . sprintf( Kind_View::rating_text( $rating ), $url, $title) . '</data>';61 echo '<data class="p-rating" value="' . esc_attr( $rating ) . '">' . esc_html( Kind_View::rating_text( $rating ) ) . '</data>'; 47 62 } 48 63 … … 50 65 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 51 66 $view = new Kind_Media_View( $photos, 'photo' ); 52 echo $view->get(); 67 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 53 68 } 54 // Close Response 69 // Close Response. 55 70 ?> 56 71 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-rsvp.php
r2394107 r3738687 1 1 <?php 2 /* 3 * RSVP Template2 /** 3 * RSVP view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { 8 26 return; 9 27 } 10 $title = isset( $cite['name'] ) ? $cite['name'] : $url;11 $rsvp = $kind_post->get( 'rsvp', true );28 $title = isset( $cite['name'] ) ? $cite['name'] : $url; 29 $rsvp = $kind_post->get( 'rsvp', true ); 12 30 13 31 ?> … … 16 34 <header> 17 35 <?php 18 echo Kind_Taxonomy::get_before_kind( 'rsvp' ); 36 echo Kind_Taxonomy::get_before_kind( 'rsvp' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 19 37 20 38 if ( ! $embed ) { 21 39 if ( $rsvp ) { 22 echo '<data class="p-rsvp" value="' . $rsvp . '">' . sprintf( Kind_View::rsvp_text( $rsvp ), $url, $title ) . '</data>';40 echo '<data class="p-rsvp" value="' . esc_attr( $rsvp ) . '">' . Kind_View::rsvp_text( $rsvp, $url, $title ) . '</data>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::rsvp_text() escapes the URL and name. 23 41 } 24 42 } … … 27 45 <?php 28 46 if ( $embed ) { 29 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );30 } else if ( array_key_exists( 'summary', $cite ) ){31 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );47 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 48 } else { 49 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 32 50 } 33 51 34 // Close Response 52 // Close Response. 35 53 ?> 36 54 </section> -
indieweb-post-kinds/tags/4.0.0/views/kind-video.php
r2459401 r3738687 1 1 <?php 2 /* 3 * Video Template2 /** 3 * Video view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 6 22 7 $videos = $kind_post->get_video(); 23 defined( 'ABSPATH' ) || exit; 24 25 $videos = $kind_post->get_video(); 8 26 $duration = null; 9 27 if ( is_array( $videos ) ) { 10 28 if ( 1 === count( $videos ) && 0 !== $videos[0] ) { 11 29 $video_attachment = new Kind_Post( $videos[0] ); 12 $cite = $video_attachment->get_cite();13 $cite = $video_attachment->normalize_cite( $cite );14 $author = Kind_View::get_hcard( $cite['author'] );15 30 $cite = $video_attachment->get_cite(); 31 $cite = $video_attachment->normalize_cite( $cite ); 32 $author = Kind_View::get_hcard( $cite['author'] ); 33 16 34 if ( array_key_exists( 'duration', $cite ) ) { 17 35 $duration = Kind_View::display_duration( $cite['duration'] ); … … 22 40 } 23 41 $first_photo = null; 24 if ( is_ countable( $photos ) ) {42 if ( is_array( $photos ) && ! empty( $photos ) ) { 25 43 $first_photo = $photos[0]; 26 44 } 27 45 if ( is_array( $cite ) && ! $videos ) { 28 46 if ( ! $embed ) { 29 $view = new Kind_Media_View( $url, 'video' );47 $view = new Kind_Media_View( $url, 'video' ); 30 48 $embed = $view->get(); 31 49 } … … 37 55 <header> 38 56 <?php 39 echo Kind_Taxonomy::get_before_kind( 'video' ); 57 echo Kind_Taxonomy::get_before_kind( 'video' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 40 58 if ( isset( $cite['name'] ) ) { 41 echo sprintf( '<span>%1s</a>', $cite['name']);59 printf( '<span class="p-name">%1$s</span>', esc_html( $cite['name'] ) ); 42 60 } 43 61 44 62 if ( $author ) { 45 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author;63 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 46 64 } 47 65 if ( $duration ) { 48 printf( '(%1$s)', $duration ); 66 printf( '(%1$s)', $duration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 49 67 } 50 68 … … 54 72 <?php 55 73 if ( $embed ) { 56 printf( '<blockquote class="e-summary">%1 s</blockquote>', $embed );74 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 57 75 } elseif ( $videos ) { 58 76 59 77 $poster = wp_get_attachment_image_url( $first_photo, 'full' ); 60 $view = new Kind_Media_View( $videos, 'video' );61 echo $view->get(); 78 $view = new Kind_Media_View( $videos, 'video' ); 79 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 62 80 } 63 81 ?> -
indieweb-post-kinds/tags/4.0.0/views/kind-watch.php
r3001401 r3738687 1 1 <?php 2 /* 3 Watch Template2 /** 3 * Watch view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 11 29 $title = Kind_View::get_cite_title( $cite, $url ); 12 30 $duration = $kind_post->get( 'duration', true ); 13 $rating = $kind_post->get( 'rating', true );31 $rating = $kind_post->get( 'rating', true ); 14 32 if ( ! $duration ) { 15 33 $duration = calculate_duration( $kind_post->get( 'start' ), $kind_post->get( 'end' ) ); … … 21 39 <header> 22 40 <?php 23 echo Kind_Taxonomy::get_before_kind( 'watch' ); 41 echo Kind_Taxonomy::get_before_kind( 'watch' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 24 42 if ( ! $embed ) { 25 43 if ( $title ) { 26 echo $title; 44 echo $title; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 27 45 } 28 46 if ( $author ) { 29 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author;47 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 30 48 } 31 49 if ( $site_name ) { 32 echo __( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>';50 echo esc_html__( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 33 51 } 34 52 if ( $duration ) { 35 echo Kind_View::display_duration( $duration ); 53 echo Kind_View::display_duration( $duration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 36 54 } 37 55 } … … 41 59 if ( $cite ) { 42 60 if ( $embed ) { 43 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );44 } else if ( array_key_exists( 'summary', $cite ) ){45 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );61 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 62 } else { 63 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 46 64 } 47 65 } 48 66 49 // Close Response 67 // Close Response. 50 68 ?> 51 69 </section> 52 70 53 <?php if ( $rating ) { 54 echo '<data class="p-rating" value="' . $rating . '">' . sprintf( Kind_View::rating_text( $rating ), $url, $title ) . '</data>'; 55 } ?> 71 <?php 72 if ( $rating ) { 73 echo '<data class="p-rating" value="' . esc_attr( $rating ) . '">' . esc_html( Kind_View::rating_text( $rating ) ) . '</data>'; 74 } 75 ?> 56 76 57 77 -
indieweb-post-kinds/tags/4.0.0/views/kind.php
r3001401 r3738687 1 1 <?php 2 /* 3 Default Template 4 * The Goal of this Template is to be a general all-purpose model that will be replaced by customization in other templates 2 /** 3 * Default view, used for any kind without its own view. 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.3.0 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 $site_name = Kind_View::get_site_name( $cite ); … … 11 29 $duration = calculate_duration( $kind_post->get( 'start' ), $kind_post->get( 'end' ) ); 12 30 } 13 $rsvp = $kind_post->get( 'rsvp', true );31 $rsvp = $kind_post->get( 'rsvp', true ); 14 32 $rating = $kind_post->get( 'rating', true ); 15 33 … … 18 36 } 19 37 20 // Add in the appropriate type 38 // Add in the appropriate type. 21 39 if ( ! empty( $type ) ) { 22 40 $type = ( empty( $url ) ? 'p-' : 'u-' ) . $type; … … 24 42 ?> 25 43 26 <section class="h-cite response <?php echo $type; ?>">44 <section class="h-cite response <?php echo esc_attr( $type ); ?>"> 27 45 <header> 28 46 <?php 29 echo Kind_Taxonomy::get_before_kind( $kind ); 47 echo Kind_Taxonomy::get_before_kind( $kind ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 30 48 if ( ! $embed ) { 31 49 if ( $title ) { 32 echo $title; 50 echo $title; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 33 51 } 34 52 if ( ! empty( $author ) ) { 35 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author;53 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 36 54 } 37 55 if ( $site_name ) { 38 echo '<em> (' . $site_name . ')</em>'; 56 echo '<em> (' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 39 57 } 40 58 if ( in_array( $kind, array( 'jam', 'listen', 'play', 'read', 'watch', 'audio', 'video' ) ) ) { 41 59 if ( $duration ) { 42 echo ' ' . Kind_View::display_duration( $duration ); 60 echo ' ' . Kind_View::display_duration( $duration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 43 61 } 44 62 } … … 49 67 if ( $cite && is_array( $cite ) ) { 50 68 if ( $embed ) { 51 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );52 } else if ( array_key_exists( 'summary', $cite ) ){53 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );69 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 70 } else { 71 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 54 72 } 55 73 } 56 74 57 // Close Response 75 // Close Response. 58 76 ?> 59 77 </section> 60 78 61 <?php if ( $rsvp && in_array( $kind, array( 'rsvp' ) ) ) { 62 echo 'RSVP <span class="p-rsvp">' . $rsvp . '</span>'; 79 <?php 80 if ( $rsvp && in_array( $kind, array( 'rsvp' ) ) ) { 81 echo esc_html__( 'RSVP', 'indieweb-post-kinds' ) . ' <span class="p-rsvp">' . esc_html( $rsvp ) . '</span>'; 63 82 } 64 83 65 84 if ( $rating ) { 66 echo '<data class="p-rating" value="' . $rating . '">' . sprintf( Kind_View::rating_text( $rating ), $url, $title ) . '</data>'; 67 } ?> 85 echo '<data class="p-rating" value="' . esc_attr( $rating ) . '">' . esc_html( Kind_View::rating_text( $rating ) ) . '</data>'; 86 } 87 ?> 68 88 69 89 <?php -
indieweb-post-kinds/trunk/.gitattributes
r2491232 r3738687 5 5 /tests export-ignore 6 6 /bin export-ignore 7 /node -modules export-ignore7 /node_modules export-ignore 8 8 /sass export-ignore 9 9 … … 17 17 /readme.md export-ignore 18 18 /phpcs.xml export-ignore 19 /phpcs-templates.xml export-ignore 19 20 /phpunit.xml export-ignore 20 /Gruntfile.js export-ignore 21 /gruntlint.js export-ignore 21 /eslint.config.js export-ignore -
indieweb-post-kinds/trunk/css/kind.admin.min.css
r2572705 r3738687 1 .svg-icon svg{fill:currentColor;padding:2px;vertical-align:middle;top:-0.0625em}#kind-all svg{width:1rem;height:1rem;color:gray}#kind-menu svg,#kind-posts svg,#tempus-onthisday svg,#tempus-thisweek svg,.kind-titles svg{width:1.5rem;height:1.5rem;color:#fff;mix-blend-mode:difference}#kind-menu ul,#kind-posts ul,#tempus-onthisday ul,#tempus-thisweek ul,.kind-titles ul{list-style-type:none}.response svg{width:2rem;height:2rem;color:#fff;mix-blend-mode:difference}.response figure{margin:auto}#replybox-meta.is-loading .loading{visibility:visible}#replybox-meta label{display:block}#replybox-meta .field-row{overflow:auto;height:100%}#replybox-meta .half{float:left;width:46%;margin:0 1%}#replybox-meta .half:first-of-type{margin-left:0;width:48%}#replybox-meta .half:last-of-type{margin-right:0;width:49%}#replybox-meta .quarter{float:left;margin-left:2%;width:12%}#replybox-meta .eighth{float:left;margin-left:2%;margin-right:1%;width:6%}#replybox-meta input[type=number]{width:100%}#replybox-meta .three-quarters{float:left;width:85%}#replybox-meta .is-loading .loading{visibility:visible}#replybox-meta .loading{visibility:hidden;position:absolute;top:0;bottom:0;left:0;right:0;z-index:10;background-color:#000;background-color:rgba(0,0,0,.4)}#replybox-meta .loading-spinner{margin:0 auto;display:block;position:relative;top:2em}/*# sourceMappingURL=kind.admin.min.css.map */1 :where(.svg-icon){display:inline-block;width:1em;height:1em;margin-right:.5rem;vertical-align:middle}:where(.svg-icon>span,.svg-icon svg){display:block;width:100%;height:100%}:where(.svg-icon svg){fill:currentColor}:where(#kind-all .svg-icon){width:1rem;height:1rem;color:gray}:where(#kind-menu,#kind-posts,#tempus-onthisday,#tempus-thisweek,.kind-titles) :where(.svg-icon){width:1.5rem;height:1.5rem;color:#fff;mix-blend-mode:difference}#kind-menu ul,#kind-posts ul,#tempus-onthisday ul,#tempus-thisweek ul,.kind-titles ul{list-style-type:none}:where(.response) :where(.svg-icon){width:2rem;height:2rem;color:#fff;mix-blend-mode:difference}.response figure{margin:auto}#replybox-meta.is-loading .loading{visibility:visible}#replybox-meta label{display:block}#replybox-meta .field-row{overflow:auto;height:100%}#replybox-meta .half{float:left;width:46%;margin:0 1%}#replybox-meta .half:first-of-type{margin-left:0;width:48%}#replybox-meta .half:last-of-type{margin-right:0;width:49%}#replybox-meta .quarter{float:left;margin-left:2%;width:12%}#replybox-meta .eighth{float:left;margin-left:2%;margin-right:1%;width:6%}#replybox-meta input[type=number]{width:100%}#replybox-meta .three-quarters{float:left;width:85%}#replybox-meta .is-loading .loading{visibility:visible}#replybox-meta .loading{visibility:hidden;position:absolute;top:0;bottom:0;left:0;right:0;z-index:10;background-color:#000;background-color:rgba(0,0,0,.4)}#replybox-meta .loading-spinner{margin:0 auto;display:block;position:relative;top:2em}/*# sourceMappingURL=kind.admin.min.css.map */ -
indieweb-post-kinds/trunk/css/kind.admin.min.css.map
r2572705 r3738687 1 {"version":3,"sourceRoot":" ","sources":["../sass/_kinds.scss","../sass/_meta-box.scss"],"names":[],"mappings":"AAAA,cACC,kBACA,YACA,sBACA,cAIA,cACC,WACA,YACA,WAKD,2FACC,aACA,cACA,WACA,0BAED,sFACC,qBAKM,cACQ,WACA,YACd,WACc,0BAEf,iBACC,YCnCF,mCACC,mBAIA,qBACC,cAED,0BACC,cACA,YAED,qBACC,WACA,UACA,YAED,mCACC,cACA,UAED,kCACC,eACA,UAED,wBACC,WACA,eACA,UAED,uBACC,WACA,eACA,gBACA,SAED,kCACC,WAED,+BACC,WACA,UAED,oCACC,mBAED,wBACC,kBACA,kBACA,MACA,SACA,OACA,QACA,WACA,sBACA,gCAED,gCACC,cACA,cACA,kBACA","file":"kind.admin.min.css"}1 {"version":3,"sourceRoot":"..\/","sources":["sass\/_kinds.scss","sass\/_meta-box.scss"],"names":[],"mappings":"AAGA,kBACC,qBACA,UACA,WACA,mBACA,sBAGD,qCACC,cACA,WACA,YAGD,sBACC,kBAGD,4BACC,WACA,YACA,WAGD,iGACC,aACA,cACA,WACA,0BAIA,sFACC,qBAIF,oCACC,WACA,YACA,WACA,0BAIA,iBACC,YCjDF,mCACC,mBAIA,qBACC,cAED,0BACC,cACA,YAED,qBACC,WACA,UACA,YAED,mCACC,cACA,UAED,kCACC,eACA,UAED,wBACC,WACA,eACA,UAED,uBACC,WACA,eACA,gBACA,SAED,kCACC,WAED,+BACC,WACA,UAED,oCACC,mBAED,wBACC,kBACA,kBACA,MACA,SACA,OACA,QACA,WACA,sBACA,gCAED,gCACC,cACA,cACA,kBACA","file":"kind.admin.min.css"} -
indieweb-post-kinds/trunk/css/kind.min.css
r2588189 r3738687 1 .svg-icon svg{fill:currentColor;padding:2px;vertical-align:middle;top:-0.0625em}#kind-all svg{width:1rem;height:1rem;color:gray}#kind-menu svg,#kind-posts svg,#tempus-onthisday svg,#tempus-thisweek svg,.kind-titles svg{width:1.5rem;height:1.5rem;color:#fff;mix-blend-mode:difference}#kind-menu ul,#kind-posts ul,#tempus-onthisday ul,#tempus-thisweek ul,.kind-titles ul{list-style-type:none}.response svg{width:2rem;height:2rem;color:#fff;mix-blend-mode:difference}.response figure{margin:auto}.response{margin-bottom:10px;margin-top:10px;margin-left:20px;padding:5px;font-size:16px;border-bottom:inset #d6d6d6}.response .kind-icon{margin-right:3px}.response blockquote{margin-left:25px}.response a:link{text-decoration:none}.response a:hover{text-decoration:none}.response img{padding:0;margin:2px}ul.cites{list-style:none;display:inline}.kind-embeds{text-align:center;position:relative;width:100%;margin-top:5px}.kind-photo-thumbnail{margin-bottom:5px}/*# sourceMappingURL=kind.min.css.map */1 :where(.svg-icon){display:inline-block;width:1em;height:1em;margin-right:.5rem;vertical-align:middle}:where(.svg-icon>span,.svg-icon svg){display:block;width:100%;height:100%}:where(.svg-icon svg){fill:currentColor}:where(#kind-all .svg-icon){width:1rem;height:1rem;color:gray}:where(#kind-menu,#kind-posts,#tempus-onthisday,#tempus-thisweek,.kind-titles) :where(.svg-icon){width:1.5rem;height:1.5rem;color:#fff;mix-blend-mode:difference}#kind-menu ul,#kind-posts ul,#tempus-onthisday ul,#tempus-thisweek ul,.kind-titles ul{list-style-type:none}:where(.response) :where(.svg-icon){width:2rem;height:2rem;color:#fff;mix-blend-mode:difference}.response figure{margin:auto}.response{margin-bottom:10px;margin-top:10px;margin-left:20px;padding:5px;font-size:16px;border-bottom:inset #d6d6d6}.response .kind-icon{margin-right:3px}.response blockquote{margin-left:25px}.response a:link{text-decoration:none}.response a:hover{text-decoration:none}.response img{padding:0;margin:2px}ul.cites{list-style:none;display:inline}.kind-embeds{text-align:center;position:relative;width:100%;margin-top:5px}.kind-photo-thumbnail{margin-bottom:5px}/*# sourceMappingURL=kind.min.css.map */ -
indieweb-post-kinds/trunk/css/kind.min.css.map
r2588189 r3738687 1 {"version":3,"sourceRoot":" ","sources":["../sass/_kinds.scss","../sass/_frontend.scss"],"names":[],"mappings":"AAAA,cACC,kBACA,YACA,sBACA,cAIA,cACC,WACA,YACA,WAKD,2FACC,aACA,cACA,WACA,0BAED,sFACC,qBAKM,cACQ,WACA,YACd,WACc,0BAEf,iBACC,YCnCF,UACI,mBACA,gBACA,iBACA,YACA,eACA,4BACF,qBACC,iBAED,qBACC,iBAGF,iBACC,qBAGD,kBACC,qBAGD,cACE,UACA,WAIH,SACI,gBACA,eAGJ,aACI,kBACA,kBACA,WACA,eAGJ,sBACI","file":"kind.min.css"}1 {"version":3,"sourceRoot":"..\/","sources":["sass\/_kinds.scss","sass\/_frontend.scss"],"names":[],"mappings":"AAGA,kBACC,qBACA,UACA,WACA,mBACA,sBAGD,qCACC,cACA,WACA,YAGD,sBACC,kBAGD,4BACC,WACA,YACA,WAGD,iGACC,aACA,cACA,WACA,0BAIA,sFACC,qBAIF,oCACC,WACA,YACA,WACA,0BAIA,iBACC,YCjDF,UACI,mBACA,gBACA,iBACA,YACA,eACA,4BACF,qBACC,iBAED,qBACC,iBAGF,iBACC,qBAGD,kBACC,qBAGD,cACE,UACA,WAIH,SACI,gBACA,eAGJ,aACI,kBACA,kBACA,WACA,eAGJ,sBACI","file":"kind.min.css"} -
indieweb-post-kinds/trunk/develop.txt
r1787314 r3738687 1 == Functions == 1 Developer notes for Post Kinds 4.0.0. Every function, method and hook listed here has a docblock in the code with its full parameters and return values. 2 2 3 `get_post_kind_slug($id)` - Return the kind slug for a given post. If `$id` is not specified, use current post. 4 `get_post_kind($id)` - Return the kind string for a given post. If `$id` is not specified, use current post. 3 == Template functions == 5 4 6 `has_post_kind($kind, $post)` - Returns true/false if kind is in post. If post is empty, then use current post 5 These global functions are loaded on `plugins_loaded`, from `includes/kind-functions.php`. 7 6 8 `get_kind_context_class( $class, $classtype )` - Returns the CSS class to be applied to the response/context if the kind is one for which there is context. Classtype defaults to u, other option is usually p. Sets the class to the kind slug and for specially specified slugs, sets appropriate mf2 classes as well. $class specifies any additional classes to be added. 7 `get_post_kind_slug( $post = null )` - Returns the kind slug of a post, or false if it has no kind. Defaults to the current post. 8 `get_post_kind( $post = null )` - Returns the translated kind name of a post, or false if it has no kind. Defaults to the current post. 9 `has_post_kind( $kinds = array(), $post = null )` - Returns whether a post has any of the given kinds, or any kind at all if `$kinds` is empty. 10 `set_post_kind( $post, $kind )` - Gives a post a kind. Returns a WP_Error if the post or the kind is not valid. 11 `get_post_kind_slugs()` - Returns the slugs of every registered kind. 12 `get_post_kind_string( $slug )` - Returns the translated name of a kind, or an empty string if it is not registered. 13 `get_post_kind_link( $kind )` - Returns the URL of a kind's archive. 14 `kind_display( $post_id = null )` - Prints the response markup for a post. Use it with the `kind_content_display` filter to place the markup in the theme. 15 `get_kind_view_part( $slug, $name = null, $args = null )` - Returns a rendered view. See Kind_View::get_view_part(). 16 `kind_get_the_title( $post = null, $args = array() )` - Returns a post's title, or a generated one (the kind's icon or name and a cited name or excerpt) for a post without one. 17 `kind_get_the_link( $post = null, $cls = null, $date_cls = null )` - Returns a link to a post, using kind_get_the_title(), followed by its date. 18 `kind_flatten_array( $a )` - Removes empty values from an array and unwraps single-item lists, recursively. 19 `kind_src_url_in_content( $content )` - Returns every src attribute value in some markup, or 0 if there are none. 9 20 21 == Registering kinds == 10 22 23 `register_post_kind( $slug, $args )` - Registers a kind. Call it on `init`. `includes/register-kinds.php` registers the built-in kinds the same way, and Kind_Taxonomy::register_post_kind() documents every argument. 24 `set_post_kind_visibility( $slug, $show = true )` - Shows or hides a kind in Settings, including the kinds reserved for future use. 11 25 12 == Filters ==26 == Time functions == 13 27 14 `get_the_kind` - Filter get_the_kinds 28 Also global, from `includes/time-functions.php`. 15 29 16 `the_kinds` - Filter get_the_kinds_list 30 `kind_safe_datetime( $value, $timezone = null, $immutable = true )` - Since 4.0.0. Creates a date from a value, returning false instead of throwing on invalid input. 31 `kind_safe_interval( $value )` - Since 4.0.0. Creates a DateInterval from an ISO 8601 duration such as `PT3M30S`, returning false instead of throwing on invalid input. 32 `calculate_duration( $start_string, $end_string )` - Returns the DateInterval between two dates, or false. 33 `date_interval_to_iso8601( $interval )` - Converts a DateInterval to an ISO 8601 duration string. 34 `build_iso8601_time( $date, $time, $offset )` - Joins a date, time and offset into an ISO 8601 string. 35 `build_iso8601_duration( $values )` - Builds an ISO 8601 duration from its pieces (Y, M, D, H, I, S). 36 `get_gmt_offsets()` - Returns the UTC offsets in use by any time zone, sorted. 37 `get_default_offset()` - Returns the site's UTC offset, such as `-05:00`. 38 `tz_seconds_to_offset( $seconds )`, `tz_offset_to_seconds( $offset )` - Convert between a UTC offset in seconds and an offset string. 39 `tz_seconds_to_timezone( $seconds )`, `tz_timezone_to_seconds( $timezone )` - Convert between a UTC offset in seconds and a DateTimeZone. 17 40 18 `kind_classes` - Filter get_kind_class 41 == Classes == 19 42 20 ` kind_verb` - Filter get_kind_verbs43 `Kind_Post` reads and writes a post's response data. Create one with a post ID or WP_Post. Its main methods: 21 44 22 `kind-response-display` - Filters the output being added to the_content or to custom location in theme 45 * `get_kind()`, `get_cite( $key = null )`, `get_author()`, `get_photo()`, `get_audio()`, `get_video()`, `get_duration()`, `get_datetime_property( $property )` and `get( $key )` read data. 46 * `set( $key, $value )`, `set_duration( $value )`, `set_datetime_property( $key, $value )` and `delete( $key )` write it. Response data is stored as post meta prefixed `mf2_`. 47 * `get_cite_type()` - Since 4.0.0. Returns the jf2 type of the post's citation: `card` for a checkin, `food` for eat and drink, and `cite` for every other kind. 48 * `Kind_Post::get_attachment_id( $url )` - Since 4.0.0. Static. A cached wrapper for attachment_url_to_postid(). 49 50 `Kind_Time` - Since 4.0.0. Static helpers for the editor's date, time and duration fields: `display_formatted_datetime()`, `divide_datetime()`, `build_datetime()`, `get_datetime_offset()`, `divide_interval()` and `build_interval()`. 51 52 `Kind_View` builds the response markup. `Kind_View::get_display( $post_id = null )` returns it for a post, `Kind_View::get_view_part( $slug, $name, $args = null )` renders one view, and `Kind_View::get_hcard( $author, $args = null )` returns an author's h-card. Since 4.0.0, its helpers return escaped markup; the 4.0.0 changelog lists the ones that changed. 53 54 `Kind_Media_Metadata::has_media( $post )` - Since 4.0.0. Returns whether a post may show audio or video. The media fragment script loads only for these posts. 55 56 `Kind_Upgrade` - Since 4.0.0. Upgrades stored post data in the background, in batches, after the plugin is updated. It gives published posts without a kind one, moves citations out of older storage locations, repairs citations that earlier versions damaged, and records the media in each post. 57 58 == WP-CLI == 59 60 `wp post-kinds upgrade` - Since 4.0.0. Runs the upgrade now instead of in the background. It is safe to run more than once. 61 62 * `--dry-run` - Report what would change without saving. 63 * `--post=<id>` - Upgrade only this post. 64 65 == REST API == 66 67 * The `kind` field on posts reads and sets a post's kind slug. 68 * `GET /wp-json/post-kinds/1.0/fields?kind={slug}` returns a kind's registration, including its field schema. An unknown kind returns a 404. It requires the `edit_posts` capability. 69 * The editor fetches details for a cited URL from the bundled Parse This library's `parse-this/1.0/parse` route. 70 71 == Filters == 72 73 Display: 74 75 `kind_content_display` - Whether to add response markup to post content, excerpts and feeds automatically. Return false to place it with kind_display() instead. Default true. 76 `kind_response_display` - The response markup for a post. Receives the markup and the post ID. 77 `kind_view_paths` - The folders searched for views before the plugin's `views` folder. Default the theme's `kind_views` folder. 78 `kind_icon_display` - Whether to show the kind's icon or name before the response. Return false to hide it and remove the option from Settings. 79 `post_kinds_icon_attributes` - The HTML attributes on the span that wraps a kind icon. Output as-is, so it must be escaped. Default empty since 4.0.0; before that, an inline style. 80 `kind_get_the_title_content` - The text of a generated title. Receives escaped text and the post. 81 `kind_get_the_title_before` - The markup before a generated title, the kind's icon or name by default. Output as-is, so it must be escaped. 82 `get_hcard_data` - The author data before it becomes an h-card. 83 `get_hcard` - The h-card markup for an author. Return non-empty, escaped markup to replace the plugin's own. 84 `post_kind_embed_approvelist` - The domains whose cited URLs are embedded with oEmbed. 85 `kind_post_type_string` - The phrase for what a known site's URL points to, such as "a video" for YouTube. 86 87 Kinds and archives: 88 89 `kind_include` - The kinds offered in the editor and listed by the Kind Menu widget. Default the kinds selected in Settings, plus note, reply and article. 90 `kind_base_slug` - Since 4.0.0. The start of kind archive URLs, as in `/kind/like/`. Default the Kind base on Settings > Permalinks, or `kind`. 91 `kind_rewrite_taxonomies` - The taxonomies that get `/kind/{kind}/{taxonomy}/{term}/` archives. Default post_tag, category and series. 92 `kind_exclude_slug` - The slug for archives that exclude kinds, as in `/exclude/kind/note,like/`. Default `exclude`. 93 `kind_photos_slug` - The slug for the archive of posts with photos. Default `photos`. 94 `kind_firehose_slug` - The slug for the feed that includes every kind. Default `firehose`. 95 96 After changing a slug or the rewrite taxonomies, flush rewrite rules: visit Settings > Permalinks and save. 97 98 Upgrade: 99 100 `kind_upgrade_batch_size` - Since 4.0.0. The number of posts the background upgrade processes per batch. Default 50. 101 102 Micropub (these run only when the Micropub plugin is active): 103 104 `post_kinds_micropub_enrich` - Since 4.0.0. Whether to fill in citation URLs from Micropub requests with data parsed from the cited page. Return false to store citations exactly as the client sent them. Default true. 105 `post_kinds_micropub_parse_timeout` - Since 4.0.0. The timeout, in seconds, for parsing a citation while the client waits. Citations that time out are retried in the background. Default 5. 106 `post_kinds_micropub_unavailable_retry_delay` - Since 4.0.0. How long to wait before retrying, once, a citation whose page returned 404, 401 or 403. Default 6 hours. 107 `post_kinds_micropub_post_types` - Since 4.0.0. The post types advertised to Micropub clients in `q=config`. 108 109 == Actions == 110 111 `change_kind` - Fires when a post's kind changes. Receives the post ID and the old and new kind slugs; either slug can be an empty string. 112 `post_kinds_citation_enrichment` - Since 4.0.0. Fires after each attempt to parse a Micropub citation. Receives the log entry and the post ID. 113 114 == Deprecated == 115 116 These still work, but report a deprecation notice when WP_DEBUG is on. 117 118 * Since 4.0.0, the global functions `display_formatted_datetime()`, `divide_datetime()`, `build_datetime()`, `get_datetime_offset()`, `divide_interval()` and `build_interval()`. Use the Kind_Time methods of the same names. They will be removed in 5.0.0. 119 * Since 4.0.0, the `MF2_Post` class. Use Kind_Post. It will be removed in a future major version. 120 121 == Styling icons == 122 123 Kind icons are `<span class="svg-icon svg-{kind}">` elements wrapping an SVG. The plugin's stylesheet sizes the span, and the SVG fills it. Its rules use `:where()`, so they have no specificity and any theme rule wins. For example, `.svg-icon { width: 1rem; height: 1rem; }` resizes every icon, and `#kind-menu .svg-icon` only those in the Kind Menu widget. Icons take their colour from the `color` property. 124 125 == Overriding views == 126 127 The markup added to each post comes from a view file in the plugin's `views` folder: `kind-{slug}.php` for a kind (for example `kind-like.php`), or `kind.php` for a kind without its own view. 128 129 To change a view, copy it into a `kind_views` folder in your theme and edit the copy. If a child theme has a `kind_views` folder, it is used instead of the parent theme's; the two aren't combined. For each post, the plugin looks for `kind-{slug}.php` in your theme and then in the plugin, and only then for `kind.php` in the same order. So a theme `kind.php` replaces only the kinds that have no view of their own. The `kind_view_paths` filter changes which folders are searched before the plugin's. 130 131 Copies don't receive updates. After upgrading, compare them with the plugin's views. Since 4.0.0, views escape everything they print, and the `Kind_View` helpers return escaped markup. 132 133 Each view is rendered by `Kind_View::get_view_part()` with these variables in scope: 134 135 * `$kind_post` (Kind_Post) - the post being displayed. 136 * `$kind` (string) - the kind's slug, such as `like`. 137 * `$type` (string) - the kind's microformats property, such as `like-of`. 138 * `$cite` (array) - the citation, normalized by `Kind_Post::normalize_cite()`, so every key is present. `name` falls back to the URL. 139 * `$url` (string) - the cited URL. 140 * `$author` (string|false) - the cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. `$cite['authors']` lists each author as a card with `name`, `url` and `photo`. 141 * `$embed` (string) - embed markup for the cited URL, or an empty string. 142 * `$photos` (int[]|false) - attachment IDs of the post's photos, from `Kind_Post::get_photo()`. 143 * `$args` (array) - the arguments passed to `Kind_View::get_view_part()`; `post_id` is the post's ID. -
indieweb-post-kinds/trunk/includes/class-kind-config.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Post Kind Configuration Class3 * Kind_Config class. 4 4 * 5 * @package Post Kinds6 * Sets Up Configuration Options for the Plugin.5 * @package Post_Kinds 6 * @since 2.0.2 7 7 */ 8 8 9 defined( 'ABSPATH' ) || exit; 10 9 11 /** 10 * Static Class to Configure Admin Options.12 * Registers the plugin settings and builds the settings page. 11 13 * 12 * @ package Post Kinds14 * @since 2.0.2 13 15 */ 14 16 class Kind_Config { … … 16 18 /** 17 19 * Function to Initialize the Configuration. 20 * 21 * @since 2.0.2 18 22 * 19 23 * @access public … … 22 26 add_action( 'admin_init', array( static::class, 'admin_init' ) ); 23 27 add_action( 'admin_menu', array( static::class, 'admin_menu' ), 11 ); 24 // Add post help tab 28 // Add post help tab. 25 29 add_action( 'load-post.php', array( static::class, 'add_post_help_tab' ), 20 ); 26 27 $args = array( 28 'type' => 'array', 29 'description' => 'Kinds Enabled on This Site', 30 'show_in_rest' => false, 31 'default' => array( 'article', 'reply', 'bookmark' ), 30 add_action( 'load-options-permalink.php', array( static::class, 'save_kind_base' ) ); 31 32 $args = array( 33 'type' => 'array', 34 'description' => 'Kinds Enabled on This Site', 35 'show_in_rest' => false, 36 'default' => array( 'article', 'reply', 'bookmark' ), 37 'sanitize_callback' => array( static::class, 'sanitize_kind_list' ), 32 38 ); 33 39 register_setting( 'iwt_options', 'kind_termslist', $args ); 34 40 $args = array( 35 'type' => 'string', 36 'description' => 'Default Kind', 37 'show_in_rest' => false, 38 'default' => 'note', 41 'type' => 'string', 42 'description' => 'Default Kind', 43 'show_in_rest' => false, 44 'default' => 'note', 45 'sanitize_callback' => array( static::class, 'sanitize_default_kind' ), 39 46 ); 40 47 register_setting( 'iwt_options', 'kind_default', $args ); 41 48 $args = array( 42 'type' => 'boolean', 43 'description' => 'Rich Embed Support for Whitelisted Sites', 44 'show_in_rest' => false, 45 'default' => 1, 49 'type' => 'boolean', 50 'description' => 'Rich Embed Support for Whitelisted Sites', 51 'show_in_rest' => false, 52 'default' => 1, 53 'sanitize_callback' => array( static::class, 'sanitize_checkbox' ), 46 54 ); 47 55 register_setting( 'iwt_options', 'kind_embeds', $args ); 48 56 $args = array( 49 'type' => 'array', 50 'description' => 'Kinds Showing in Main Archive', 51 'show_in_rest' => false, 52 'default' => array(), 57 'type' => 'array', 58 'description' => 'Kinds Showing in Main Archive', 59 'show_in_rest' => false, 60 'default' => array(), 61 'sanitize_callback' => array( static::class, 'sanitize_kind_list' ), 53 62 ); 54 63 register_setting( 'iwt_options', 'kind_firehose', $args ); 55 64 56 65 $args = array( 57 'type' => 'boolean', 58 'description' => 'Response Information Should Be After Content', 59 'show_in_rest' => false, 60 'default' => 0, 66 'type' => 'boolean', 67 'description' => 'Response Information Should Be After Content', 68 'show_in_rest' => false, 69 'default' => 0, 70 'sanitize_callback' => array( static::class, 'sanitize_checkbox' ), 61 71 ); 62 72 register_setting( 'iwt_options', 'kind_bottom', $args ); 63 73 $args = array( 64 'type' => 'string', 65 'description' => 'Display Preferences for Before Kind', 66 'show_in_rest' => false, 67 'default' => 'icon', 74 'type' => 'string', 75 'description' => 'Display Preferences for Before Kind', 76 'show_in_rest' => false, 77 'default' => 'icon', 78 'sanitize_callback' => array( static::class, 'sanitize_display' ), 68 79 ); 69 80 register_setting( 'iwt_options', 'kind_display', $args ); 70 81 $args = array( 71 'type' => 'string', 72 'description' => 'KSES Content Protection on Responses', 73 'show_in_rest' => false, 74 'default' => str_replace( '},"', "},\r\n\"", wp_json_encode( wp_kses_allowed_html( 'post' ), 128 ) ), 82 'type' => 'string', 83 'description' => 'KSES Content Protection on Responses', 84 'show_in_rest' => false, 85 'default' => str_replace( '},"', "},\r\n\"", wp_json_encode( wp_kses_allowed_html( 'post' ), 128 ) ), 86 'sanitize_callback' => array( static::class, 'sanitize_kses' ), 75 87 ); 76 88 register_setting( 'iwt_options', 'kind_kses', $args ); 77 89 $args = array( 78 'type' => 'boolean', 79 'description' => 'Automatically add the Kind to the Title', 80 'show_in_rest' => false, 81 'default' => 0, 90 'type' => 'boolean', 91 'description' => 'Automatically add the Kind to the Title', 92 'show_in_rest' => false, 93 'default' => 0, 94 'sanitize_callback' => array( static::class, 'sanitize_checkbox' ), 82 95 ); 83 96 register_setting( 'iwt_options', 'kind_title', $args ); 97 $args = array( 98 'type' => 'string', 99 'description' => 'Kind Base', 100 'show_in_rest' => false, 101 'default' => '', 102 'sanitize_callback' => array( static::class, 'sanitize_kind_base' ), 103 ); 104 register_setting( 'permalink', 'kind_base', $args ); 105 } 106 107 /** 108 * Sanitizes the kind_base setting. 109 * 110 * @since 4.0.0 111 * 112 * @param mixed $value Submitted value. 113 * @return string The base, such as 'type' or 'blog/type', or an empty string for the default. 114 */ 115 public static function sanitize_kind_base( $value ) { 116 if ( ! is_string( $value ) ) { 117 return ''; 118 } 119 $segments = array_filter( array_map( 'sanitize_title', explode( '/', $value ) ) ); 120 $base = implode( '/', $segments ); 121 return 'kind' === $base ? '' : $base; 122 } 123 124 /** 125 * Saves the Kind base field on Settings > Permalinks. 126 * 127 * Core's permalink page doesn't save fields that plugins add, so this runs 128 * before it, with its nonce. Core flushes the rewrite rules when the page 129 * reloads. 130 * 131 * @since 4.0.0 132 */ 133 public static function save_kind_base() { 134 if ( ! isset( $_POST['kind_base'] ) || ! is_string( $_POST['kind_base'] ) || ! current_user_can( 'manage_options' ) ) { 135 return; 136 } 137 check_admin_referer( 'update-permalink' ); 138 // Sanitized by sanitize_kind_base(), the setting's sanitize callback. 139 update_option( 'kind_base', wp_unslash( $_POST['kind_base'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 140 } 141 142 /** 143 * Outputs the Kind base field on Settings > Permalinks. 144 * 145 * @since 4.0.0 146 */ 147 public static function kind_base_callback() { 148 printf( 149 '<input name="kind_base" id="kind_base" type="text" value="%1$s" class="regular-text code" placeholder="kind" />', 150 esc_attr( get_option( 'kind_base', '' ) ) 151 ); 152 } 153 154 /** 155 * Sanitizes a list of kinds: the kind_termslist and kind_firehose settings. 156 * 157 * @since 4.0.0 158 * 159 * @param mixed $value Submitted value. 160 * @return string[] Registered kind slugs. 161 */ 162 public static function sanitize_kind_list( $value ) { 163 if ( ! is_array( $value ) ) { 164 return array(); 165 } 166 $value = array_map( 'sanitize_key', array_filter( $value, 'is_string' ) ); 167 return array_values( array_unique( array_intersect( $value, Kind_Taxonomy::get_kind_list() ) ) ); 168 } 169 170 /** 171 * Sanitizes the default kind setting. 172 * 173 * @since 4.0.0 174 * 175 * @param mixed $value Submitted value. 176 * @return string A registered kind slug, or note. 177 */ 178 public static function sanitize_default_kind( $value ) { 179 $value = is_string( $value ) ? sanitize_key( $value ) : ''; 180 return in_array( $value, Kind_Taxonomy::get_kind_list(), true ) ? $value : 'note'; 181 } 182 183 /** 184 * Sanitizes the display setting for the text before a kind. 185 * 186 * @since 4.0.0 187 * 188 * @param mixed $value Submitted value. 189 * @return string icon, text, both or hide. Defaults to icon. 190 */ 191 public static function sanitize_display( $value ) { 192 return in_array( $value, array( 'icon', 'text', 'both', 'hide' ), true ) ? $value : 'icon'; 193 } 194 195 /** 196 * Sanitizes a checkbox setting. 197 * 198 * @since 4.0.0 199 * 200 * @param mixed $value Submitted value. 201 * @return int 1 if checked, otherwise 0. 202 */ 203 public static function sanitize_checkbox( $value ) { 204 return empty( $value ) ? 0 : 1; 205 } 206 207 /** 208 * Sanitizes the allowed HTML setting, a JSON object as wp_kses() expects. 209 * 210 * Invalid JSON is rejected and the previous value kept. 211 * 212 * @since 4.0.0 213 * 214 * @param mixed $value Submitted value. 215 * @return string JSON-encoded allowed HTML. 216 */ 217 public static function sanitize_kses( $value ) { 218 $decoded = is_string( $value ) ? json_decode( $value, true ) : null; 219 if ( is_array( $decoded ) ) { 220 return $value; 221 } 222 // The settings error API is only loaded in the admin. 223 if ( function_exists( 'add_settings_error' ) ) { 224 add_settings_error( 'kind_kses', 'kind_kses_invalid', __( 'The allowed HTML must be a valid JSON object. The previous value has been kept.', 'indieweb-post-kinds' ) ); 225 } 226 $previous = get_option( 'kind_kses' ); 227 return is_string( $previous ) ? $previous : ''; 228 } 229 230 /** 231 * Returns the kinds enabled in settings (the kind_termslist option). 232 * 233 * Always returns an array of registered kind slugs, whatever is stored: the 234 * option can be missing, false or hold kinds that are no longer registered. 235 * Note, which is always available, is not included unless it was saved. 236 * 237 * @since 4.0.0 238 * 239 * @return string[] Kind slugs. 240 */ 241 public static function get_termslist() { 242 $termslist = get_option( 'kind_termslist' ); 243 if ( ! is_array( $termslist ) ) { 244 return array(); 245 } 246 return array_values( array_intersect( $termslist, Kind_Taxonomy::get_kind_list() ) ); 84 247 } 85 248 86 249 /** 87 250 * Function to Set up Settings. 251 * 252 * @since 2.0.2 88 253 * 89 254 * @access public … … 92 257 add_action( 'admin_bar_menu', array( static::class, 'dashbar_links' ), 20 ); 93 258 add_action( 'admin_bar_menu', array( static::class, 'remove_dashbar_post' ), 200 ); 259 add_settings_field( 260 'kind_base', 261 __( 'Kind base', 'indieweb-post-kinds' ), 262 array( static::class, 'kind_base_callback' ), 263 'permalink', 264 'optional', 265 array( 'label_for' => 'kind_base' ) 266 ); 94 267 add_settings_section( 95 268 'iwt-content', … … 189 362 ) 190 363 ); 191 // Add Query Var to Admin 364 // Add Query Var to Admin. 192 365 add_filter( 'query_vars', array( static::class, 'query_var' ) ); 193 366 } … … 195 368 /** 196 369 * Function to add our kindurl query var. 370 * 371 * @since 2.3.3 197 372 * 198 373 * @access public … … 209 384 * Function to remove "post" from the "New" admin bar section. 210 385 * 211 * @access public 212 * 213 * @param WP_Admin_Bar $wp_admin_bar 386 * @since 3.0.7 387 * 388 * @access public 389 * 390 * @param WP_Admin_Bar $wp_admin_bar The admin bar. 214 391 */ 215 392 public static function remove_dashbar_post( $wp_admin_bar ) { … … 220 397 * Function to add our Post Kind post links to the "New" admin bar section. 221 398 * 222 * @access public 223 * @param WP_Admin_Bar $wp_admin_bar 399 * @since 3.0.7 400 * 401 * @access public 402 * 403 * @param WP_Admin_Bar $wp_admin_bar The admin bar. 224 404 */ 225 405 public static function dashbar_links( $wp_admin_bar ) { 226 $termslist = get_option( 'kind_termslist');227 // Note can never be removed 406 $termslist = self::get_termslist(); 407 // Note can never be removed. 228 408 array_unshift( $termslist, 'note' ); 229 foreach ( $termslistas $term ) {409 foreach ( array_unique( $termslist ) as $term ) { 230 410 $wp_admin_bar->add_menu( 231 411 array( … … 242 422 * Adds Options Page for Plugin Options. 243 423 * 424 * @since 2.0.2 425 * 244 426 * @access public 245 427 */ … … 249 431 add_submenu_page( 250 432 'indieweb', 251 __( 'Post Kinds', 'indieweb-post-kinds' ), // page title 252 __( 'Post Kinds', 'indieweb-post-kinds' ), // menu title 253 'manage_options', // access capability 433 __( 'Post Kinds', 'indieweb-post-kinds' ), // page title. 434 __( 'Post Kinds', 'indieweb-post-kinds' ), // menu title. 435 'manage_options', // access capability. 254 436 'kind_options', 255 437 array( static::class, 'options_form' ) … … 263 445 * Callback for Options on Options Page. 264 446 * 447 * @since 2.0.2 448 * 265 449 * @access public 266 450 */ … … 270 454 /** 271 455 * Generate a Checkbox. 456 * 457 * @since 2.0.2 272 458 * 273 459 * @access public … … 280 466 $option = get_option( $args['name'] ); 281 467 $checked = intval( $option ?? 0 ); 282 printf( '<input name="%1$s" type="hidden" value="0" />', esc_attr( $args['name'] ) ); // phpcs:ignore283 printf( '<input name="%1$s" type="checkbox" value="1" %2$s />', esc_attr( $args['name'] ), checked( 1, $checked, false ) ); // phpcs:ignore468 printf( '<input name="%1$s" type="hidden" value="0" />', esc_attr( $args['name'] ) ); 469 printf( '<input name="%1$s" type="checkbox" value="1" %2$s />', esc_attr( $args['name'] ), checked( 1, $checked, false ) ); 284 470 } 285 471 286 472 /** 287 473 * Generate a Textbox. 474 * 475 * @since 2.0.2 288 476 * 289 477 * @access public … … 296 484 $option = get_option( $args['name'] ); 297 485 if ( is_array( $option ) ) { 298 $option = print_r( $option, true);299 } 300 echo "<textarea rows='10' cols='50' class='large-text code' name='" . esc_attr( $args['name'] ) . "'>" . $option . '</textarea> '; // phpcs:ignore486 $option = wp_json_encode( $option, JSON_PRETTY_PRINT ); 487 } 488 echo "<textarea rows='10' cols='50' class='large-text code' name='" . esc_attr( $args['name'] ) . "'>" . esc_textarea( (string) $option ) . '</textarea> '; 301 489 } 302 490 303 491 /** 304 492 * Generate a Term List. 493 * 494 * @since 2.5.0 305 495 * 306 496 * @access public … … 309 499 $terms = Kind_Taxonomy::get_kind_list(); 310 500 // Hide these terms until ready for use for now. 311 $termslist = get_option( 'kind_termslist');501 $termslist = self::get_termslist(); 312 502 echo '<div id="kind-all">'; 313 503 foreach ( $terms as $term ) { 314 504 $value = Kind_Taxonomy::get_post_kind_info( $term ); 315 505 if ( $value->show ) { 316 printf( '<input name="kind_termslist[]" type="checkbox" value="%1$s" %2$s />', esc_attr( $term ), checked( in_array( $term, $termslist, true ), true, false ) ); // phpcs:ignore317 printf( '%1$s<strong>%2$s</strong> - %3$s<br />', Kind_Taxonomy::get_icon( $term ), sanitize_text_field( $value->singular_name ), sanitize_text_field( $value->description ) ); // phpcs:ignore506 printf( '<input name="kind_termslist[]" type="checkbox" value="%1$s" %2$s />', esc_attr( $term ), checked( in_array( $term, $termslist, true ), true, false ) ); 507 printf( '%1$s<strong>%2$s</strong> - %3$s<br />', Kind_Taxonomy::get_icon( $term ), esc_html( $value->singular_name ), esc_html( $value->description ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The plugin's own SVG icon markup. 318 508 } 319 509 } … … 322 512 323 513 /** 324 * Generate a Term List. 325 * 326 * @access public 514 * Outputs a multiple select of kinds for a setting. 515 * 516 * @since 3.6.0 517 * 518 * @access public 519 * 520 * @param array $args Field arguments. 'name' is the option name. 327 521 */ 328 522 public static function kindmultiselect_callback( array $args ) { 329 $terms = get_option( 'kind_termslist');523 $terms = self::get_termslist(); 330 524 $terms[] = 'note'; 525 $terms = array_unique( $terms ); 331 526 332 527 $kindlist = get_option( $args['name'] ); … … 339 534 foreach ( $terms as $term ) { 340 535 $value = Kind_Taxonomy::get_post_kind_info( $term ); 341 printf( '<option value="%1$s" %2$s />%3$s</option>', esc_attr($term), selected( in_array( $term, $kindlist ), true, false ), sanitize_text_field( $value->singular_name ) ); // phpcs:ignore536 printf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $term ), selected( in_array( $term, $kindlist, true ), true, false ), esc_html( $value->singular_name ) ); 342 537 } 343 538 echo '</select>'; … … 349 544 * Generate a Kind Select List. 350 545 * 546 * @since 2.3.3 547 * 351 548 * @access public 352 549 */ 353 550 public static function defaultkind_callback() { 354 $terms = get_option( 'kind_termslist');551 $terms = self::get_termslist(); 355 552 $terms[] = 'note'; 553 $terms = array_unique( $terms ); 356 554 sort( $terms, SORT_STRING ); 357 555 … … 361 559 foreach ( $terms as $term ) { 362 560 $value = Kind_Taxonomy::get_post_kind_info( $term ); 363 printf( '<option value="%1$s" %2$s />%3$s</option>', esc_attr($term), selected( $term, $defaultkind, false ), sanitize_text_field( $value->singular_name ) ); // phpcs:ignore561 printf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $term ), selected( $term, $defaultkind, false ), esc_html( $value->singular_name ) ); 364 562 } 365 563 echo '</select>'; … … 368 566 /** 369 567 * Generate Radio Options. 568 * 569 * @since 2.6.6 370 570 * 371 571 * @access public … … 376 576 $display = get_option( 'kind_display' ); 377 577 foreach ( $args['options'] as $key => $value ) { 378 printf( '<input id="%1$s" name="%1$s" type="radio" value="%2$s" class="%3$s" %4$s />%5$s<br />', esc_attr( $args['name'] ), esc_attr( $key ), esc_attr( $args['class'] ), checked( $key, $display, false ), sanitize_text_field( $value ) ); // phpcs:ignore578 printf( '<input id="%1$s" name="%1$s" type="radio" value="%2$s" class="%3$s" %4$s />%5$s<br />', esc_attr( $args['name'] ), esc_attr( $key ), esc_attr( $args['class'] ), checked( $key, $display, false ), esc_html( $value ) ); 379 579 } 380 580 } … … 382 582 /** 383 583 * Generate Options Form. 584 * 585 * @since 2.0.2 384 586 * 385 587 * @access public … … 406 608 /** 407 609 * Function to generate a help tab in the WordPress admin help tab. 610 * 611 * @since 2.3.0 408 612 * 409 613 * @access public -
indieweb-post-kinds/trunk/includes/class-kind-fields.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Kind Fields Class3 * Kind_Fields class. 4 4 * 5 * @package Post Kind 6 * Used to Generate Form Fields for a Post UI 5 * @package Post_Kinds 6 * @since 3.3.4 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Generates form fields for the posting UI from a kind's property schema. 7 13 * 8 * Fields currently supported and their options{9 *datetime10 *duration11 * author - is a representation of an author and consists of name, url, and photo by default 12 * select 13 * options - associative array of values for the select. Key being the value and the value being the description 14 * url 15 * text16 * list 17 * textarea 18 * number 19 * cite - is a representation of an h-cite object 20 * } 14 * Supported field types and their options: 15 * - datetime 16 * - duration 17 * - author: a name, URL and photo by default. 18 * - select: `options` is an associative array of value => label. 19 * - url 20 * - text 21 * - list 22 * - textarea 23 * - number 24 * - cite: an h-cite object. 25 * 26 * @since 3.3.4 21 27 */ 22 23 28 class Kind_Fields { 24 29 30 /** 31 * Returns a list of time zones with translated labels. 32 * 33 * @since 3.3.4 34 * 35 * @return string[] Labels, keyed by time zone identifier. 36 */ 25 37 public static function timezone_list() { 26 38 return array( … … 279 291 } 280 292 293 /** 294 * Returns the UTC offsets in use by any time zone, sorted. 295 * 296 * @since 3.3.4 297 * 298 * @return string[] Offsets such as '-05:00'. 299 */ 281 300 public static function get_offset_list() { 282 301 $o = wp_cache_get( 'kind_offset_list' ); … … 288 307 foreach ( $t_zones as $a ) { 289 308 $datetime = new DateTime( 'now', new DateTimeZone( $a ) ); 290 $o[] = get_datetime_offset( $datetime );309 $o[] = Kind_Time::get_datetime_offset( $datetime ); 291 310 } 292 311 $o = array_unique( $o ); … … 297 316 298 317 /** 299 * Function to render date/time field inputs. 300 * 301 * @access public 302 * 303 * @param string|DateTime $time Date/time value. 304 * @param string $name Property name 305 * @param array $args 306 * @param string|array $class Class to use for fields. 307 * @return string 318 * Renders date, time and offset inputs. 319 * 320 * @since 3.3.4 321 * 322 * @param array $args Field arguments, as returned by validate(): 'name', 'label' and 'class'. 323 * @param DateTime|string|false $datetime Date, or a date string. Default now. 324 * @return string The inputs. 308 325 */ 309 326 public static function field_datetime( $args, $datetime ) { … … 315 332 } 316 333 $return = array(); 317 $time = divide_datetime( $datetime );334 $time = Kind_Time::divide_datetime( $datetime ); 318 335 $return[] = sprintf( '<label for="mf2_%1$s" class="%2$s">%3$s', $args['name'], $args['class'], $args['label'] ); 319 336 $return[] = sprintf( '<input type="date" name="mf2_%1$s_date" id="mf2_%1$s_date" value="%2$s"/>', $args['name'], ( $time['date'] ?? '' ) ); … … 328 345 329 346 /** 330 * Function to render dateinterval field inputs. 331 * 332 * @access public 333 * 334 * @param string|Kind_DateTime $time Date/time value. 335 * @param string $name Property name 336 * @param array $args 337 * @param string|array $class Class to use for fields. 338 * @return string 347 * Renders duration inputs. 348 * 349 * @since 3.3.4 350 * 351 * @param array $args Field arguments, as returned by validate(), including 'pieces'. 352 * @param DateInterval|string $interval Duration, or an ISO 8601 duration. Default 0. 353 * @return string The inputs. 339 354 */ 340 355 public static function field_duration( $args, $interval ) { … … 346 361 } 347 362 $return = array(); 348 $duration = divide_interval( $interval );363 $duration = Kind_Time::divide_interval( $interval ); 349 364 $max = array( 350 365 'Y' => 1000, … … 364 379 365 380 /** 366 * Function to render author inputs. 367 * 368 * @access public 369 * 370 * @param string $prefix Field prefix. 371 * @param string|array $author Defautl values. If string, considered to be the name property below. { 372 * @param string $name Author Name 373 * @param string $url Author URL 374 * @param string $photo Author Photo 375 * } 376 * @return string 381 * Renders author name, URL and photo inputs. 382 * 383 * @since 3.3.4 384 * 385 * @param array $args Field arguments, as returned by validate(). 386 * @param array|false $author Values, with 'name', 'url' and 'photo' keys. 387 * @return string The inputs. 377 388 */ 378 389 public static function field_author( $args, $author ) { … … 394 405 ), 395 406 ); 396 // Ensure all props are set for values 407 // Ensure all props are set for values. 397 408 foreach ( array_keys( $props ) as $prop ) { 398 409 if ( ! array_key_exists( $prop, $author ) ) { … … 405 416 406 417 /** 407 * Function to render a select.408 * 409 * @ access public410 * 411 * @param string $name412 * @param string $selected Selected field type.413 * @return string 418 * Renders a select. The options are in $args['options']. 419 * 420 * @since 3.3.4 421 * 422 * @param array $args Field arguments, as returned by validate(). 423 * @param string $selected Value of the selected option. 424 * @return string The input. 414 425 */ 415 426 public static function field_select( $args, $selected ) { … … 426 437 427 438 /** 428 * Function to render a url.429 * 430 * @ access public431 * 432 * @param string $name433 * @param string $url 434 * @return string 439 * Renders a URL input. 440 * 441 * @since 3.3.4 442 * 443 * @param array $args Field arguments, as returned by validate(). 444 * @param string $url Value. 445 * @return string The input. 435 446 */ 436 447 public static function field_url( $args, $url ) { … … 443 454 444 455 /** 445 * Function to render a number.446 * 447 * @ access public448 * 449 * @param string $name450 * @param string $value 451 * @return string 456 * Renders a number input. The step is in $args['step']. 457 * 458 * @since 3.3.4 459 * 460 * @param array $args Field arguments, as returned by validate(). 461 * @param string $value Value. 462 * @return string The input. 452 463 */ 453 464 public static function field_number( $args, $value ) { … … 460 471 461 472 /** 462 * Function to render a text.463 * 464 * @ access public465 * 466 * @param string $name467 * @param string $value 468 * @return string 473 * Renders a text input. 474 * 475 * @since 3.3.4 476 * 477 * @param array $args Field arguments, as returned by validate(). 478 * @param string $value Value. 479 * @return string The input. 469 480 */ 470 481 public static function field_text( $args, $value ) { … … 477 488 478 489 /** 479 * Function to rendera textarea.480 * 481 * @ access public482 * 483 * @param string $name484 * @param string $value 485 * @return string 490 * Renders a textarea. 491 * 492 * @since 3.3.4 493 * 494 * @param array $args Field arguments, as returned by validate(). 495 * @param string $value Value. 496 * @return string The input. 486 497 */ 487 498 public static function field_textarea( $args, $value ) { … … 493 504 } 494 505 506 /** 507 * Renders a textarea for a list, with items separated by semicolons. 508 * 509 * @since 3.3.4 510 * 511 * @param array $args Field arguments, as returned by validate(). 512 * @param string|array $value Value, or a list of values. 513 * @return string The input. 514 */ 495 515 public static function field_list( $args, $value ) { 496 516 if ( is_array( $value ) ) { … … 500 520 } 501 521 522 /** 523 * Renders the inputs for a venue's properties. 524 * 525 * @since 3.3.4 526 * 527 * @param array $args Field arguments, as returned by validate(), including 'properties'. 528 * @param array $value Values, keyed by property. 529 * @return string The inputs. 530 */ 502 531 public static function field_venue( $args, $value ) { 503 532 return self::render( $args['properties'], $value ); 504 533 } 505 534 535 /** 536 * Renders the inputs for a citation's properties. 537 * 538 * @since 3.3.4 539 * 540 * @param array $args Field arguments, as returned by validate(), including 'properties'. 541 * @param array $value Values, keyed by property. 542 * @return string The inputs. 543 */ 506 544 public static function field_cite( $args, $value ) { 507 545 return self::render( $args['properties'], $value ); 508 546 } 509 547 548 /** 549 * Checks whether a string starts with a prefix. 550 * 551 * @since 3.3.4 552 * 553 * @param string $source The source string. 554 * @param string $prefix The prefix to look for. 555 * @return bool True if the source starts with the prefix. 556 */ 510 557 public static function str_prefix( $source, $prefix ) { 511 558 return strncmp( $source, $prefix, strlen( $prefix ) ) === 0; 512 559 } 513 560 561 /** 562 * Returns a value from an array. 563 * 564 * @since 3.3.4 565 * 566 * @param string $key Key. 567 * @param array $array The array. 568 * @return mixed The value, or false if it is not set. 569 */ 514 570 private static function get( $key, $array ) { 515 571 if ( ! is_array( $array ) ) { … … 522 578 } 523 579 580 /** 581 * Checks a field definition and fills in defaults. 582 * 583 * @since 3.3.4 584 * 585 * @param array $element Field definition. 586 * @return array|false The field definition, or false if it is not valid. 587 */ 524 588 private static function validate( $element ) { 525 // Everything must have a type 589 // Everything must have a type. 526 590 if ( ! self::get( 'type', $element ) ) { 527 591 return false; 528 592 } 529 // Everything must have a label 593 // Everything must have a label. 530 594 if ( ! self::get( 'label', $element ) ) { 531 595 return false; 532 596 } 533 // If no name property then the label property copies over 597 // If no name property then the label property copies over. 534 598 if ( ! self::get( 'name', $element ) ) { 535 599 $element['name'] = sanitize_title( $element['label'] ); 536 600 } 537 // If no label property then the name property copies over 601 // If no label property then the name property copies over. 538 602 if ( ! self::get( 'label', $element ) ) { 539 603 $element['label'] = $element['name']; 540 604 } 541 // If no class property then the class property is set to empty 605 // If no class property then the class property is set to empty. 542 606 if ( ! self::get( 'class', $element ) ) { 543 607 $element['class'] = ''; 544 608 } 545 // Class can be an array 609 // Class can be an array. 546 610 if ( is_array( $element['class'] ) ) { 547 611 $element['class'] = implode( ' ', $element['class'] ); 548 612 } 549 613 $type = self::get( 'type', $element ); 550 // Any non supported type should be considered to be text 614 // Any non supported type should be considered to be text. 551 615 if ( ! self::supported_type( $type ) ) { 552 616 $type = 'text'; 553 617 } 554 // Type Specific Conditions 618 // Type Specific Conditions. 555 619 switch ( $type ) { 556 620 case 'cite': … … 562 626 case 'number': 563 627 if ( ! array_key_exists( 'step', $element ) ) { 564 $element['step'] = 1; // Defaults to Even Numbers 628 $element['step'] = 1; // Defaults to Even Numbers. 565 629 } 566 630 break; 567 631 case 'select': 568 if ( ! array_key_exists( 'options', $element s) ) {632 if ( ! array_key_exists( 'options', $element ) ) { 569 633 return false; 570 634 } 571 635 break; 572 636 case 'duration': 573 if ( array_key_exists( 'pieces', $element s) ) {574 // Ensure only valid options 575 $element['pieces'] = array_intersect( array( 'Y', 'M', 'D', 'H', 'I', 'S' ), $element s['pieces'] );637 if ( array_key_exists( 'pieces', $element ) ) { 638 // Ensure only valid options. 639 $element['pieces'] = array_intersect( array( 'Y', 'M', 'D', 'H', 'I', 'S' ), $element['pieces'] ); 576 640 } else { 577 // By default only show hours, minutes, seconds 641 // By default only show hours, minutes, seconds. 578 642 $element['pieces'] = array( 'H', 'I', 'S' ); 579 643 } … … 582 646 return $element; 583 647 } 648 649 /** 650 * Whether a field type is supported. 651 * 652 * @since 3.3.4 653 * 654 * @param string $type Field type. 655 * @return bool 656 */ 584 657 public static function supported_type( $type ) { 585 658 return in_array( $type, array( 'cite', 'venue', 'coordinate', 'author', 'datetime', 'duration', 'text', 'url', 'textarea', 'list', 'section' ), true ); … … 588 661 589 662 /** 590 * Sets an array with only the mf2 prefixed meta. 591 * 663 * Returns the properties stored in mf2_ prefixed post meta, without the prefix. 664 * 665 * Reads the current post; the $post argument is not used. 666 * 667 * @since 3.3.4 668 * 669 * @param int|WP_Post $post Not used. 670 * @return array|false The properties, or false if there is no current post. 592 671 */ 593 672 private function get_mf2meta( $post ) { … … 606 685 unset( $meta[ $key ] ); 607 686 $key = str_replace( 'mf2_', '', $key ); 608 // Do not save microput prefixed instructions 687 // Do not save microput prefixed instructions. 609 688 if ( self::str_prefix( $key, 'mp-' ) ) { 610 689 continue; … … 625 704 626 705 /** 627 * Function to render a form from a schema array628 * 629 * @ access public630 * 631 * @param array $schema 632 * 633 * @return string Form706 * Renders a form from a field schema. 707 * 708 * @since 3.3.4 709 * 710 * @param array $schema Field definitions, keyed by property. 711 * @param array $values Optional. Values, keyed by property. Default empty array. 712 * @return string The form. 634 713 */ 635 714 public static function render( $schema, $values = array() ) { … … 649 728 } 650 729 651 /* Extracts microformats elements from post data. 652 * Microformats elements are prefixed by mf2_ 653 * After that, underscore would indicate properties that need to be reconstituted 654 */ 730 /** 731 * Extracts microformats properties from submitted form data. 732 * 733 * Properties are prefixed by mf2_. After that, an underscore marks the 734 * pieces of a property that need to be put back together. 735 * 736 * @since 3.3.4 737 * 738 * @param array $data Form data, such as $_POST. 739 * @return array The properties. 740 */ 655 741 public static function rebuild_data( $data ) { 656 742 $raw = array(); … … 669 755 $raw[ $pieces[0] ] = $pieces[1]; 670 756 unset( $raw[ $key ] ); 671 // If this has the elements of a duration 757 // If this has the elements of a duration. 672 758 if ( ! empty( array_intersect( array( 'Y', 'M', 'D', 'H', 'I', 'S' ), $raw[ $pieces[0] ] ) ) ) { 673 $interval = build_interval( $raw[ $pieces[0] ] );759 $interval = Kind_Time::build_interval( $raw[ $pieces[0] ] ); 674 760 $raw[ $pieces[0] ] = date_interval_to_iso8601( $interval ); 675 761 } 676 // If this has the elements of a datetime 762 // If this has the elements of a datetime. 677 763 if ( ! empty( array_intersect( array( 'date', 'time', 'offset' ), $raw[ $pieces[0] ] ) ) ) { 678 $datetime = build_datetime( $raw[ $pieces[0] ] );764 $datetime = Kind_Time::build_datetime( $raw[ $pieces[0] ] ); 679 765 $raw[ $pieces[0] ] = $datetime->format( DATE_W3C ); 680 766 } -
indieweb-post-kinds/trunk/includes/class-kind-media-metadata.php
r3001401 r3738687 1 1 <?php 2 2 /** 3 * Kind_Media_Metadata class. 3 4 * 4 * Enhances Metadata for Media 5 * @package Post_Kinds 6 * @since 3.1.0 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Enhances the metadata stored for media attachments, and records the media a post uses. 13 * 14 * @since 3.1.0 5 15 */ 6 16 class Kind_Media_Metadata { … … 9 19 * Function to Initialize the Configuration. 10 20 * 21 * @since 3.1.0 22 * 11 23 * @access public 12 24 */ 13 25 public static function init() { 14 $wp_version = get_bloginfo( 'version' );15 26 add_filter( 'wp_generate_attachment_metadata', array( static::class, 'wp_generate_attachment_metadata' ), 33, 2 ); 16 if ( version_compare( $wp_version, '5.3', '>' ) ) { 17 add_filter( 'wp_update_attachment_metadata', array( static::class, 'wp_sanitize_media_metadata' ), 9, 2 ); 18 } 27 add_filter( 'wp_update_attachment_metadata', array( static::class, 'wp_sanitize_media_metadata' ), 9, 2 ); 19 28 add_action( 'wp_enqueue_scripts', array( static::class, 'enqueue' ) ); 20 29 21 add_action( 'save_post', array( static::class, 'save_post' ), 20 ); 30 // Kinds apply to posts only; revisions and autosaves have their own post type. 31 add_action( 'save_post_post', array( static::class, 'save_post' ), 20 ); 32 33 // The photo property can change outside a save, for example from Micropub. 34 foreach ( array( 'added_post_meta', 'updated_post_meta', 'deleted_post_meta' ) as $hook ) { 35 add_action( $hook, array( static::class, 'photo_meta_changed' ), 10, 3 ); 36 } 22 37 23 38 add_filter( 'attachment_fields_to_edit', array( static::class, 'attachment_fields_to_edit' ), 10, 2 ); … … 25 40 } 26 41 42 /** 43 * Whether the request is for an AMP page, according to the AMP plugin. 44 * 45 * @since 3.5.1 46 * 47 * @return bool 48 */ 27 49 public static function is_amp_endpoint() { 28 50 if ( function_exists( 'is_amp_endpoint' ) ) { … … 32 54 } 33 55 56 /** 57 * Enqueues the media fragment script on single posts that have media. 58 * 59 * @since 3.3.0 60 */ 34 61 public static function enqueue() { 35 if ( ! is_front_page() && is_singular() && ! self::is_amp_endpoint() ) { 36 wp_enqueue_script( 37 'media-fragment', 38 plugins_url( 'js/clone-media-fragment.js', __DIR__ ), 39 array(), 40 '1.0', 41 true 42 ); 43 } 62 if ( is_front_page() || ! is_singular() || self::is_amp_endpoint() || ! self::has_media( get_queried_object() ) ) { 63 return; 64 } 65 wp_enqueue_script( 66 'media-fragment', 67 plugins_url( 'js/clone-media-fragment.js', __DIR__ ), 68 array(), 69 '1.0', 70 // WordPress 6.2 reads the array as a true $in_footer; 6.3 and later also defer it. 71 array( 72 'in_footer' => true, 73 'strategy' => 'defer', 74 ) 75 ); 76 } 77 78 /** 79 * Returns whether a post may show audio or video, which the media 80 * fragment script links to. 81 * 82 * @since 4.0.0 83 * 84 * @param mixed $post Post object, usually the queried object. 85 * @return bool True if the post has an audio or video kind or property, or 86 * audio or video in its content. 87 */ 88 public static function has_media( $post ) { 89 if ( ! $post instanceof WP_Post ) { 90 return false; 91 } 92 if ( 'post' === $post->post_type ) { 93 if ( has_post_kind( array( 'audio', 'video', 'listen', 'watch', 'jam' ), $post ) ) { 94 return true; 95 } 96 if ( get_post_meta( $post->ID, 'mf2_audio', true ) || get_post_meta( $post->ID, 'mf2_video', true ) ) { 97 return true; 98 } 99 } 100 return (bool) preg_match( '/<(audio|video)[\s>]|\[(audio|video|playlist)[\s\]]/i', $post->post_content ); 44 101 } 45 102 … … 47 104 /** 48 105 * Displays the Media Creator Description on the Attachment Page. 106 * 107 * @since 3.4.0 49 108 * 50 109 * @param array $form_fields See attachment_fields_to_edit filter in WordPress. … … 55 114 56 115 $author = get_post_meta( $post->ID, 'mf2_author', true ); 57 if ( Parse_This_MF2_Utils::is_microformat( $author ) ) {58 $author = mf2_to_jf2( $author );116 if ( \ParseThis\MF2_Utils::is_microformat( $author ) ) { 117 $author = \ParseThis\mf2_to_jf2( $author ); 59 118 } else { 60 119 $author = array(); … … 81 140 * Saves Extra Fields. 82 141 * 83 * @param array $post Attachment post data. 84 * @param array $attachment data. 142 * @since 3.4.0 143 * 144 * @param array $post Attachment post data. 145 * @param array $attachment Attachment fields submitted. 85 146 */ 86 147 public static function attachment_fields_to_save( $post, $attachment ) { 148 // Core checks this before saving; check again as the filter can be applied elsewhere. 149 if ( empty( $post['ID'] ) || ! current_user_can( 'edit_post', $post['ID'] ) ) { 150 return $post; 151 } 152 // Core passes the request data slashed. 87 153 $author = array(); 88 if ( isset( $attachment['author_name'] ) && ! empty( $attachment['author_name'] ) ) {89 $author['name'] = $attachment['author_name'];90 } 91 if ( isset( $attachment['author_url'] ) && ! empty( $attachment['author_url'] ) ) {92 $author['url'] = $attachment['author_url'];154 if ( isset( $attachment['author_name'] ) && is_string( $attachment['author_name'] ) ) { 155 $author['name'] = sanitize_text_field( wp_unslash( $attachment['author_name'] ) ); 156 } 157 if ( isset( $attachment['author_url'] ) && is_string( $attachment['author_url'] ) ) { 158 $author['url'] = sanitize_url( trim( wp_unslash( $attachment['author_url'] ) ), array( 'http', 'https' ) ); 93 159 } 94 160 $author = array_filter( $author ); … … 97 163 } else { 98 164 $author['type'] = 'card'; 99 update_post_meta( $post['ID'], 'mf2_author', jf2_to_mf2( $author ) ); 165 // The metadata API unslashes, so slash the sanitized values. 166 update_post_meta( $post['ID'], 'mf2_author', wp_slash( \ParseThis\jf2_to_mf2( $author ) ) ); 100 167 } 101 168 return $post; 102 169 } 103 170 104 /* 171 /** 172 * Finds the media library attachments used in a content block. 173 * 174 * The content is parsed once for images, audio and video. An image's ID 175 * is taken from its wp-image-{id} class when it has one; otherwise, and 176 * for audio and video sources, the URL is looked up. 177 * 178 * @since 4.0.0 179 * 180 * @param string $content Content. 181 * @return array { 182 * Attachment IDs found, each list without duplicates or zeros. 183 * 184 * @type int[] $img Image attachment IDs. 185 * @type int[] $audio Audio attachment IDs. 186 * @type int[] $video Video attachment IDs. 187 * } 188 */ 189 private static function get_media_from_content( $content ) { 190 $return = array( 191 'img' => array(), 192 'audio' => array(), 193 'video' => array(), 194 ); 195 $content = wp_unslash( (string) $content ); 196 // Most content has no media; skip parsing it. 197 if ( false === stripos( $content, '<img' ) && false === stripos( $content, '<audio' ) && false === stripos( $content, '<video' ) ) { 198 return $return; 199 } 200 $doc = \ParseThis\pt_load_domdocument( $content ); 201 202 foreach ( $doc->getElementsByTagName( 'img' ) as $image ) { 203 $id = 0; 204 foreach ( explode( ' ', $image->getAttribute( 'class' ) ) as $class ) { 205 if ( 0 === strpos( $class, 'wp-image-' ) ) { 206 $id = (int) substr( $class, strlen( 'wp-image-' ) ); 207 break; 208 } 209 } 210 if ( ! $id ) { 211 $id = attachment_url_to_postid( $image->getAttribute( 'src' ) ); 212 } 213 $return['img'][] = $id; 214 } 215 foreach ( array( 'audio', 'video' ) as $tag ) { 216 foreach ( $doc->getElementsByTagName( $tag ) as $media ) { 217 foreach ( $media->getElementsByTagName( 'source' ) as $source ) { 218 $return[ $tag ][] = attachment_url_to_postid( remove_query_arg( '_', $source->getAttribute( 'src' ) ) ); 219 } 220 } 221 } 222 foreach ( $return as $type => $ids ) { 223 $return[ $type ] = array_values( array_unique( array_filter( array_map( 'intval', $ids ) ) ) ); 224 } 225 return $return; 226 } 227 228 /** 105 229 * Determine Attached Images from a Content Block. 230 * 231 * @since 3.4.0 106 232 * 107 233 * @param string $content Content. 108 234 * @return array Array of Attachment IDs. 109 */235 */ 110 236 public static function get_img_from_content( $content ) { 111 $content = wp_unslash( $content ); 112 $return = array(); 113 $doc = pt_load_domdocument( $content ); 114 $images = $doc->getElementsByTagName( 'img' ); 115 foreach ( $images as $image ) { 116 $classes = $image->getAttribute( 'class' ); 117 $classes = explode( ' ', $classes ); 118 foreach ( $classes as $class ) { 119 if ( 0 === strpos( $class, 'wp-image-' ) ) { 120 $id = (int) str_replace( 'wp-image-', '', $class ); 121 if ( 0 !== $id ) { 122 $return[] = $id; 123 } 124 break; 125 } 126 } 127 $url = $image->getAttribute( 'src' ); 128 $id = attachment_url_to_postid( $url ); 129 if ( 0 !== $id ) { 130 $return[] = $id; 131 } 132 } 133 return array_unique( $return ); 134 } 135 136 /* 237 return self::get_media_from_content( $content )['img']; 238 } 239 240 /** 137 241 * Determine Attached Audio from a Content Block. 242 * 243 * @since 3.4.0 138 244 * 139 245 * @param string $content Content. 140 246 * @return array Array of Attachment IDs. 141 */247 */ 142 248 public static function get_audio_from_content( $content ) { 143 $content = wp_unslash( $content ); 144 $return = array(); 145 $doc = pt_load_domdocument( $content ); 146 $audios = $doc->getElementsByTagName( 'audio' ); 147 foreach ( $audios as $audio ) { 148 $sources = $audio->getElementsByTagName( 'source' ); 149 foreach ( $sources as $source ) { 150 $url = remove_query_arg( '_', $source->getAttribute( 'src' ) ); 151 $id = attachment_url_to_postid( $url ); 152 $return[] = $id; 153 } 154 } 155 return array_unique( $return ); 156 } 157 158 /* 249 return self::get_media_from_content( $content )['audio']; 250 } 251 252 /** 159 253 * Determine Attached Videos from a Content Block. 254 * 255 * @since 3.4.0 160 256 * 161 257 * @param string $content Content. 162 258 * @return array Array of Attachment IDs. 163 */259 */ 164 260 public static function get_video_from_content( $content ) { 165 $content = wp_unslash( $content ); 166 $return = array(); 167 $doc = pt_load_domdocument( $content ); 168 $videos = $doc->getElementsByTagName( 'video' ); 169 foreach ( $videos as $video ) { 170 $sources = $video->getElementsByTagName( 'source' ); 171 foreach ( $sources as $source ) { 172 $url = remove_query_arg( '_', $source->getAttribute( 'src' ) ); 173 $id = attachment_url_to_postid( $url ); 174 $return[] = $id; 175 } 176 } 177 return array_unique( $return ); 178 } 179 180 private function media_sideload_image( $url, $post_id, $description = null ) { 181 // To prevent sideloading the same URL multiple times check for the original URL which will now be stored 182 $ids = get_posts( 183 array( 184 'post_type' => 'attachment', 185 'suppress_filters' => false, 186 'nopaging' => true, 187 'meta_key' => '_source_url', 188 'meta_value' => $url, 189 'fields' => 'ids', 190 ) 191 ); 192 if ( ! empty( $ids ) ) { 193 return $ids[0]; 194 } 195 $id = media_sideload_image( $url, $post_id, $description, 'id' ); 196 // Version 5.4 adds this automatically. 197 if ( $id && version_compare( $wp_version, '5.3', '>' ) ) { 198 update_post_meta( $id, '_source_url', $url ); 199 } 200 return $id; 201 } 202 203 204 205 /** 206 * Every time the post is saved check for media embedded in content and save a list of attachment IDs. 207 */ 261 return self::get_media_from_content( $content )['video']; 262 } 263 264 /** 265 * Every time a post is saved, stores the attachment IDs of the media in its content. 266 * 267 * The lists are kept in the _content_img_ids, _content_audio_ids and 268 * _content_video_ids meta; a key is deleted when there is no media of that 269 * type, so the photos archive can select posts by whether the key exists. 270 * 271 * @since 3.4.0 272 * 273 * @param int $post_id Post ID. 274 */ 208 275 public static function save_post( $post_id ) { 209 $post = get_post( $post_id ); 210 $content = do_shortcode( $post->post_content ); 211 212 $gallery_ids = array(); 213 $gallery = get_post_gallery( $post_id, false ); 214 215 if ( is_array( $gallery ) ) { 216 if ( array_key_exists( 'ids', $gallery ) ) { 217 $gallery_ids = explode( ',', $gallery['ids'] ); 218 } 219 } 220 $ids = self::get_img_from_content( $content ); 221 if ( is_array( $ids ) && is_array( $gallery_ids ) ) { 222 $ids = array_merge( $ids, $gallery_ids ); 223 } 224 225 if ( ! $ids ) { 226 delete_post_meta( $post_id, '_content_img_ids' ); 276 // The classic editor's draft autosave; the next save will record the media. 277 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { 278 return; 279 } 280 $post = get_post( $post_id ); 281 if ( ! $post ) { 282 return; 283 } 284 $content = $post->post_content; 285 // Shortcodes such as [gallery], [audio] and [video] produce the media markup. 286 if ( false !== strpos( $content, '[' ) ) { 287 $content = do_shortcode( $content ); 288 } 289 $media = self::get_media_from_content( $content ); 290 291 $gallery = get_post_gallery( $post_id, false ); 292 if ( is_array( $gallery ) && ! empty( $gallery['ids'] ) ) { 293 $media['img'] = array_values( array_unique( array_merge( $media['img'], array_filter( array_map( 'intval', explode( ',', $gallery['ids'] ) ) ) ) ) ); 294 } 295 296 foreach ( $media as $type => $ids ) { 297 $key = '_content_' . $type . '_ids'; 298 if ( empty( $ids ) ) { 299 delete_post_meta( $post_id, $key ); 300 } else { 301 update_post_meta( $post_id, $key, $ids ); 302 } 303 } 304 self::update_photo_flag( $post_id ); 305 } 306 307 /** 308 * Updates the photo flag when a post's photo property changes. 309 * 310 * @since 4.0.0 311 * 312 * @param int|int[] $meta_id Meta ID, or IDs when deleting. 313 * @param int $object_id Post ID. 314 * @param string $meta_key Meta key. 315 */ 316 public static function photo_meta_changed( $meta_id, $object_id, $meta_key ) { 317 if ( 'mf2_photo' === $meta_key && 'post' === get_post_type( $object_id ) ) { 318 self::update_photo_flag( $object_id ); 319 } 320 } 321 322 /** 323 * Records whether a post has photos, for the photos archive. 324 * 325 * A post has photos when it has images in its content or a photo 326 * property. The _kind_has_photo meta exists only for those posts, so the 327 * archive can select them with one indexed meta key. 328 * 329 * @since 4.0.0 330 * 331 * @param int $post_id Post ID. 332 */ 333 public static function update_photo_flag( $post_id ) { 334 if ( get_post_meta( $post_id, '_content_img_ids', true ) || get_post_meta( $post_id, 'mf2_photo', true ) ) { 335 update_post_meta( $post_id, '_kind_has_photo', 1 ); 227 336 } else { 228 update_post_meta( $post_id, '_content_img_ids', $ids ); 229 } 230 $ids = self::get_video_from_content( $content ); 231 if ( ! $ids ) { 232 delete_post_meta( $post_id, '_content_video_ids' ); 233 } else { 234 update_post_meta( $post_id, '_content_video_ids', $ids ); 235 } 236 $ids = self::get_audio_from_content( $content ); 237 if ( ! $ids ) { 238 delete_post_meta( $post_id, '_content_audio_ids' ); 239 } else { 240 update_post_meta( $post_id, '_content_audio_ids', $ids ); 337 delete_post_meta( $post_id, '_kind_has_photo' ); 241 338 } 242 339 } … … 248 345 * bad character encodings from causing database calls and API endpoints to fail. 249 346 * 250 * @param array $metadata An existing array with data 347 * @since 3.3.0 348 * 349 * @param array $metadata An existing array with data. 251 350 * 252 351 * @return array Returns array of sanitized metadata. … … 271 370 } 272 371 372 /** 373 * Stores an attachment's author, album and duration from its file metadata. 374 * 375 * The image credit, or the artist of audio or video, becomes the author; 376 * the album becomes the publication. 377 * 378 * @since 3.3.4 379 * 380 * @param array|mixed $data Attachment metadata. 381 * @param int $attachment_id Attachment ID. 382 * @return array|mixed The metadata, with empty values removed. 383 */ 273 384 public static function wp_generate_attachment_metadata( $data, $attachment_id ) { 274 385 if ( ! is_array( $data ) ) { 275 386 return $data; 276 387 } 277 $data = array_filter( $data ); 278 $attachment = get_post( $attachment_id, ARRAY_A ); 388 $data = array_filter( $data ); 279 389 if ( isset( $data['image_meta'] ) ) { 280 390 $meta = $data['image_meta']; … … 283 393 $attachment_id, 284 394 'mf2_author', 285 jf2_to_mf2(395 \ParseThis\jf2_to_mf2( 286 396 array( 287 397 'name' => $meta['credit'], … … 299 409 $attachment_id, 300 410 'mf2_author', 301 jf2_to_mf2(411 \ParseThis\jf2_to_mf2( 302 412 array( 303 413 'name' => $data['artist'], … … 308 418 } 309 419 if ( isset( $data['length'] ) ) { 310 update_post_meta( $attachment_id, 'mf2_duration', array( seconds_to_iso8601( $data['length'] ) ) );420 update_post_meta( $attachment_id, 'mf2_duration', array( \ParseThis\seconds_to_iso8601( $data['length'] ) ) ); 311 421 } 312 422 return $data; -
indieweb-post-kinds/trunk/includes/class-kind-media-view.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Kind Media View Class3 * Kind_Media_View class. 4 4 * 5 * @package Post Kind6 * Used to Display Media5 * @package Post_Kinds 6 * @since 3.3.0 7 7 */ 8 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Builds the markup for images, audio and video in kind views. 13 * 14 * @since 3.3.0 15 */ 9 16 class Kind_Media_View { 17 /** 18 * Attachment IDs or media URLs. 19 * 20 * @since 3.3.0 21 * @var int[]|string[]|int|string 22 */ 10 23 private $ids; 24 25 /** 26 * Media type: 'photo', 'audio' or 'video'. 27 * 28 * @since 3.3.0 29 * @var string 30 */ 11 31 private $type; 32 33 /** 34 * Sets up the media to display. 35 * 36 * @since 3.3.0 37 * 38 * @param int[]|string[]|int|string $ids Attachment IDs or media URLs. 39 * @param string $type Media type: 'photo', 'audio' or 'video'. 40 */ 12 41 public function __construct( $ids, $type ) { 13 42 $this->ids = $ids; … … 15 44 } 16 45 46 /** 47 * Returns the markup for the media. 48 * 49 * @since 3.3.0 50 * 51 * @return string The markup, or an empty string if the type is not supported. 52 */ 17 53 public function get() { 18 54 switch ( $this->type ) { … … 27 63 } 28 64 65 /** 66 * Returns a one-column gallery of photos. 67 * 68 * @since 3.3.0 69 * 70 * @param int[] $photos Attachment IDs. 71 * @return string The gallery markup. 72 */ 29 73 private function photo( $photos ) { 30 74 return gallery_shortcode( … … 41 85 * Return a media view for the audio post kind. 42 86 * 87 * @since 3.3.0 88 * 43 89 * @access private 44 * @param int|string $id Audio attachment ID or audio URL 45 * @param mixed $args Arguments for the audio media view. 90 * 91 * @param int|string|array $id Audio attachment ID or audio URL, or a list of them. 92 * @param array|null $args Arguments for the audio media view. 46 93 * 47 94 * @return array|string|void … … 72 119 $return = ''; 73 120 if ( $args['src'] ) { 121 $args = wp_parse_args( $args, $default ); 74 122 $return = wp_audio_shortcode( $args ); 75 123 } … … 80 128 * Return a media view for the video post kind. 81 129 * 130 * @since 3.3.0 131 * 82 132 * @access private 83 * @param int|string $id Video attachment ID or URL. 84 * @param mixed $args Arguments for the video media view. 133 * 134 * @param int|string|array $id Video attachment ID or URL, or a list of them. 135 * @param array|null $args Arguments for the video media view. 85 136 * 86 137 * @return array|string|void … … 98 149 } elseif ( wp_http_validate_url( $id ) ) { 99 150 $args['src'] = $id; 100 } if ( 0 === $id ) {151 } elseif ( 0 === $id ) { 101 152 return ''; 102 153 } else { -
indieweb-post-kinds/trunk/includes/class-kind-menu-widget.php
r3067251 r3738687 1 1 <?php 2 /** 3 * Kind_Menu_Widget class. 4 * 5 * @package Post_Kinds 6 * @since 3.1.3 7 */ 2 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Widget that lists links to the archive of each kind. 13 * 14 * @since 3.1.3 15 */ 3 16 class Kind_Menu_Widget extends WP_Widget { 4 17 /** 5 18 * Register widget with WordPress. 19 * 20 * @since 3.1.3 6 21 */ 7 22 public function __construct() { 8 23 parent::__construct( 9 'Kind_Menu_Widget', // Base ID 10 __( 'Kind Menu Widget', 'indieweb-post-kinds' ), // Name 24 'Kind_Menu_Widget', // Base ID. 25 __( 'Kind Menu Widget', 'indieweb-post-kinds' ), // Name. 11 26 array( 12 27 'classname' => 'kind_menu_widget', … … 19 34 /** 20 35 * Front-end display of widget. 36 * 37 * @since 3.1.3 21 38 * 22 39 * @see WP_Widget::widget() … … 35 52 } 36 53 $include = array_merge( $include, array( 'note', 'reply', 'article' ) ); 37 // Filter Kinds 54 /** 55 * This filter is documented in includes/class-kind-taxonomy.php 56 */ 38 57 $include = array_unique( apply_filters( 'kind_include', $include ) ); 39 // Note cannot be removed or disabled without hacking the code 58 // Note cannot be removed or disabled without hacking the code. 40 59 if ( ! in_array( 'note', $include, true ) ) { 41 60 $include[] = 'note'; 42 61 } 43 62 44 // phpcs:ignore 45 echo $args['before_widget']; 63 echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget wrapper markup registered by the theme. 46 64 if ( $title ) { 47 echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore 65 echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Wrapper markup is the theme's; the title is escaped by core's widget_title filter. 48 66 } 49 67 ?> … … 52 70 <ul> 53 71 <?php 54 $feed = is_front_page() ? ' rel="feed"' : '';72 $feed = is_front_page() ? ' rel="feed"' : ''; 55 73 foreach ( $include as $i ) { 56 74 $count = Kind_Taxonomy::get_post_kind_count( $i ); … … 60 78 $name = ( 1 === $count ) ? Kind_Taxonomy::get_kind_info( $i, 'singular_name' ) : Kind_Taxonomy::get_kind_info( $i, 'name' ); 61 79 /* translators: 1. Blog Title 2. Kind Singular Name */ 62 $ title = sprintf( __( 'title="%1$s %2$s Feed"', 'indieweb-post-kinds' ), esc_attr( get_bloginfo( 'name' ) ), esc_attR( Kind_Taxonomy::get_kind_info( $i, 'singular_name' )) );63 if ( 1 === (int) $instance['count']) {64 $ count = sprintf( '%1$s (%2$s)', $name, $count);80 $feed_title = sprintf( __( '%1$s %2$s Feed', 'indieweb-post-kinds' ), get_bloginfo( 'name' ), Kind_Taxonomy::get_kind_info( $i, 'singular_name' ) ); 81 if ( 1 === (int) ( $instance['count'] ?? 0 ) ) { 82 $label = sprintf( '%1$s (%2$s)', $name, number_format_i18n( $count ) ); 65 83 } else { 66 $ count= $name;84 $label = $name; 67 85 } 68 86 printf( 69 '<li><a %4$s %5$stype="text/html" href="%2$s">%1$s%3$s</a></li>',70 Kind_Taxonomy::get_icon( $i ), // phpcs:ignore 87 '<li><a%4$s title="%5$s" type="text/html" href="%2$s">%1$s%3$s</a></li>', 88 Kind_Taxonomy::get_icon( $i ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The plugin's own SVG icon markup. 71 89 esc_url( Kind_Taxonomy::get_post_kind_link( $i ) ), 72 $count, // phpcs:ignore73 $feed, // phpcs:ignore 74 $title // phpcs:ignore75 ); // phpcs:ignore90 esc_html( $label ), 91 $feed, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- A fixed attribute string. 92 esc_attr( $feed_title ) 93 ); 76 94 } 77 if ( 1 === (int) $instance['all']) {78 printf( '<li><a type="text/html" href="%2$s">%1$s%3$s</a></li>', Kind_Taxonomy::get_icon( 'firehose' ), esc_url( get_post_type_archive_link( 'post' ) ), esc_html__( 'All Posts', 'indieweb-post-kinds' ) ); // phpcs:ignore 95 if ( 1 === (int) ( $instance['all'] ?? 0 ) ) { 96 printf( '<li><a type="text/html" href="%2$s">%1$s%3$s</a></li>', Kind_Taxonomy::get_icon( 'firehose' ), esc_url( get_post_type_archive_link( 'post' ) ), esc_html__( 'All Posts', 'indieweb-post-kinds' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The plugin's own SVG icon markup. 79 97 } 80 98 ?> … … 83 101 84 102 <?php 85 // phpcs:ignore 86 echo $args['after_widget']; 103 echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget wrapper markup registered by the theme. 87 104 } 88 105 … … 90 107 /** 91 108 * Sanitize widget form values as they are saved. 109 * 110 * @since 3.1.3 92 111 * 93 112 * @see WP_Widget::update() … … 99 118 */ 100 119 public function update( $new_instance, $old_instance ) { 101 array_walk_recursive( $new_instance, 'sanitize_text_field' ); 102 return $new_instance; 120 $termslist = isset( $new_instance['termslist'] ) && is_array( $new_instance['termslist'] ) ? array_filter( $new_instance['termslist'], 'is_string' ) : array(); 121 $termslist = array_values( array_unique( array_intersect( array_map( 'sanitize_key', $termslist ), Kind_Taxonomy::get_kind_list() ) ) ); 122 return array( 123 'title' => isset( $new_instance['title'] ) && is_string( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '', 124 'termslist' => $termslist, 125 'count' => empty( $new_instance['count'] ) ? 0 : 1, 126 'all' => empty( $new_instance['all'] ) ? 0 : 1, 127 ); 103 128 } 104 129 … … 106 131 /** 107 132 * Create the form for the Widget admin 133 * 134 * @since 3.1.3 108 135 * 109 136 * @see WP_Widget::form() … … 120 147 $termslist = (array) $instance['termslist']; 121 148 ?> 122 <p><label for="title"><?php esc_html_e( 'Title: ', 'indieweb-post-kinds' ); ?></label> 123 <input type="text" size="30" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?> id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" value=" 124 <?php echo esc_html( ( $instance['title'] ?? '' ) ); ?>" /></p> 149 <p><label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title: ', 'indieweb-post-kinds' ); ?></label> 150 <input type="text" size="30" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" value="<?php echo esc_attr( $instance['title'] ?? '' ); ?>" /></p> 125 151 <div id="kind-all"> 126 152 <?php 127 foreach ( get_option( 'kind_termslist', Kind_Taxonomy::get_kind_list()) as $term ) {153 foreach ( Kind_Config::get_termslist() as $term ) { 128 154 $value = Kind_Taxonomy::get_post_kind_info( $term ); 129 155 if ( $value->show ) { … … 135 161 checked( in_array( $term, $termslist, true ), true, false ) 136 162 ); 137 printf( '%1$s %2$s<br />', Kind_Taxonomy::get_icon( $term ), esc_html( $value->singular_name ) ); // phpcs:ignore163 printf( '%1$s %2$s<br />', Kind_Taxonomy::get_icon( $term ), esc_html( $value->singular_name ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The plugin's own SVG icon markup. 138 164 } 139 165 } -
indieweb-post-kinds/trunk/includes/class-kind-metabox.php
r3001401 r3738687 1 1 <?php 2 2 /** 3 * Post Kind Post MetaBox Class3 * Kind_Metabox class. 4 4 * 5 * Sets Up Tabbed Metabox in the Posting UI for Kind data. 5 * @package Post_Kinds 6 * @since 2.2.0 6 7 */ 7 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Adds the tabbed Post Kinds metabox to the Classic Editor and saves its fields. 13 * 14 * @since 2.2.0 15 */ 8 16 class Kind_Metabox { 9 17 10 18 /** 19 * Plugin version, used to version enqueued assets. 20 * 11 21 * @var string $version 12 22 */ … … 15 25 /** 16 26 * Function to initiate our metabox. 27 * 28 * @since 2.2.0 17 29 * 18 30 * @access public … … 21 33 self::$version = Post_Kinds_Plugin::$version; 22 34 add_action( 'edit_form_after_title', array( static::class, 'after_title_metabox' ) ); 23 // Add meta box to new post/post pages only 35 // Add meta box to new post/post pages only. 24 36 add_action( 'load-post.php', array( static::class, 'kindbox_setup' ) ); 25 37 add_action( 'load-post-new.php', array( static::class, 'kindbox_setup' ) ); … … 33 45 * Function to change our post kind. 34 46 * 47 * @since 3.1.0 48 * 35 49 * @access public 36 50 * … … 68 82 69 83 /** 70 * Function to change if a new post should be considered empty. 84 * Filters whether a post should be considered empty. 85 * 86 * Many kinds (likes, bookmarks, reposts, and so on) legitimately have no 87 * content, title or excerpt, which core would otherwise reject as empty. 88 * 89 * @since 3.0.0 71 90 * 72 91 * @access public 73 92 * 74 93 * @param bool $maybe_empty Whether or not the post should be considered empty. 75 * @param array $postarr Data for the post to be inserted .94 * @param array $postarr Data for the post to be inserted or updated. 76 95 * 77 96 * @return bool 78 97 */ 79 98 public static function wp_insert_post_empty_content( $maybe_empty, $postarr ) { 80 // Always let updates to trash posts through 81 if ( 'trash' === $postarr['post_status'] ) {99 // Always let updates to trash posts through. 100 if ( isset( $postarr['post_status'] ) && 'trash' === $postarr['post_status'] ) { 82 101 return false; 83 102 } 84 // Let All Micropub Posts through85 if ( isset( $postarr['meta_input'] ) && isset( $postarr['meta_input']['micropub_auth_response'] ) ) {103 // Let all Micropub creates through. 104 if ( isset( $postarr['meta_input']['micropub_auth_response'] ) ) { 86 105 return false; 87 106 } 88 if ( ! isset( $postarr['tax_input'] ) && ! isset( $postarr['tax_input']['kind'] ) ) { 89 return $maybe_empty; 90 } 91 $kind = get_term_by( 'id', $postarr['tax_input']['kind'][0], 'kind' ); 92 $kind = ( $kind instanceof WP_Term ) ? $kind->slug : ''; 93 // Use traditional rules for articles 107 $kind = self::kind_from_postarr( $postarr ); 108 // Use traditional rules for articles, and for posts without a kind. 94 109 if ( 'article' === $kind || ! $kind ) { 95 110 return $maybe_empty; 96 111 } 97 $keys = array( 'cite_url', 'cite_name', 'cite_summary' ); 98 $keys = array_flip( $keys ); 99 $diff = array_filter( array_intersect_key( $_POST, $keys ) ); 100 if ( ! empty( $diff ) ) { 112 // An existing post of a kind other than article may have no content, 113 // for example when Micropub updates a like. 114 if ( ! empty( $postarr['ID'] ) ) { 101 115 return false; 102 116 } 117 // A new post from the metabox needs something to respond to. 118 foreach ( array( 'cite_url', 'cite_name', 'cite_summary' ) as $key ) { 119 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checks whether a field was filled in; save_post() verifies the nonce before saving. 120 if ( isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) && '' !== sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) ) { 121 return false; 122 } 123 } 103 124 return $maybe_empty; 104 125 } 105 126 106 127 /** 128 * Returns the kind slug for post data being inserted or updated. 129 * 130 * The kind comes from tax_input, which the kind metabox submits as a slug and 131 * other callers may pass as term IDs, or failing that from the existing post. 132 * 133 * @since 4.0.0 134 * 135 * @access public 136 * 137 * @param array $postarr Data for the post to be inserted or updated. 138 * @return string Kind slug, or an empty string if none. 139 */ 140 public static function kind_from_postarr( $postarr ) { 141 if ( isset( $postarr['tax_input']['kind'] ) ) { 142 $kind = $postarr['tax_input']['kind']; 143 if ( is_array( $kind ) ) { 144 $kind = reset( $kind ); 145 } 146 $term = is_numeric( $kind ) ? get_term_by( 'id', (int) $kind, 'kind' ) : get_term_by( 'slug', (string) $kind, 'kind' ); 147 return ( $term instanceof WP_Term ) ? $term->slug : ''; 148 } 149 if ( ! empty( $postarr['ID'] ) ) { 150 $kind = get_post_kind_slug( $postarr['ID'] ); 151 return $kind ? $kind : ''; 152 } 153 return ''; 154 } 155 156 /** 107 157 * Execute metaboxes for the current screen, after the post title. 108 158 * 159 * @since 3.0.0 160 * 109 161 * @access public 110 162 * @param WP_Post $post Post object for the current screen. … … 117 169 /** 118 170 * Metabox setup. 171 * 172 * @since 2.2.0 119 173 * 120 174 * @access public … … 124 178 /* Add meta boxes on the 'add_meta_boxes' hook. */ 125 179 add_action( 'add_meta_boxes', array( $cls, 'add_meta_boxes' ) ); 180 add_filter( 'hidden_meta_boxes', array( $cls, 'hidden_meta_boxes' ), 10, 2 ); 126 181 add_action( 'admin_enqueue_scripts', array( $cls, 'enqueue_admin_scripts' ) ); 127 182 } … … 129 184 /** 130 185 * Enqueue our needed assets. 186 * 187 * @since 2.2.0 131 188 * 132 189 * @access public … … 145 202 146 203 // Provide a global object to our JS file containing our REST API endpoint, and API nonce 147 // Nonce must be 'wp_rest' 204 // Nonce must be 'wp_rest'. 148 205 wp_localize_script( 149 206 'kindmeta', … … 154 211 'success_message' => __( 'Your URL has been successfully retrieved and parsed', 'indieweb-post-kinds' ), 155 212 'clear_message' => __( 'Are you sure you want to clear post properties?', 'indieweb-post-kinds' ), 213 'error_message' => __( 'Error: Unable to Retrieve', 'indieweb-post-kinds' ), 214 'invalid_url' => __( 'Invalid URL', 'indieweb-post-kinds' ), 215 'media_title' => __( 'Attach', 'indieweb-post-kinds' ), 216 'media_button' => __( 'Use this media', 'indieweb-post-kinds' ), 156 217 ) 157 218 ); … … 163 224 /** 164 225 * Utility function to concatenate a list of post kinds. 226 * 227 * @since 2.7.5 165 228 * 166 229 * @access public … … 180 243 * Utility function to separate out a list of post kinds. 181 244 * 245 * @since 2.7.5 246 * 182 247 * @access public 183 248 * … … 194 259 /** 195 260 * Function to render date/time field inputs. 261 * 262 * @since 2.5.0 196 263 * 197 264 * @access public … … 206 273 $tz_seconds = get_option( 'gmt_offset' ) * 3600; 207 274 $offset = tz_seconds_to_offset( $tz_seconds ); 208 $time = divide_datetime( $datetime );275 $time = Kind_Time::divide_datetime( $datetime ); 209 276 if ( ! is_array( $time ) ) { 210 277 $time = array(); … … 213 280 $offset = $time['offset']; 214 281 } 215 $string = '<label class="half ' . $class . '" for="' . $prefix . '">' . $label . '<br/>'; 216 $string .= '<input class="date" type="date" name="' . $prefix . '_date" id="' . $prefix . '_date" value="' . ( $time['date'] ?? '' ) . '"/>'; 217 $string .= '<input class="time" type="time" name="' . $prefix . '_time" id="' . $prefix . '_time" step="1" value="' . ( $time['time'] ?? '' ) . '"/>'; 282 $prefix = esc_attr( $prefix ); 283 $string = '<label class="half ' . esc_attr( $class ) . '" for="' . $prefix . '">' . esc_html( $label ) . '<br/>'; 284 $string .= '<input class="date" type="date" name="' . $prefix . '_date" id="' . $prefix . '_date" value="' . esc_attr( $time['date'] ?? '' ) . '"/>'; 285 $string .= '<input class="time" type="time" name="' . $prefix . '_time" id="' . $prefix . '_time" step="1" value="' . esc_attr( $time['time'] ?? '' ) . '"/>'; 218 286 $string .= self::select_offset( $prefix, $offset ); 219 287 $string .= '</label>'; … … 224 292 * Function to render our timezone choices. 225 293 * 294 * @since 2.5.0 295 * 226 296 * @access public 227 297 * … … 231 301 */ 232 302 public static function select_offset( $prefix, $select ) { 233 $string = '<select name="' . $prefix . '_offset" id="' . $prefix. '_offset">';303 $string = '<select name="' . esc_attr( $prefix ) . '_offset" id="' . esc_attr( $prefix ) . '_offset">'; 234 304 $string .= self::timezone_offset_choice( $select ); 235 305 $string .= '</select>'; … … 240 310 * Function to render options for a chosen timezone select field. 241 311 * 312 * @since 3.0.0 313 * 242 314 * @access public 243 315 * … … 248 320 $tzlist = get_gmt_offsets(); 249 321 $string = ''; 250 foreach ( $tzlist as $ key => $value ) {251 $string .= '<option value="' . $value. '"';322 foreach ( $tzlist as $value ) { 323 $string .= '<option value="' . esc_attr( $value ) . '"'; 252 324 if ( $select === $value ) { 253 325 $string .= ' selected'; 254 326 } 255 $string .= '>GMT' . $value. '</option>';327 $string .= '>GMT' . esc_html( $value ) . '</option>'; 256 328 } 257 329 return $string; … … 261 333 * Render the options for the rating select field. 262 334 * 263 * @access public 264 * 265 * @param string $selected Selected Rating choice 335 * @since 3.7.0 336 * 337 * @access public 338 * 339 * @param string $selected Selected Rating choice. 266 340 * @return string 267 341 */ … … 278 352 $ret = ''; 279 353 foreach ( $ratings as $key => $value ) { 280 $ret .= '<option value="' . $key. '"';354 $ret .= '<option value="' . esc_attr( $key ) . '"'; 281 355 if ( $selected === $key ) { 282 356 $ret .= ' selected'; 283 357 } 284 $ret .= '>' . $value. '</option>';358 $ret .= '>' . esc_html( (string) $value ) . '</option>'; 285 359 } 286 360 return $ret; … … 290 364 * Render the options for the RSVP select field. 291 365 * 292 * @access public 293 * 294 * @param string $selected Selected RSVP choice 366 * @since 3.0.0 367 * 368 * @access public 369 * 370 * @param string $selected Selected RSVP choice. 295 371 * @return string 296 372 */ … … 306 382 $string = ''; 307 383 foreach ( $rsvps as $key => $value ) { 308 $string .= '<option value="' . $key. '"';384 $string .= '<option value="' . esc_attr( $key ) . '"'; 309 385 if ( $selected === $key ) { 310 386 $string .= ' selected'; 311 387 } 312 $string .= '>' . $value. '</option>';388 $string .= '>' . esc_html( (string) $value ) . '</option>'; 313 389 } 314 390 return $string; … … 317 393 /** 318 394 * Render our RSVP select input. 395 * 396 * @since 2.5.0 319 397 * 320 398 * @access public … … 324 402 */ 325 403 public static function rsvp_select( $selected ) { 326 $string = '<label for="mf2_rsvp">' . __( 'RSVP', 'indieweb-post-kinds' ) . '</label><br/>';404 $string = '<label for="mf2_rsvp">' . esc_html__( 'RSVP', 'indieweb-post-kinds' ) . '</label><br/>'; 327 405 $string .= '<select name="mf2_rsvp" id="mf2_rsvp">'; 328 406 $string .= self::rsvp_choice( $selected ); … … 334 412 * Render our rating select input. 335 413 * 414 * @since 3.7.0 415 * 336 416 * @access public 337 417 * … … 340 420 */ 341 421 public static function rating_select( $selected ) { 342 $string = '<label for="mf2_rating">' . __( 'Rating', 'indieweb-post-kinds' ) . '</label><br/>';422 $string = '<label for="mf2_rating">' . esc_html__( 'Rating', 'indieweb-post-kinds' ) . '</label><br/>'; 343 423 $string .= '<select name="mf2_rating" id="mf2_rating">'; 344 424 $string .= self::rating_choice( $selected ); … … 348 428 349 429 /** 430 * Keeps the Response Properties box from being saved as hidden. 431 * 432 * The editor script hides the box for kinds without a response, such as 433 * note. WordPress saves every hidden box as unticked in Screen Options 434 * whenever any box is collapsed or any Screen Options box is toggled, so 435 * the box would stay hidden for every kind afterwards. The script shows 436 * and hides it by kind instead. 437 * 438 * @since 4.0.0 439 * 440 * @param string[] $hidden IDs of the hidden meta boxes. 441 * @param WP_Screen $screen The current screen. 442 * @return string[] IDs of the hidden meta boxes. 443 */ 444 public static function hidden_meta_boxes( $hidden, $screen ) { 445 if ( ! is_array( $hidden ) || ! $screen instanceof WP_Screen || 'post' !== $screen->id ) { 446 return $hidden; 447 } 448 return array_values( array_diff( $hidden, array( 'replybox-meta' ) ) ); 449 } 450 451 /** 350 452 * Create one or more meta boxes to be displayed on the post editor screen. 453 * 454 * @since 2.2.0 351 455 * 352 456 * @access public … … 354 458 public static function add_meta_boxes() { 355 459 add_meta_box( 356 'replybox-meta', // Unique ID 357 esc_html__( 'Response Properties', 'indieweb-post-kinds' ), // Title 358 array( static::class, 'reply_metabox' ), // Callback function 460 'replybox-meta', // Unique ID. 461 esc_html__( 'Response Properties', 'indieweb-post-kinds' ), // Title. 462 array( static::class, 'reply_metabox' ), // Callback function. 359 463 'post', 360 'kind_after_title', // Context 361 'default', // Priority 464 'kind_after_title', // Context. 465 'default', // Priority. 362 466 array( 363 467 '__block_editor_compatible_meta_box' => false, … … 370 474 * Render our reply meta box. 371 475 * 476 * @since 3.0.0 477 * 372 478 * @access public 373 479 * … … 382 488 * Process and save meta box data. 383 489 * 384 * @access public 385 * 386 * @param int $post_id Saved post ID. 387 * @param WP_Pos $post Saved post object. 490 * @since 2.2.0 491 * 492 * @access public 493 * 494 * @param int $post_id Saved post ID. 495 * @param WP_Post $post Saved post object. 388 496 */ 389 497 public static function save_post( $post_id, $post ) { … … 399 507 400 508 // Verify that the nonce is valid. 401 if ( ! wp_verify_nonce( $_POST['replykind_metabox_nonce'], 'replykind_metabox' ) ) {509 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['replykind_metabox_nonce'] ) ), 'replykind_metabox' ) ) { 402 510 return; 403 511 } … … 409 517 410 518 // Check the user's permissions. 411 if ( isset( $_POST['post_type'] ) && 'page' === $_POST['post_type']) {519 if ( 'page' === self::posted_text( 'post_type' ) ) { 412 520 if ( ! current_user_can( 'edit_page', $post_id ) ) { 413 521 return; … … 422 530 423 531 if ( isset( $_POST['mf2_start_date'] ) || isset( $_POST['mf2_start_time'] ) ) { 424 $start = build_iso8601_time( s anitize_text_field( $_POST['mf2_start_date'] ), sanitize_text_field( $_POST['mf2_start_time'] ), sanitize_text_field( $_POST['mf2_start_offset']) );532 $start = build_iso8601_time( self::posted_text( 'mf2_start_date' ), self::posted_text( 'mf2_start_time' ), self::posted_text( 'mf2_start_offset' ) ); 425 533 if ( ! $start ) { 426 534 $kind_post->delete( 'start' ); … … 430 538 } 431 539 if ( isset( $_POST['mf2_end_date'] ) || isset( $_POST['mf2_end_time'] ) ) { 432 $end = build_iso8601_time( s anitize_text_field( $_POST['mf2_end_date'] ), sanitize_text_field( $_POST['mf2_end_time'] ), sanitize_text_field( $_POST['mf2_end_offset']) );540 $end = build_iso8601_time( self::posted_text( 'mf2_end_date' ), self::posted_text( 'mf2_end_time' ), self::posted_text( 'mf2_end_offset' ) ); 433 541 if ( ! $end ) { 434 542 $kind_post->delete( 'end' ); … … 443 551 444 552 $durations = array( 445 'Y' => intval( $_POST['duration_years'] ?? ''),446 'M' => intval( $_POST['duration_months'] ?? ''),447 'D' => intval( $_POST['duration_days'] ?? ''),448 'H' => intval( $_POST['duration_hours'] ?? ''),449 'I' => intval( $_POST['duration_minutes'] ?? ''),450 'S' => intval( $_POST['duration_seconds'] ?? ''),553 'Y' => absint( self::posted_text( 'duration_years' ) ), 554 'M' => absint( self::posted_text( 'duration_months' ) ), 555 'D' => absint( self::posted_text( 'duration_days' ) ), 556 'H' => absint( self::posted_text( 'duration_hours' ) ), 557 'I' => absint( self::posted_text( 'duration_minutes' ) ), 558 'S' => absint( self::posted_text( 'duration_seconds' ) ), 451 559 ); 452 560 $duration = build_iso8601_duration( $durations ); … … 456 564 if ( $duration instanceof DateInterval ) { 457 565 $duration = date_interval_to_iso8601( $duration ); 458 error_log( wp_json_encode( $duration ) );459 566 } 460 567 } … … 465 572 } 466 573 467 $kind_post->set( 'rsvp', $_POST['mf2_rsvp'] ); 468 if ( array_key_exists( 'mf2_rating', $_POST ) && is_numeric( $_POST['mf2_rating'] ) ) { 469 $kind_post->set( 'rating', intval( $_POST['mf2_rating'] ) ); 470 } 471 472 if ( isset( $_POST['cite_published_date'] ) || isset( $_POST['published_time'] ) ) { 473 $cite['published'] = build_iso8601_time( sanitize_text_field( $_POST['cite_published_date'] ), sanitize_text_field( $_POST['cite_published_time'] ), sanitize_text_field( $_POST['cite_published_offset'] ) ); 574 if ( isset( $_POST['mf2_rsvp'] ) ) { 575 $rsvp = self::posted_text( 'mf2_rsvp' ); 576 if ( in_array( $rsvp, array( 'yes', 'no', 'maybe', 'interested', 'remote' ), true ) ) { 577 $kind_post->set( 'rsvp', $rsvp ); 578 } else { 579 $kind_post->delete( 'rsvp' ); 580 } 581 } 582 $rating = self::posted_text( 'mf2_rating' ); 583 if ( is_numeric( $rating ) ) { 584 $kind_post->set( 'rating', intval( $rating ) ); 585 } 586 587 if ( isset( $_POST['cite_published_date'] ) || isset( $_POST['cite_published_time'] ) ) { 588 $cite['published'] = build_iso8601_time( self::posted_text( 'cite_published_date' ), self::posted_text( 'cite_published_time' ), self::posted_text( 'cite_published_offset' ) ); 474 589 } 475 590 if ( isset( $_POST['cite_updated_date'] ) || isset( $_POST['cite_updated_time'] ) ) { 476 $cite['updated'] = build_iso8601_time( sanitize_text_field( $_POST['cite_updated_date'] ), sanitize_text_field( $_POST['cite_updated_time'] ), sanitize_text_field( $_POST['cite_updated_offset'] ) ); 477 } 478 $cite['summary'] = wp_kses_post( $_POST['cite_summary'] ?? '' ); 479 $cite['name'] = sanitize_text_field( $_POST['cite_name'] ?? '' ); 480 $cite['url'] = esc_url( $_POST['cite_url'] ?? '' ); 591 $cite['updated'] = build_iso8601_time( self::posted_text( 'cite_updated_date' ), self::posted_text( 'cite_updated_time' ), self::posted_text( 'cite_updated_offset' ) ); 592 } 593 // Summaries are plain text; line breaks are kept. 594 $cite['summary'] = isset( $_POST['cite_summary'] ) && is_string( $_POST['cite_summary'] ) ? sanitize_textarea_field( wp_unslash( $_POST['cite_summary'] ) ) : ''; 595 $cite['name'] = self::posted_text( 'cite_name' ); 596 $cite['url'] = self::posted_urls( 'cite_url' ); 481 597 if ( isset( $_POST['cite_tags'] ) ) { 482 $cite['category'] = array_filter( explode( ';', sanitize_text_field( $_POST['cite_tags'] ) ) ); 483 } 484 $cite['publication'] = sanitize_text_field( $_POST['cite_publication'] ?? '' ); 485 $cite['featured'] = esc_url( $_POST['cite_featured'] ?? '' ); 486 487 $author = array(); 488 $author['name'] = self::explode( sanitize_text_field( $_POST['cite_author_name'] ?? '' ) ); 489 $author['url'] = self::explode( $_POST['cite_author_url'] ?? '' ); 490 if ( is_array( $author['url'] ) ) { 491 $author['url'] = array_map( 'esc_url', $author['url'] ); 598 $cite['category'] = array_values( array_filter( array_map( 'trim', explode( ';', self::posted_text( 'cite_tags' ) ) ) ) ); 599 } 600 $cite['publication'] = self::posted_text( 'cite_publication' ); 601 $cite['featured'] = self::posted_urls( 'cite_featured' ); 602 603 // One h-card per author. Like the other fields, an empty value clears what was stored. 604 $authors = array_map( '\ParseThis\jf2_to_mf2', self::posted_authors() ); 605 if ( empty( $authors ) ) { 606 $cite['author'] = ''; 492 607 } else { 493 $author['url'] = esc_url( $author['url'] ); 494 } 495 496 $author['photo'] = self::explode( $_POST['cite_author_photo'] ?? '' ); 497 498 if ( is_array( $author['photo'] ) ) { 499 $author['photo'] = array_map( 'esc_url', $author['photo'] ); 500 } else { 501 $author['photo'] = esc_url( $author['photo'] ); 502 } 503 504 $author = array_filter( $author ); 505 if ( ! empty( $author ) ) { 506 507 $author['type'] = 'card'; 508 $cite['author'] = jf2_to_mf2( $author ); 509 } 608 $cite['author'] = 1 === count( $authors ) ? $authors[0] : $authors; 609 } 610 510 611 $kind = $kind_post->get_kind(); 511 612 $type = Kind_Taxonomy::get_kind_info( $kind, 'property' ); 512 // Make sure there is no overwrite of properties that might not be handled by the plugin 513 $fetch = $kind_post->get_cite(); 514 if ( ! $fetch ) { 515 $fetch = array(); 516 } else { 517 $fetch = array_filter( $fetch ); 518 } 613 // Make sure there is no overwrite of properties that might not be handled by the plugin. 614 $fetch = self::stored_cite_properties( $kind_post->get_cite() ); 519 615 if ( empty( $_POST['cite_media'] ) ) { 520 616 $cite = array_merge( $fetch, $cite ); … … 531 627 } 532 628 $cite = array( 'properties' => $build ); 533 // Temporary code which assumes everything except a checkin is a citation 629 // Temporary code which assumes everything except a checkin is a citation. 534 630 if ( 'checkin' === $kind ) { 535 631 $cite['type'] = array( 'h-card' ); … … 541 637 } 542 638 } 543 $kind_post->set( $type, $cite ); 639 // Values are unslashed for sanitizing; the metadata API expects them slashed. 640 $kind_post->set( $type, wp_slash( $cite ) ); 641 } 642 643 /** 644 * Returns a posted metabox field as sanitized text. 645 * 646 * Only called from save_post(), after the nonce is verified. 647 * 648 * @since 4.0.0 649 * 650 * @param string $key Field name. 651 * @return string The unslashed, sanitized value, or an empty string if it is missing. 652 */ 653 private static function posted_text( $key ) { 654 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in save_post(). 655 if ( ! isset( $_POST[ $key ] ) || ! is_string( $_POST[ $key ] ) ) { 656 return ''; 657 } 658 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in save_post(). 659 return sanitize_text_field( wp_unslash( $_POST[ $key ] ) ); 660 } 661 662 /** 663 * Returns the properties of a stored citation, keyed by property name. 664 * 665 * Saving merges these with the posted fields, so properties the metabox 666 * does not edit are kept. A citation stored as a plain URL has none. 667 * Before 4.0.0, saving merged the whole stored microformat instead, nesting 668 * it inside its own properties one level deeper on each save; those levels 669 * are flattened here, newer values winning. 670 * 671 * @since 4.0.0 672 * 673 * @param mixed $cite Citation as returned by Kind_Post::get_cite(). 674 * @return array Property values, each a list. 675 */ 676 private static function stored_cite_properties( $cite ) { 677 if ( wp_is_numeric_array( $cite ) && 1 === count( $cite ) ) { 678 $cite = $cite[0]; 679 } 680 if ( ! is_array( $cite ) || empty( $cite['properties'] ) || ! is_array( $cite['properties'] ) ) { 681 return array(); 682 } 683 return array_filter( Kind_Upgrade::flatten_properties( $cite['properties'] ) ); 684 } 685 686 /** 687 * Returns a posted metabox URL field, sanitized. 688 * 689 * Only called from save_post(), after the nonce is verified. 690 * 691 * @since 4.0.0 692 * 693 * @param string $key Field name. 694 * @return string The URL, or an empty string if it is not valid. 695 */ 696 private static function posted_urls( $key ) { 697 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in save_post(). 698 if ( ! isset( $_POST[ $key ] ) || ! is_string( $_POST[ $key ] ) ) { 699 return ''; 700 } 701 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verified in save_post(); sanitized with sanitize_url() (sanitize_text_field() would alter %-encoding). 702 return sanitize_url( trim( wp_unslash( $_POST[ $key ] ) ) ); 703 } 704 705 /** 706 * Returns the authors posted in the metabox's Author tab, as jf2 h-cards. 707 * 708 * Each field holds one value per author, separated by semicolons, so the 709 * first name goes with the first URL and the first photo, and so on. An 710 * author without a URL leaves that position empty, as in "Ann; Bob" and 711 * "; https://example.com/bob". 712 * 713 * Only called from save_post(), after the nonce is verified. 714 * 715 * @since 4.0.0 716 * 717 * @return array[] The authors, each with type 'card' and any of name, url and photo. 718 */ 719 private static function posted_authors() { 720 $fields = array(); 721 foreach ( array( 722 'name' => 'cite_author_name', 723 'url' => 'cite_author_url', 724 'photo' => 'cite_author_photo', 725 ) as $property => $key ) { 726 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verified in save_post(); each value is sanitized below. 727 $value = isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ? wp_unslash( $_POST[ $key ] ) : ''; 728 $values = array_map( 'trim', explode( ';', $value ) ); 729 730 $fields[ $property ] = array_map( 'name' === $property ? 'sanitize_text_field' : 'sanitize_url', $values ); 731 } 732 733 $authors = array(); 734 $count = max( array_map( 'count', $fields ) ); 735 for ( $i = 0; $i < $count; $i++ ) { 736 $author = array(); 737 foreach ( $fields as $property => $values ) { 738 if ( isset( $values[ $i ] ) && '' !== $values[ $i ] ) { 739 $author[ $property ] = $values[ $i ]; 740 } 741 } 742 if ( $author ) { 743 $authors[] = array_merge( array( 'type' => 'card' ), $author ); 744 } 745 } 746 return $authors; 544 747 } 545 748 546 749 /** 547 750 * Function to handle saving our kind data upon post status transition. 751 * 752 * @since 2.2.0 548 753 * 549 754 * @access public -
indieweb-post-kinds/trunk/includes/class-kind-plugins.php
r2973430 r3738687 1 1 <?php 2 3 2 /** 4 * Post Kind Plugins Class3 * Kind_Plugins class. 5 4 * 6 * Custom Functions for Specific Other Pugins 5 * @package Post_Kinds 6 * @since 2.5.2 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Integrations with other plugins, such as Micropub and ActivityPub. 7 13 * 8 * @ package Post Kinds14 * @since 2.5.2 9 15 */ 10 16 class Kind_Plugins { 11 17 12 18 /** 19 * WP-Cron hook that retries parsing a Micropub citation in the background. 20 * 21 * @var string 22 */ 23 const ENRICH_EVENT = 'post_kinds_enrich_citation'; 24 25 /** 26 * How many times a citation is parsed after temporary failures, counting the 27 * attempt during the request. 28 * 29 * @var int 30 */ 31 const ENRICH_ATTEMPTS = 3; 32 33 /** 34 * Post meta holding the log of citation parsing outcomes for a post. 35 * 36 * @var string 37 */ 38 const LOG_META = '_kind_citation_log'; 39 40 /** 41 * Post meta holding the post's citation status: retrying or failed. It is 42 * removed when no citation is retrying or failed. 43 * 44 * @var string 45 */ 46 const STATUS_META = '_kind_citation_status'; 47 48 /** 49 * How many log entries are kept per post. 50 * 51 * @var int 52 */ 53 const LOG_LIMIT = 20; 54 55 /** 56 * Citation URLs from the current Micropub request that could not be parsed, 57 * as arrays of property, URL and WP_Error, waiting for the post ID. 58 * 59 * @var array[] 60 */ 61 private static $pending = array(); 62 63 /** 13 64 * Initialize our plugin integrations. 14 65 * 66 * @since 2.5.2 67 * 15 68 * @access public 16 69 */ 17 70 public static function init() { 18 // Set Post Kind for Micropub Inputs. 71 // Set the post kind for Micropub posts before they are inserted (Micropub 2.1.0 72 // and later), with after_micropub as a fallback. 73 add_filter( 'micropub_tax_input', array( static::class, 'micropub_tax_input' ), 10, 2 ); 19 74 add_action( 'after_micropub', array( static::class, 'micropub_set_kind' ), 9, 2 ); 20 75 add_action( 'after_micropub', array( static::class, 'post_formats' ), 11, 2 ); 21 76 add_filter( 'before_micropub', array( static::class, 'micropub_parse' ), 11 ); 77 add_action( 'after_micropub', array( static::class, 'schedule_enrichment' ), 20, 2 ); 78 add_action( self::ENRICH_EVENT, array( static::class, 'enrich_citation' ), 10, 4 ); 79 // Advertise the enabled kinds to Micropub clients (Micropub 1.4 and later). 80 add_filter( 'micropub_query', array( static::class, 'micropub_query' ), 10, 2 ); 22 81 add_filter( 'tempus_widget_post_title', array( static::class, 'tempus_widget_post_title' ), 10, 2 ); 23 82 // Override Post Type in Semantic Linkbacks. 24 83 add_filter( 'semantic_linkbacks_post_type', array( static::class, 'semantic_post_type' ), 11, 2 ); 25 84 26 // Remove the Automatic Post Generation that the Micropub Plugin Offers 27 if ( class_exists( 'Micropub_Render' ) ) { 28 if ( has_filter( 'micropub_post_content', array( 'Micropub_Render', 'generate_post_content' ) ) ) { 29 remove_filter( 'micropub_post_content', array( 'Micropub_Render', 'generate_post_content' ), 1, 2 ); 30 } 31 } elseif ( class_exists( 'Micropub_Plugin' ) ) { 32 if ( has_filter( 'micropub_post_content', array( 'Micropub_Plugin', 'generate_post_content' ) ) ) { 33 remove_filter( 'micropub_post_content', array( 'Micropub_Plugin', 'generate_post_content' ), 1, 2 ); 34 } 35 } 36 37 // Hum Compatibility Filters 85 // Post Kinds displays the response itself, so turn off Micropub's dynamic 86 // rendering of Micropub posts (Micropub 2.4.0 and later). 87 add_filter( 'micropub_dynamic_render', '__return_false' ); 88 89 // Hum Compatibility Filters. 38 90 add_action( 'hum_local_types', array( static::class, 'hum_local_types' ), 11 ); 39 91 add_action( 'hum_type_prefix', array( static::class, 'hum_type_prefix' ), 11, 2 ); 40 92 41 // ActivityPub Filters 93 // ActivityPub Filters. 42 94 add_filter( 'activitypub_post', array( static::class, 'activitypub_post' ), 12 ); 43 95 } … … 47 99 * Construct a title for the Tempus Widget link. 48 100 * 101 * @since 3.5.4 102 * 49 103 * @access public 50 104 * 51 * @param string $title The Original Title.52 * @param WP_Post $post Post object.105 * @param string $title The Original Title. 106 * @param WP_Post $post Post object. 53 107 * @return string 54 108 */ … … 58 112 59 113 /** 60 * 61 * @param array $post_array 114 * Sets the ActivityPub object type based on the post kind. 115 * 116 * @since 3.6.2 117 * 118 * @param array $post_array ActivityPub object array. 62 119 * 63 120 * @return array … … 93 150 } 94 151 152 /** 153 * Adds the kinds' shortlink type codes to the Hum plugin. 154 * 155 * @since 3.4.0 156 * 157 * @param string[] $types Local type codes. 158 * @return string[] Local type codes. 159 */ 95 160 public static function hum_local_types( $types ) { 96 161 // http://tantek.pbworks.com/w/page/21743973/Whistle#design - Some of the uses are modified based on design considerations noted. 97 $types[] = 'f'; // Favorited, Likes, etc 98 $types[] = 'e'; // Events 99 $types[] = 'g'; // Geo Checkin 100 $types[] = 'h'; // Link 101 $types[] = 'm'; // Metric 102 $types[] = 'q'; // Question 103 $types[] = 'r'; // Review 104 $types[] = 'x'; // Experience 105 $types[] = 'u'; // Status Update 162 $types[] = 'f'; // Favorited, Likes, etc. 163 $types[] = 'e'; // Events. 164 $types[] = 'g'; // Geo Checkin. 165 $types[] = 'h'; // Link. 166 $types[] = 'm'; // Metric. 167 $types[] = 'q'; // Question. 168 $types[] = 'r'; // Review. 169 $types[] = 'x'; // Experience. 170 $types[] = 'u'; // Status Update. 106 171 return $types; 107 172 } 108 173 174 /** 175 * Uses the kind's shortlink code as a post's Hum type prefix. 176 * 177 * @since 3.4.0 178 * 179 * @param string $prefix Type prefix. 180 * @param int $post_id Post ID. 181 * @return string The kind's shortlink code, or the prefix if it has none. 182 */ 109 183 public static function hum_type_prefix( $prefix, $post_id ) { 110 184 $post_type = get_post_type( $post_id ); … … 124 198 * Replaces need for replacing the entire excerpt. 125 199 * 200 * @since 2.5.2 201 * 126 202 * @access public 127 203 * … … 135 211 136 212 /** 137 * Take mf2 properties and set a post kind. 138 * Implements Post Type Discovery https://www.w3.org/TR/post-type-discovery/ 139 * 140 * @param array $input Micropub Request in JSON. 141 * @param array $wp_args Arguments passed to insert or update posts. 213 * Returns the post kind for an mf2 object, using Post Type Discovery. 214 * Implements https://www.w3.org/TR/post-type-discovery/ via Parse This. 215 * 216 * @since 4.0.0 217 * 218 * @param array $mf2 mf2 object, such as a Micropub request. 219 * @return string Registered kind slug, or an empty string if none applies. 220 */ 221 public static function discover_kind( $mf2 ) { 222 if ( ! is_array( $mf2 ) || empty( $mf2['properties'] ) || ! function_exists( '\\ParseThis\\post_type_discovery' ) ) { 223 return ''; 224 } 225 $type = \ParseThis\post_type_discovery( \ParseThis\mf2_to_jf2( $mf2 ) ); 226 return ( $type && Kind_Taxonomy::get_post_kind_info( $type ) ) ? $type : ''; 227 } 228 229 /** 230 * Sets the kind of a Micropub post in the taxonomy input, so the post is 231 * inserted with its kind rather than the default term. Everything that runs 232 * during the insert, such as the publish transition, post formats and other 233 * plugins' publish hooks, then sees the right kind. 234 * 235 * Hooked to Micropub's micropub_tax_input filter (Micropub 2.1.0 and later), 236 * which is applied when a post is created. 237 * 238 * @since 4.0.0 239 * 240 * @param array|null $tax_input Taxonomy input for the new post. 241 * @param array $input Micropub request. 242 * @return array|null Taxonomy input, with the kind added. 243 */ 244 public static function micropub_tax_input( $tax_input, $input ) { 245 $kind = self::discover_kind( $input ); 246 if ( ! $kind ) { 247 return $tax_input; 248 } 249 if ( ! is_array( $tax_input ) ) { 250 $tax_input = array(); 251 } 252 if ( empty( $tax_input['kind'] ) ) { 253 $tax_input['kind'] = array( $kind ); 254 } 255 return $tax_input; 256 } 257 258 /** 259 * Sets the kind of a Micropub post after it is created, if it does not 260 * already have it. 261 * 262 * The kind is normally set before the insert (see micropub_tax_input()). This 263 * covers Micropub versions before 2.1.0, and users who cannot assign kind 264 * terms, for whom WordPress ignores the taxonomy input. 265 * 266 * @since 2.5.2 267 * 268 * @param array $input Micropub request. 269 * @param array|null $wp_args Arguments of the created or updated post; null for queries. 142 270 */ 143 271 public static function micropub_set_kind( $input, $wp_args ) { 144 // Only continue if create or update 145 if ( ! $wp_args ) { 272 if ( empty( $wp_args['ID'] ) ) { 146 273 return; 147 274 } 148 $type = post_type_discovery( mf2_to_jf2( $input ) ); 149 if ( ! empty( $type ) ) { 150 set_post_kind( $wp_args['ID'], $type ); 151 } 152 } 153 154 /** 155 * Set our post formats. 275 $post_id = (int) $wp_args['ID']; 276 if ( isset( $input['action'] ) && 'update' === $input['action'] ) { 277 // Only recalculate when the update changed a property that decides the 278 // kind, so a kind chosen in the editor survives unrelated updates. 279 if ( ! self::update_changes_kind( $input ) ) { 280 return; 281 } 282 $kind = self::discover_kind( self::stored_mf2( $post_id ) ); 283 } else { 284 $kind = self::discover_kind( $input ); 285 } 286 if ( $kind && get_post_kind_slug( $post_id ) !== $kind ) { 287 self::set_kind_without_moving_properties( $post_id, $kind ); 288 } 289 } 290 291 /** 292 * Returns the properties that decide a post's kind: each registered kind's 293 * property, plus rsvp, which Post Type Discovery checks first. 294 * 295 * @since 4.0.0 296 * 297 * @return string[] Property names. 298 */ 299 public static function kind_properties() { 300 $properties = array( 'rsvp' ); 301 foreach ( Kind_Taxonomy::get_kind_list() as $kind ) { 302 $property = Kind_Taxonomy::get_kind_info( $kind, 'property' ); 303 if ( $property ) { 304 $properties[] = $property; 305 } 306 } 307 return array_values( array_unique( $properties ) ); 308 } 309 310 /** 311 * Whether a Micropub update request replaces, adds or deletes a property 312 * that decides the kind. 313 * 314 * @since 4.0.0 315 * 316 * @param array $input Micropub update request. 317 * @return bool 318 */ 319 public static function update_changes_kind( $input ) { 320 $changed = array(); 321 foreach ( array( 'replace', 'add' ) as $operation ) { 322 if ( isset( $input[ $operation ] ) && is_array( $input[ $operation ] ) ) { 323 $changed = array_merge( $changed, array_keys( $input[ $operation ] ) ); 324 } 325 } 326 if ( isset( $input['delete'] ) && is_array( $input['delete'] ) ) { 327 // Either a list of property names, or an object of property => values. 328 $changed = array_merge( $changed, wp_is_numeric_array( $input['delete'] ) ? $input['delete'] : array_keys( $input['delete'] ) ); 329 } 330 return (bool) array_intersect( $changed, self::kind_properties() ); 331 } 332 333 /** 334 * Builds the mf2 for a post from what is stored: the mf2_ post meta, plus the 335 * title and content, which Post Type Discovery uses to tell articles from notes. 336 * 337 * @since 4.0.0 338 * 339 * @param int $post_id Post ID. 340 * @return array mf2 object. 341 */ 342 public static function stored_mf2( $post_id ) { 343 $properties = array(); 344 foreach ( get_post_meta( $post_id ) as $key => $values ) { 345 if ( 0 !== strpos( $key, 'mf2_' ) || 'mf2_type' === $key ) { 346 continue; 347 } 348 $value = maybe_unserialize( $values[0] ); 349 if ( '' === $value || array() === $value ) { 350 continue; 351 } 352 $properties[ substr( $key, 4 ) ] = is_array( $value ) ? $value : array( $value ); 353 } 354 $post = get_post( $post_id ); 355 if ( $post && '' !== $post->post_title ) { 356 $properties['name'] = array( $post->post_title ); 357 } 358 if ( $post && '' !== $post->post_content ) { 359 $properties['content'] = array( $post->post_content ); 360 } 361 return array( 362 'type' => array( 'h-entry' ), 363 'properties' => $properties, 364 ); 365 } 366 367 /** 368 * Sets a post's kind without moving property values between properties. 369 * 370 * When the kind changes in the editor, Kind_Metabox::change_kind() moves the 371 * old kind's property to the new one (a like-of becomes an in-reply-to, for 372 * example). After a Micropub update the properties are already where the 373 * client put them, so that is suspended here. 374 * 375 * @since 4.0.0 376 * 377 * @param int $post_id Post ID. 378 * @param string $kind Kind slug. 379 */ 380 private static function set_kind_without_moving_properties( $post_id, $kind ) { 381 $moving = has_action( 'change_kind', array( 'Kind_Metabox', 'change_kind' ) ); 382 if ( false !== $moving ) { 383 remove_action( 'change_kind', array( 'Kind_Metabox', 'change_kind' ), $moving ); 384 } 385 set_post_kind( $post_id, $kind ); 386 if ( false !== $moving ) { 387 add_action( 'change_kind', array( 'Kind_Metabox', 'change_kind' ), $moving, 3 ); 388 } 389 } 390 391 /** 392 * Sets the post format of a post created or updated by Micropub to match its kind. 393 * 394 * @since 2.5.2 156 395 * 157 396 * @access public 158 397 * 159 * @param $input160 * @param $wp_args398 * @param array $input The Micropub request. 399 * @param array $wp_args Arguments passed to wp_insert_post() or wp_update_post(). 161 400 */ 162 401 public static function post_formats( $input, $wp_args ) { … … 169 408 170 409 /** 171 * Parse our micropub values. 410 * Returns the Micropub properties whose URL values are enriched into citations. 411 * 412 * @since 4.0.0 413 * 414 * @return string[] Property names. 415 */ 416 public static function citation_properties() { 417 return array( 'bookmark-of', 'like-of', 'favorite-of', 'in-reply-to', 'read-of', 'listen-of', 'watch-of' ); 418 } 419 420 /** 421 * Enriches the URLs in citation properties of a Micropub create request, or 422 * of the replace part of an update request, into h-cite objects parsed from 423 * the cited page. 424 * 425 * Follows the Micropub spec: property values are arrays, so a property whose 426 * value is not an array is left for Micropub to handle, and only string URLs 427 * inside the array are enriched. Values the client already sent as objects, 428 * such as an h-cite, are kept as sent. 429 * 430 * @since 2.7.5 172 431 * 173 432 * @access public 174 433 * 175 * @param array $input Array of inputs to parse.176 * @return mixed434 * @param array $input Micropub request, in mf2 JSON. 435 * @return array The request, with citation URLs enriched where possible. 177 436 */ 178 437 public static function micropub_parse( $input ) { 179 if ( ! $input ) { 438 self::$pending = array(); 439 // Queries (q) are left as is. 440 if ( ! is_array( $input ) || isset( $input['q'] ) ) { 180 441 return $input; 181 442 } 182 // q indicates a get query 183 if ( isset( $input['q'] ) ) { 443 if ( ! class_exists( '\\ParseThis\\Parser' ) || ! self::enrichment_enabled( $input ) ) { 184 444 return $input; 185 445 } 186 if ( ! isset( $input['properties'] ) ) { 187 return $input; 188 } 189 $parsed = array( 'bookmark-of', 'like-of', 'favorite-of', 'in-reply-to', 'read-of', 'listen-of', 'watch-of' ); 190 foreach ( $input['properties'] as $property => $value ) { 191 if ( in_array( $property, $parsed, true ) ) { 192 if ( wp_is_numeric_array( $value ) ) { 193 foreach ( $value as $i => $v ) { 194 if ( wp_http_validate_url( $v ) ) { 195 $parse = new Parse_This( $v ); 196 $fetch = $parse->fetch(); 197 if ( ! is_wp_error( $fetch ) ) { 198 $parse->parse(); 199 $jf2 = $parse->get(); 200 // Entries become citations 201 if ( 'entry' === $jf2['type'] ) { 202 $jf2['type'] = 'cite'; 203 } 204 $mf2 = jf2_to_mf2( $jf2 ); 205 $input['properties'][ $property ][ $i ] = $mf2; 206 } else { 207 error_log( wp_json_encode( $fetch ) ); // phpcs:ignore 208 } 209 } 210 } 211 } elseif ( isset( $value['url'] ) && wp_http_validate_url( $value['url'] ) ) { 212 $parse = new Parse_This( $value['url'] ); 213 $fetch = $parse - fetch(); 214 if ( ! is_wp_error( $fetch ) ) { 215 $parse->parse(); 216 $input['properties'][ $property ] = array_merge( $value, jf2_to_mf2( $parse->get() ) ); 446 // Create requests carry properties. Update requests can replace citation 447 // properties; Micropub only allows adding categories, syndication and media. 448 if ( isset( $input['properties'] ) && is_array( $input['properties'] ) ) { 449 $input['properties'] = self::enrich_properties( $input['properties'] ); 450 } 451 if ( isset( $input['action'] ) && 'update' === $input['action'] && isset( $input['replace'] ) && is_array( $input['replace'] ) ) { 452 $input['replace'] = self::enrich_properties( $input['replace'] ); 453 } 454 return $input; 455 } 456 457 /** 458 * Enriches the citation URLs in a set of Micropub properties. 459 * 460 * @since 4.0.0 461 * 462 * @param array $properties Properties, keyed by name, each an array of values. 463 * @return array The properties, with citation URLs enriched where possible. 464 */ 465 private static function enrich_properties( $properties ) { 466 foreach ( self::citation_properties() as $property ) { 467 if ( empty( $properties[ $property ] ) || ! wp_is_numeric_array( $properties[ $property ] ) ) { 468 continue; 469 } 470 foreach ( $properties[ $property ] as $i => $value ) { 471 if ( ! is_string( $value ) || ! wp_http_validate_url( $value ) ) { 472 continue; 473 } 474 // Keep the client waiting only briefly: a short timeout and no follow-up 475 // requests. Failures are logged, and retried in the background where 476 // worthwhile, once the post exists (see schedule_enrichment()). 477 $cite = self::parse_citation( $value, self::request_parse_args() ); 478 if ( is_wp_error( $cite ) ) { 479 self::report_failure( $value, $cite ); 480 self::$pending[] = array( $property, $value, $cite ); 481 continue; 482 } 483 $properties[ $property ][ $i ] = $cite; 484 } 485 } 486 return $properties; 487 } 488 489 /** 490 * Fetches and parses a URL into an mf2 citation. 491 * 492 * @since 4.0.0 493 * 494 * @access public 495 * 496 * @param string $url URL of the cited page. 497 * @param array $args { 498 * Optional. Limits for this parse. 499 * 500 * @type int $timeout HTTP timeout in seconds. Default: Parse This's own (15). 501 * @type int $max_requests Follow-up requests allowed. Default: Parse This's own (10). 502 * } 503 * @return array|WP_Error mf2 citation, or WP_Error if the page could not be fetched or parsed. 504 */ 505 public static function parse_citation( $url, $args = array() ) { 506 $timeout = isset( $args['timeout'] ) ? (int) $args['timeout'] : 0; 507 $max_requests = isset( $args['max_requests'] ) ? (int) $args['max_requests'] : null; 508 $limit_timeout = static function ( $request ) use ( $timeout ) { 509 $request['timeout'] = isset( $request['timeout'] ) ? min( (float) $request['timeout'], $timeout ) : $timeout; 510 return $request; 511 }; 512 $limit_budget = static function () use ( $max_requests ) { 513 return $max_requests; 514 }; 515 if ( $timeout > 0 ) { 516 add_filter( 'http_request_args', $limit_timeout, 99 ); 517 } 518 if ( null !== $max_requests ) { 519 add_filter( 'parse_this_max_requests', $limit_budget, 99 ); 520 } 521 try { 522 $parse = new \ParseThis\Parser( $url ); 523 $fetch = $parse->fetch(); 524 if ( is_wp_error( $fetch ) ) { 525 return $fetch; 526 } 527 // Keep nested objects in place rather than moving them to refs. 528 $parse->parse( array( 'references' => false ) ); 529 $jf2 = $parse->get(); 530 } finally { 531 remove_filter( 'http_request_args', $limit_timeout, 99 ); 532 remove_filter( 'parse_this_max_requests', $limit_budget, 99 ); 533 } 534 if ( ! is_array( $jf2 ) || empty( $jf2 ) ) { 535 return new WP_Error( 'empty', __( 'Nothing could be parsed from the URL.', 'indieweb-post-kinds' ) ); 536 } 537 // Content Parse This did not recognize is returned whole under raw. 538 unset( $jf2['raw'] ); 539 $type = isset( $jf2['type'] ) ? $jf2['type'] : ''; 540 // A page that parses as a feed (a home page, for example) is cited as the 541 // page itself, not its entries. 542 if ( 'feed' === $type ) { 543 $jf2 = array_intersect_key( $jf2, array_flip( array( 'name', 'url', 'author', 'summary', 'photo' ) ) ); 544 } 545 $jf2 = self::clean_citation( $jf2 ); 546 // Entries and feeds become citations. Other types, such as an h-event for 547 // an RSVP, are kept. 548 $jf2['type'] = in_array( $type, array( '', 'entry', 'feed' ), true ) ? 'cite' : $type; 549 return \ParseThis\jf2_to_mf2( $jf2 ); 550 } 551 552 /** 553 * Whether to enrich citation URLs in Micropub requests. 554 * 555 * @since 4.0.0 556 * 557 * @param array|null $input Micropub request, or null outside a request. 558 * @return bool 559 */ 560 public static function enrichment_enabled( $input = null ) { 561 /** 562 * Filters whether Post Kinds enriches citation URLs in Micropub requests 563 * (in-reply-to, like-of, and so on) with data parsed from the cited page. 564 * 565 * Return false to store citations exactly as the client sent them. 566 * 567 * @since 4.0.0 568 * 569 * @param bool $enabled Whether to enrich citations. Default true. 570 * @param array|null $input Micropub request, or null when a background retry runs. 571 */ 572 return (bool) apply_filters( 'post_kinds_micropub_enrich', true, $input ); 573 } 574 575 /** 576 * Returns the limits for parsing citations during a Micropub request. 577 * 578 * @since 4.0.0 579 * 580 * @return array Arguments for parse_citation(). 581 */ 582 public static function request_parse_args() { 583 /** 584 * Filters the HTTP timeout, in seconds, for parsing a citation while a 585 * Micropub client waits. Citations that time out are retried in the 586 * background. 587 * 588 * @since 4.0.0 589 * 590 * @param int $timeout Timeout in seconds. Default 5. 591 */ 592 $timeout = (int) apply_filters( 'post_kinds_micropub_parse_timeout', 5 ); 593 return array( 594 'timeout' => max( 1, $timeout ), 595 'max_requests' => 0, 596 ); 597 } 598 599 /** 600 * Classifies a parse failure to decide whether to retry it. 601 * 602 * @since 4.0.0 603 * 604 * @param WP_Error $error Parse failure. 605 * @return string One of: 606 * 'temporary' A connection problem or timeout, a server error or rate limiting. 607 * 'unavailable' The page was missing or refused (HTTP 404, 401 or 403), which 608 * may be an outage or a configuration problem on the cited site. 609 * 'permanent' Anything else, such as content that cannot be parsed. 610 */ 611 public static function failure_type( $error ) { 612 if ( 'http_request_failed' === $error->get_error_code() ) { 613 return 'temporary'; 614 } 615 $code = self::failure_response_code( $error ); 616 if ( 429 === $code || $code >= 500 ) { 617 return 'temporary'; 618 } 619 if ( in_array( $code, array( 401, 403, 404 ), true ) ) { 620 return 'unavailable'; 621 } 622 return 'permanent'; 623 } 624 625 /** 626 * Returns the HTTP status code of a parse failure, if there was one. 627 * 628 * @since 4.0.0 629 * 630 * @param WP_Error $error Parse failure. 631 * @return int HTTP status code, or 0. 632 */ 633 private static function failure_response_code( $error ) { 634 $data = $error->get_error_data(); 635 return is_array( $data ) && isset( $data['response_code'] ) ? (int) $data['response_code'] : 0; 636 } 637 638 /** 639 * Returns how long to wait before retrying a failed citation, if at all. 640 * 641 * Temporary failures are retried after a minute, then after 15 minutes, up 642 * to ENRICH_ATTEMPTS attempts in all. A missing or refused page is retried 643 * once, after some hours, in case the cited site was down or misconfigured. 644 * Other failures are not retried. 645 * 646 * @since 4.0.0 647 * 648 * @param int $post_id Post ID. 649 * @param string $url Cited URL. 650 * @param WP_Error $error The failure. 651 * @param int $attempt Which attempt failed. 652 * @return int Seconds to wait, or 0 for no retry. 653 */ 654 public static function retry_delay( $post_id, $url, $error, $attempt ) { 655 switch ( self::failure_type( $error ) ) { 656 case 'temporary': 657 if ( $attempt >= self::ENRICH_ATTEMPTS ) { 658 return 0; 659 } 660 return 1 === $attempt ? MINUTE_IN_SECONDS : 15 * MINUTE_IN_SECONDS; 661 case 'unavailable': 662 // Only once per citation. 663 foreach ( self::get_citation_log( $post_id ) as $entry ) { 664 if ( $url === $entry['url'] && 'unavailable' === $entry['type'] ) { 665 return 0; 217 666 } 218 667 } 219 } 220 } 221 return $input; 668 /** 669 * Filters how long to wait before retrying, once, a citation whose page 670 * was missing or refused (HTTP 404, 401 or 403). 671 * 672 * @since 4.0.0 673 * 674 * @param int $delay Delay in seconds. Default 6 hours. 675 * @param string $url Cited URL. 676 * @param WP_Error $error The failure. 677 */ 678 return max( 0, (int) apply_filters( 'post_kinds_micropub_unavailable_retry_delay', 6 * HOUR_IN_SECONDS, $url, $error ) ); 679 default: 680 return 0; 681 } 682 } 683 684 /** 685 * Reports a citation that could not be parsed. Only shown when WP_DEBUG is on. 686 * 687 * @since 4.0.0 688 * 689 * @param string $url Cited URL. 690 * @param WP_Error $error Parse failure. 691 */ 692 public static function report_failure( $url, $error ) { 693 wp_trigger_error( 694 __METHOD__, 695 sprintf( 'Could not parse %1$s for a Micropub citation: %2$s', esc_url( $url ), esc_html( $error->get_error_message() ) ) 696 ); 697 } 698 699 /** 700 * Logs and, where worthwhile, schedules a retry for citations that could not 701 * be parsed during a Micropub create or update request, once the post ID is known. 702 * 703 * @since 4.0.0 704 * 705 * @param array $input Micropub request. 706 * @param array|null $wp_args Arguments of the created or updated post. 707 */ 708 public static function schedule_enrichment( $input, $wp_args ) { 709 $pending = self::$pending; 710 self::$pending = array(); 711 if ( empty( $pending ) || empty( $wp_args['ID'] ) ) { 712 return; 713 } 714 foreach ( $pending as $citation ) { 715 self::handle_failure( (int) $wp_args['ID'], $citation[0], $citation[1], 1, $citation[2] ); 716 } 717 } 718 719 /** 720 * Logs a failed attempt and schedules a retry if one is worthwhile. 721 * 722 * @since 4.0.0 723 * 724 * @param int $post_id Post ID. 725 * @param string $property Micropub property, such as in-reply-to. 726 * @param string $url Cited URL. 727 * @param int $attempt Which attempt failed. 728 * @param WP_Error $error The failure. 729 */ 730 private static function handle_failure( $post_id, $property, $url, $attempt, $error ) { 731 // Work out the delay before logging, so the log holds only earlier failures. 732 $delay = self::retry_delay( $post_id, $url, $error, $attempt ); 733 $retry_at = $delay ? time() + $delay : 0; 734 self::log_outcome( $post_id, $property, $url, $attempt, $error, $retry_at ); 735 if ( $retry_at ) { 736 $args = array( $post_id, $property, $url, $attempt + 1 ); 737 if ( ! wp_next_scheduled( self::ENRICH_EVENT, $args ) ) { 738 wp_schedule_single_event( $retry_at, self::ENRICH_EVENT, $args ); 739 } 740 } 741 } 742 743 /** 744 * Parses a Micropub citation in the background and stores the result. 745 * 746 * The citation is only replaced if the post still holds the URL as it was 747 * stored, so later edits are not overwritten. 748 * 749 * @since 4.0.0 750 * 751 * @param int $post_id Post ID. 752 * @param string $property Micropub property, such as in-reply-to. 753 * @param string $url Cited URL. 754 * @param int $attempt Which attempt this is. 755 */ 756 public static function enrich_citation( $post_id, $property, $url, $attempt ) { 757 if ( ! get_post( $post_id ) || ! class_exists( '\\ParseThis\\Parser' ) || ! self::enrichment_enabled() ) { 758 return; 759 } 760 $key = 'mf2_' . $property; 761 $values = get_post_meta( $post_id, $key, true ); 762 $index = is_array( $values ) ? array_search( $url, $values, true ) : false; 763 if ( false === $index ) { 764 self::log_outcome( $post_id, $property, $url, $attempt, 'skipped', 0 ); 765 return; 766 } 767 $cite = self::parse_citation( $url ); 768 if ( is_wp_error( $cite ) ) { 769 self::report_failure( $url, $cite ); 770 self::handle_failure( $post_id, $property, $url, $attempt, $cite ); 771 return; 772 } 773 $values[ $index ] = $cite; 774 update_post_meta( $post_id, $key, $values ); 775 self::log_outcome( $post_id, $property, $url, $attempt, 'enriched', 0 ); 776 } 777 778 /** 779 * Returns the log of citation parsing outcomes for a post, oldest first. 780 * 781 * Each entry is an array with these keys: 782 * - time Unix timestamp of the attempt. 783 * - property Micropub property, such as in-reply-to. 784 * - url Cited URL. 785 * - attempt Attempt number; 1 is the attempt during the Micropub request. 786 * - result 'enriched', 'failed', or 'skipped' (the citation was edited since). 787 * - type For failures: 'temporary', 'unavailable' or 'permanent'. Otherwise empty. 788 * - code For failures: the WP_Error code. Otherwise empty. 789 * - response_code For failures: the HTTP status code, or 0. 790 * - message For failures: the error message. Otherwise empty. 791 * - retry Unix timestamp of the scheduled retry, or 0 if none. 792 * 793 * @since 4.0.0 794 * 795 * @param int $post_id Post ID. 796 * @return array[] Log entries. 797 */ 798 public static function get_citation_log( $post_id ) { 799 $log = get_post_meta( $post_id, self::LOG_META, true ); 800 return is_array( $log ) ? $log : array(); 801 } 802 803 /** 804 * Adds an outcome to a post's citation log and updates its citation status. 805 * 806 * @since 4.0.0 807 * 808 * @param int $post_id Post ID. 809 * @param string $property Micropub property. 810 * @param string $url Cited URL. 811 * @param int $attempt Attempt number. 812 * @param WP_Error|string $result The failure, or 'enriched' or 'skipped'. 813 * @param int $retry_at Timestamp of the scheduled retry, or 0. 814 */ 815 private static function log_outcome( $post_id, $property, $url, $attempt, $result, $retry_at ) { 816 $failed = is_wp_error( $result ); 817 $entry = array( 818 'time' => time(), 819 'property' => $property, 820 'url' => $url, 821 'attempt' => (int) $attempt, 822 'result' => $failed ? 'failed' : $result, 823 'type' => $failed ? self::failure_type( $result ) : '', 824 'code' => $failed ? $result->get_error_code() : '', 825 'response_code' => $failed ? self::failure_response_code( $result ) : 0, 826 'message' => $failed ? $result->get_error_message() : '', 827 'retry' => (int) $retry_at, 828 ); 829 $log = self::get_citation_log( $post_id ); 830 $log[] = $entry; 831 $log = array_slice( $log, -self::LOG_LIMIT ); 832 update_post_meta( $post_id, self::LOG_META, $log ); 833 834 // The status reflects the latest outcome for each cited URL. 835 $latest = array(); 836 foreach ( $log as $item ) { 837 $latest[ $item['url'] ] = $item; 838 } 839 $status = ''; 840 foreach ( $latest as $item ) { 841 if ( 'failed' !== $item['result'] ) { 842 continue; 843 } 844 if ( ! $item['retry'] ) { 845 $status = 'failed'; 846 break; 847 } 848 $status = 'retrying'; 849 } 850 if ( $status ) { 851 update_post_meta( $post_id, self::STATUS_META, $status ); 852 } else { 853 delete_post_meta( $post_id, self::STATUS_META ); 854 } 855 856 /** 857 * Fires after each attempt to parse a Micropub citation. 858 * 859 * @since 4.0.0 860 * 861 * @param array $entry The log entry. See Kind_Plugins::get_citation_log(). 862 * @param int $post_id Post ID. 863 */ 864 do_action( 'post_kinds_citation_enrichment', $entry, $post_id ); 865 } 866 867 /** 868 * Removes Parse This bookkeeping from parsed jf2, recursively. 869 * 870 * Keys starting with an underscore (_code, _links, _alternate, 871 * _source_format, ...), post-type and refs are not microformats properties, 872 * and would otherwise be stored in post meta and returned by Micropub's 873 * q=source. 874 * 875 * @since 4.0.0 876 * 877 * @access public 878 * 879 * @param mixed $jf2 Parsed jf2, or any value within it. 880 * @return mixed The value without bookkeeping keys. 881 */ 882 public static function clean_citation( $jf2 ) { 883 if ( ! is_array( $jf2 ) ) { 884 return $jf2; 885 } 886 foreach ( array_keys( $jf2 ) as $key ) { 887 if ( is_string( $key ) && ( 0 === strpos( $key, '_' ) || in_array( $key, array( 'post-type', 'refs' ), true ) ) ) { 888 unset( $jf2[ $key ] ); 889 } elseif ( is_array( $jf2[ $key ] ) ) { 890 $jf2[ $key ] = self::clean_citation( $jf2[ $key ] ); 891 } 892 } 893 return $jf2; 894 } 895 /** 896 * Adds the enabled kinds to Micropub's configuration query, using the 897 * post-types extension (Query for Supported Vocabulary), so clients only 898 * offer the kinds this site uses. 899 * 900 * @since 4.0.0 901 * 902 * @see https://indieweb.org/Micropub-extensions#Query_for_Supported_Vocabulary 903 * 904 * @param array|mixed $response Micropub query response. 905 * @param array $input Micropub query. 906 * @return array|mixed The response, with post-types added for q=config. 907 */ 908 public static function micropub_query( $response, $input ) { 909 if ( ! is_array( $response ) || ! isset( $input['q'] ) || 'config' !== $input['q'] ) { 910 return $response; 911 } 912 $types = isset( $response['post-types'] ) && is_array( $response['post-types'] ) ? $response['post-types'] : array(); 913 $known = wp_list_pluck( $types, 'type' ); 914 foreach ( self::micropub_post_types() as $type ) { 915 if ( ! in_array( $type['type'], $known, true ) ) { 916 $types[] = $type; 917 } 918 } 919 $response['post-types'] = $types; 920 return $response; 921 } 922 923 /** 924 * Returns the enabled kinds as Micropub post types. 925 * 926 * @since 4.0.0 927 * 928 * @return array[] Arrays with the keys type (the kind slug, which is its Post 929 * Type Discovery type) and name (the kind's singular name). 930 */ 931 public static function micropub_post_types() { 932 // The kinds offered in the editor: those enabled in settings, plus note, 933 // which is always available. 934 $kinds = Kind_Config::get_termslist(); 935 array_unshift( $kinds, 'note' ); 936 $types = array(); 937 foreach ( array_unique( $kinds ) as $kind ) { 938 $name = Kind_Taxonomy::get_kind_info( $kind, 'singular_name' ); 939 if ( $name ) { 940 $types[] = array( 941 'type' => $kind, 942 'name' => $name, 943 ); 944 } 945 } 946 /** 947 * Filters the post types advertised to Micropub clients in q=config. 948 * 949 * @since 4.0.0 950 * 951 * @param array[] $types Arrays with the keys type and name. 952 */ 953 return apply_filters( 'post_kinds_micropub_post_types', $types ); 222 954 } 223 955 } // End Class Kind_Plugins -
indieweb-post-kinds/trunk/includes/class-kind-post-widget.php
r3001401 r3738687 1 1 <?php 2 /** 3 * Kind_Post_Widget class. 4 * 5 * @package Post_Kinds 6 * @since 3.1.5 7 */ 2 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Widget that lists recent posts of selected kinds. 13 * 14 * @since 3.1.5 15 */ 3 16 class Kind_Post_Widget extends WP_Widget { 4 17 /** 5 18 * Register widget with WordPress. 19 * 20 * @since 3.1.5 6 21 */ 7 22 public function __construct() { 8 23 parent::__construct( 9 'Kind_Post_Widget', // Base ID 10 __( 'Kind Post Widget', 'indieweb-post-kinds' ), // Name 24 'Kind_Post_Widget', // Base ID. 25 __( 'Kind Post Widget', 'indieweb-post-kinds' ), // Name. 11 26 array( 12 27 'classname' => 'kind_post_widget', … … 20 35 * Front-end display of widget. 21 36 * 37 * @since 3.1.5 38 * 22 39 * @see WP_Widget::widget() 23 40 * … … 26 43 */ 27 44 public function widget( $args, $instance ) { 28 /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ 29 $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ); 30 31 $kind = $instance['kind'] ?? 'note'; 32 // phpcs:ignore 33 echo $args['before_widget']; 34 if ( ! empty( $instance['title'] ) ) { 35 echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore 36 } 37 $transient = get_transient( 'kind_post_widget' ); 38 if ( false === $transient ) { 39 $query = array( 40 'tax_query' => array( 41 array( 42 'taxonomy' => 'kind', 43 'field' => 'slug', 44 'terms' => $kind, 45 ), 45 $kind = $instance['kind'] ?? 'note'; 46 $number = absint( $instance['number'] ?? 5 ); 47 $posts = get_posts( 48 array( 49 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Kinds are a taxonomy; this is the indexed way to select them. 50 'tax_query' => array( 51 array( 52 'taxonomy' => 'kind', 53 'field' => 'slug', 54 'terms' => $kind, 46 55 ), 47 'numberposts' => $instance['number'] ?? 5, 48 ); 49 $posts = get_posts( $query ); 56 ), 57 'numberposts' => $number ? $number : 5, 58 'no_found_rows' => true, 59 'ignore_sticky_posts' => true, 60 ) 61 ); 62 // Like core's Recent Posts widget, show nothing when there are no posts. 63 // WP_Query caches the result in the object cache, so no transient is needed. 64 if ( empty( $posts ) ) { 65 return; 50 66 } 51 67 52 if ( 0 === count( $posts ) ) { 53 return; 68 /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ 69 $title = apply_filters( 'widget_title', $instance['title'] ?? '', $instance, $this->id_base ); 70 71 echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget wrapper markup registered by the theme. 72 if ( $title ) { 73 echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Wrapper markup is the theme's; the title is escaped by core's widget_title filter. 54 74 } 55 echo '<div id="kind-posts">'; 56 if ( 0 !== count( $posts ) ) { 57 echo '<ul>'; 58 foreach ( $posts as $post ) { 59 printf( '<li>%1$s</li>', kind_get_the_link( $post ) ); // phpcs:ignore 60 } 61 echo '</ul>'; 62 } else { 63 esc_html_e( 'No Posts Found', 'indieweb-post-kinds' ); 75 echo '<div id="kind-posts"><ul>'; 76 foreach ( $posts as $post ) { 77 printf( '<li>%1$s</li>', kind_get_the_link( $post ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- kind_get_the_link() escapes its parts. 64 78 } 65 echo '</ div>';66 echo $args['after_widget']; // phpcs:ignore 79 echo '</ul></div>'; 80 echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget wrapper markup registered by the theme. 67 81 } 68 82 69 83 /** 70 84 * Sanitize widget form values as they are saved. 85 * 86 * @since 3.1.5 71 87 * 72 88 * @see WP_Widget::update() … … 78 94 */ 79 95 public function update( $new_instance, $old_instance ) { 80 array_walk_recursive( $new_instance, 'sanitize_text_field' ); 81 return $new_instance; 96 $kind = isset( $new_instance['kind'] ) && is_string( $new_instance['kind'] ) ? sanitize_key( $new_instance['kind'] ) : ''; 97 $number = isset( $new_instance['number'] ) ? absint( $new_instance['number'] ) : 0; 98 return array( 99 'title' => isset( $new_instance['title'] ) && is_string( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '', 100 'kind' => in_array( $kind, Kind_Taxonomy::get_kind_list(), true ) ? $kind : 'note', 101 'number' => $number > 0 ? $number : 5, 102 ); 82 103 } 83 104 … … 85 106 /** 86 107 * Create the form for the Widget admin 108 * 109 * @since 3.1.5 87 110 * 88 111 * @see WP_Widget::form() … … 93 116 $instance['kind'] = ( $instance['kind'] ?? 'note' ); 94 117 ?> 95 <p><label for=" title"><?php esc_html_e( 'Title: ', 'indieweb-post-kinds' ); ?></label>96 <input type="text" size="30" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?> id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"97 value="<?php echo esc_ html( $instance['title'] ?? '' ); ?>" /></p>118 <p><label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title: ', 'indieweb-post-kinds' ); ?></label> 119 <input type="text" size="30" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" 120 value="<?php echo esc_attr( $instance['title'] ?? '' ); ?>" /></p> 98 121 <select name="<?php echo esc_attr( $this->get_field_name( 'kind' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'kind' ) ); ?>"> 99 122 <?php 100 $list = get_option( 'kind_termslist', Kind_Taxonomy::get_kind_list());123 $list = Kind_Config::get_termslist(); 101 124 $list[] = 'note'; 125 $list = array_unique( $list ); 102 126 foreach ( $list as $term ) { 103 $value = Kind_Taxonomy::get_post_kind_info( $term );104 127 printf( 105 128 '<option value="%1$s" %3$s>%2$s</option>', 106 129 esc_attr( $term ), 107 Kind_Taxonomy::get_kind_info( $term, 'singular_name' ), // phpcs:ignore108 selected( $instance['kind'], $term )130 esc_html( Kind_Taxonomy::get_kind_info( $term, 'singular_name' ) ), 131 selected( $instance['kind'], $term, false ) 109 132 ); 110 printf( '%1$s %2$s<br />', Kind_Taxonomy::get_icon( $term ), esc_html( $value->singular_name ) ); //phpcs:ignore111 133 } 112 134 ?> -
indieweb-post-kinds/trunk/includes/class-kind-post.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Kind Post Class3 * Kind_Post class. 4 4 * 5 * @package Post Kinds 6 * Assists in retrieving/saving properties from a Post. Replaces MF2_Post by only looking for Items on Demand instead of Parsing Them initially. 5 * @package Post_Kinds 6 * @since 3.4.0 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Reads and writes the kind properties of a post. 13 * 14 * Replaces MF2_Post by looking up each property on demand instead of parsing 15 * all of them up front. 16 * 17 * @since 3.4.0 7 18 */ 8 19 class Kind_Post { 9 20 21 /** 22 * Post ID. 23 * 24 * @since 3.4.0 25 * @var int 26 */ 10 27 public $id; 11 28 29 /** 30 * Sets up the post to read and write. 31 * 32 * @since 3.4.0 33 * 34 * @param int|WP_Post|string $post Post ID, post object, or the URL of a post or attachment. 35 */ 12 36 public function __construct( $post ) { 13 37 if ( is_numeric( $post ) ) { … … 21 45 $post = $id; 22 46 } else { 23 $id = attachment_url_to_postid( $post );47 $id = self::get_attachment_id( $post ); 24 48 $this->id = $id; 25 49 $post = $id; … … 30 54 } 31 55 32 /* 33 * Check is post is attachment. 34 * 35 * @param WP_Post $post Post Object. 36 * @return boolean True if attachment. 56 /** 57 * Checks whether a post is an attachment. 58 * 59 * @since 3.4.10 60 * 61 * @param int|WP_Post $post Post ID or post object. 62 * @return bool True if the post is an attachment. 37 63 */ 38 64 private function is_attachment( $post ) { … … 41 67 42 68 43 /* 44 * Returns WP_Post Object 45 * 46 * @return WP_Post Post Object. 47 * 69 /** 70 * Returns the post object. 71 * 72 * @since 3.4.0 73 * 74 * @return WP_Post|null The post, or null if it does not exist. 48 75 */ 49 76 public function get_post() { … … 51 78 } 52 79 53 /* 54 * Returns the Post Kind. For Attachments will return the media type. 55 * 56 * @return string Post Kind. 57 * 80 /** 81 * Returns the post kind. For an attachment, returns its media type instead. 82 * 83 * @since 3.4.0 84 * 85 * @return string|false|null The kind slug ('photo', 'video' or 'audio' for an attachment), 86 * false if a post has no kind, or null for an attachment of another type. 58 87 */ 59 88 public function get_kind() { … … 73 102 } 74 103 75 /* 76 * Get Name. 77 * 78 * @return string Return name. 104 /** 105 * Returns the post's name (title). 106 * 107 * A title that is just the post ID, or an attachment title that is just its 108 * file name, does not count as a name. 109 * 110 * @since 3.4.0 111 * 112 * @return string|false The name, or false if there is none. 79 113 */ 80 114 public function get_name() { … … 102 136 } 103 137 104 /* 105 * Get Content. 106 * 107 * @return array with HTML and Plaintext Version of Content or Summary 138 /** 139 * Returns the post's content or summary (excerpt). 140 * 141 * @since 3.4.0 142 * 143 * @param string $property Either 'content' or 'summary'. 144 * @return array|false Array with the HTML ('html') and plain text ('value') versions, 145 * or false if the property is empty or not supported. 108 146 */ 109 147 public function get_html( $property ) { … … 124 162 } 125 163 126 /* 127 * Get Permalink URL 128 * 129 * @return string $url 164 /** 165 * Returns the post's permalink, or the file URL for an attachment. 166 * 167 * @since 3.4.0 168 * 169 * @return string|false The URL, or false if there is none. 130 170 */ 131 171 public function get_url() { … … 137 177 } 138 178 139 /* 140 * Get Featured Image Permalink 141 * 142 * @return string $url 179 /** 180 * Returns the URL of the post's featured image. 181 * 182 * @since 3.4.0 183 * 184 * @return string|false The URL, or false if the post has no featured image. 143 185 */ 144 186 public function get_featured() { … … 150 192 151 193 152 /* 153 * Return datetime property as a DateTime Object. 154 * 155 * @param string $property Property You Wish to Return. 156 * @return DateTimeImmutable Published Time in Local Timezone. 157 * 194 /** 195 * Returns a date property as a date object. 196 * 197 * For posts other than attachments, 'published' and 'updated' come from the 198 * post's own dates. Everything else comes from post meta. 199 * 200 * @since 3.4.0 201 * 202 * @param string $property Property to return, such as 'published' or 'start'. 203 * @return DateTimeImmutable|false The date, or false if it is missing or not a valid date. 158 204 */ 159 205 public function get_datetime_property( $property ) { … … 175 221 $datetime = $datetime[0]; 176 222 } 177 return new DateTimeImmutable( $datetime ); 178 } 179 180 /* 181 * Returns Publication. 182 * 183 * @return string 184 * 223 return kind_safe_datetime( $datetime ); 224 } 225 226 /** 227 * Returns the publication the post appeared in. 228 * 229 * For posts other than attachments, this is the site title. 230 * 231 * @since 3.4.0 232 * 233 * @return string|null The publication, or null if an attachment has none. 185 234 */ 186 235 public function get_publication() { … … 195 244 } 196 245 246 /** 247 * Returns the post's stored duration. 248 * 249 * @since 3.4.0 250 * 251 * @return DateInterval|false The duration, or false if it is missing or not valid. 252 */ 197 253 public function get_duration() { 198 254 $duration = get_post_meta( $this->id, 'mf2_duration', true ); … … 200 256 $duration = $duration[0]; 201 257 } 202 if ( $duration ) {203 return new DateInterval( $duration );204 } 205 return false;206 }207 208 /*209 * Return Categories which are a combination of Tags and Categories.210 * 211 * @return array Array of the names of Categories and Tag names.212 * 258 return kind_safe_interval( $duration ); 259 } 260 261 /** 262 * Returns the names of the post's categories and tags, combined. 263 * 264 * 'Uncategorized' is left out. 265 * 266 * @since 3.4.0 267 * 268 * @return string[] Category and tag names. 213 269 */ 214 270 public function get_categories() { 215 271 $category = array(); 216 // Get a list of categories and extract their names 272 // Get a list of categories and extract their names. 217 273 $post_categories = get_the_terms( $this->id, 'category' ); 218 274 if ( ! empty( $post_categories ) && ! is_wp_error( $post_categories ) ) { … … 220 276 } 221 277 222 // Get a list of tags and extract their names 278 // Get a list of tags and extract their names. 223 279 $post_tags = get_the_terms( $this->id, 'post_tag' ); 224 280 if ( ! empty( $post_tags ) && ! is_wp_error( $post_tags ) ) { 225 $category = array_merge( $ this->category, wp_list_pluck( $post_tags, 'name' ) );281 $category = array_merge( $category, wp_list_pluck( $post_tags, 'name' ) ); 226 282 } 227 283 if ( in_array( 'Uncategorized', $category, true ) ) { … … 232 288 233 289 /** 234 * Is prefix in string. 235 * 236 * @param string $source The source string. 237 * @param string $prefix The prefix you wish to check for in source. 238 * @return boolean The result. 290 * Checks whether a string starts with a prefix. 291 * 292 * @since 3.4.0 293 * 294 * @param string $source The source string. 295 * @param string $prefix The prefix to look for. 296 * @return bool True if the source starts with the prefix. 239 297 */ 240 298 protected static function str_prefix( $source, $prefix ) { … … 243 301 244 302 /** 245 * Returns True if Array is Multidimensional. 246 * 247 * @param array $arr array. 248 * 249 * @return boolean result 303 * Checks whether an array is multidimensional. 304 * 305 * @since 3.4.0 306 * 307 * @param array $arr The array to check. 308 * @return bool True if any element is an array. 250 309 */ 251 310 protected static function is_multi_array( $arr ) { … … 257 316 } 258 317 318 /** 319 * Unwraps single-item arrays, recursively. 320 * 321 * @since 3.4.0 322 * 323 * @param mixed $value The value to unwrap. 324 * @param bool $discard Optional. Whether to keep only the first item of a list 325 * with more than one item. Default false. 326 * @return mixed The value, with single-item arrays replaced by their item. 327 */ 259 328 protected function single_array( $value, $discard = false ) { 260 329 if ( ! is_array( $value ) ) { … … 274 343 275 344 /** 276 * Retrieve author 277 * 278 * @return boolean|array The result or false if does not exist. 345 * Returns the post's author as an h-card. 346 * 347 * For an attachment, or a post with no WordPress author, this is the author 348 * stored in post meta. 349 * 350 * @since 3.4.0 351 * 352 * @return array|string|false The author as mf2, the stored value, or false if there is none. 279 353 */ 280 354 public function get_author() { … … 284 358 return ( $author ?? false ); 285 359 } 286 // Attachments may have been uploaded by a user but may have metadata for original author 360 // Attachments may have been uploaded by a user but may have metadata for original author. 287 361 if ( 'attachment' === get_post_type( $this->id ) ) { 288 362 $author = get_post_meta( $post->ID, 'mf2_author', true ); … … 294 368 'name' => array( get_the_author_meta( 'display_name', $post->post_author ) ), 295 369 'url' => array( get_the_author_meta( 'user_url', $post->post_author ) ? get_the_author_meta( 'user_url', $post->post_author ) : get_author_posts_url( $post->post_author ) ), 296 'photo' => array( get_avatar_url( $ this->post_author ) ),370 'photo' => array( get_avatar_url( $post->post_author ) ), 297 371 ), 298 372 ); 299 373 } 300 374 301 /* 302 * Return Attached Media IDs. 303 * 304 * @param string $type audio, video, or photo. 305 * @return array Array of Media IDs. 306 * 375 /** 376 * Returns the IDs of media attached to the post. 377 * 378 * @since 3.4.0 379 * 380 * @param string $type 'audio', 'video' or 'photo'. 381 * @return int[]|false Attachment IDs, or false if the type is not supported. 307 382 */ 308 383 public function get_attached_media( $type ) { … … 318 393 } 319 394 320 /* 321 * Return Attached Photos. 322 * 323 * Looks in both attached media and the photo property. 324 * 325 * @param boolean $content If true then return empty if there are any images in content. 326 * @return array Array of Media IDs. 327 * 395 /** 396 * Returns the post's photos. 397 * 398 * Looks in attached media, the photo property and images in the content. 399 * Returns an empty array if the post has a featured image. 400 * 401 * @since 3.4.0 402 * 403 * @param bool $content Optional. Whether to return an empty array if the content 404 * has any images. Default true. 405 * @return int[]|false Attachment IDs, or false if there are none. 328 406 */ 329 407 public function get_photo( $content = true ) { 408 if ( ! $this->get_post() ) { 409 return false; 410 } 330 411 // Check if the post itself is an image attachment. 331 412 if ( wp_attachment_is( 'image', $this->id ) ) { … … 333 414 } 334 415 416 // Recorded when the post is saved; see Kind_Media_Metadata::save_post(). 335 417 $content_ids = get_post_meta( $this->id, '_content_img_ids', true ); 336 337 if ( false === $content_ids ) {338 // Check for a gallery first.339 $gallery = get_post_gallery( $this->id, false );340 if ( $gallery & array_key_exists( 'ids', $gallery ) ) {341 $content_ids = explode( ',', $gallery['ids'] );342 }343 $content_ids = array();344 $post = $this->get_post();345 $shortcode = false;346 $shortcodes = apply_filter( 'kind_photo_shortcode_exclude', array( 'vr', '360' ) );347 if ( $post->post_content ) {348 foreach ( $shortcodes as $code ) {349 if ( has_shortcode( $post->post_content, $code ) ) {350 $shortcode = true;351 }352 }353 if ( $shortcode ) {354 $content_ids = array();355 } else {356 $content_ids = array_merge( $content_ids, Kind_Media_Metadata::get_img_from_content( $post->post_content ) );357 }358 update_post_meta( $this->id, '_content_img_ids', $content_ids );359 }360 }361 418 362 419 // If there are photos in the content then end here if this is true. … … 393 450 } 394 451 395 /* 396 * Return Attached Audio. 397 * 398 * Looks in both attached media and the audio property. 399 * 400 * @param boolean $content If true then return empty if there are any audio files in content. 401 * @return array Array of Media IDs or URLs. 402 * 452 /** 453 * Returns the post's audio. 454 * 455 * Looks in attached media, the audio property and audio in the content. 456 * 457 * @since 3.4.0 458 * 459 * @param bool $content Optional. Whether to return an empty array if the content 460 * has any audio. Default true. 461 * @return array|false Attachment IDs, external URLs if no attachments were found, 462 * or false if there is no audio. 403 463 */ 404 464 public function get_audio( $content = true ) { … … 408 468 } 409 469 470 // Recorded when the post is saved; see Kind_Media_Metadata::save_post(). 410 471 $content_ids = get_post_meta( $this->id, '_content_audio_ids', true ); 411 412 if ( false === $content_ids ) {413 $post = get_post();414 if ( $post->post_content ) {415 $content_ids = Kind_Media_Metadata::get_audio_from_content( $post->post_content );416 update_post_meta( $this->id, '_content_audio_ids', $content_ids );417 }418 }419 472 420 473 // If there are ids in the content then end here if this is true. … … 431 484 $audio_ids = is_array( $audios ) ? $this->get_attachments_from_urls( $audios ) : array(); 432 485 433 // If there are ids found return them 486 // If there are ids found return them. 434 487 if ( ! empty( $audio_ids ) || ! empty( $att_ids ) || ! empty( $content_ids ) ) { 435 return array_unique( array_merge( $att_ids, $ this->get_attachments_from_urls( $audios ), $content_ids ) );488 return array_unique( array_merge( $att_ids, $audio_ids, $content_ids ) ); 436 489 } 437 490 … … 444 497 } 445 498 446 /* 447 * Return Attached Video. 448 * 449 * Looks in both attached media and the audio property. 450 * 451 * @param boolean $content If true then return empty if there are any video files in content. 452 * @return array Array of Media IDs. 453 * 499 /** 500 * Returns the post's videos. 501 * 502 * Looks in attached media, the video property and videos in the content. 503 * 504 * @since 3.4.0 505 * 506 * @param bool $content Optional. Whether to return an empty array if the content 507 * has any videos. Default true. 508 * @return int[]|false Attachment IDs, or false if there are none. 454 509 */ 455 510 public function get_video( $content = true ) { … … 458 513 return array( $this->id ); 459 514 } 515 // Recorded when the post is saved; see Kind_Media_Metadata::save_post(). 460 516 $content_ids = get_post_meta( $this->id, '_content_video_ids', true ); 461 462 if ( false === $content_ids ) {463 $post = get_post();464 if ( $post->post_content ) {465 $content_ids = Kind_Media_Metadata::get_video_from_content( $post->post_content );466 update_post_meta( $this->id, '_content_video_ids', $content_ids );467 }468 }469 517 470 518 // If there are ids in the content then end here if this is true. … … 488 536 } 489 537 538 /** 539 * Look up the attachment ID for a URL, with caching. 540 * 541 * Wraps attachment_url_to_postid(), which runs an uncached query, in the 542 * object cache. The key includes the posts last_changed value, so adding, 543 * changing or deleting any post or attachment invalidates it. Without a 544 * persistent object cache this still avoids repeat lookups in a request. 545 * 546 * @since 4.0.0 547 * 548 * @param string $url Media URL. 549 * @return int Attachment ID, or 0 if there is none. 550 */ 551 public static function get_attachment_id( $url ) { 552 if ( ! is_string( $url ) || '' === $url ) { 553 return 0; 554 } 555 $key = 'attachment_id:' . md5( $url ) . ':' . wp_cache_get_last_changed( 'posts' ); 556 $id = wp_cache_get( $key, 'post-kinds' ); 557 if ( false === $id ) { 558 $id = attachment_url_to_postid( $url ); 559 wp_cache_set( $key, $id, 'post-kinds' ); 560 } 561 return (int) $id; 562 } 563 564 /** 565 * Looks up the attachment IDs for media URLs. 566 * 567 * @since 3.4.0 568 * 569 * @param string|array $urls A URL, or a list of URLs, attachment IDs or arrays with a 'url' key. 570 * @return int[] Attachment IDs found. URLs with no attachment are left out. 571 */ 490 572 public function get_attachments_from_urls( $urls ) { 491 573 if ( is_string( $urls ) ) { 492 $attachment = attachment_url_to_postid( $urls );574 $attachment = self::get_attachment_id( $urls ); 493 575 if ( $attachment ) { 494 576 return array( $attachment ); … … 502 584 if ( is_array( $url ) ) { 503 585 if ( isset( $url['url'] ) ) { 504 $att_ids[] = attachment_url_to_postid( $url['url'] );586 $att_ids[] = self::get_attachment_id( $url['url'] ); 505 587 } 506 588 } elseif ( is_numeric( $url ) ) { 507 589 $att_ids[] = $url; 508 590 } else { 509 $att_ids[] = attachment_url_to_postid( $url );591 $att_ids[] = self::get_attachment_id( $url ); 510 592 } 511 593 } … … 515 597 516 598 /** 517 * Retrieve value 518 * 519 * @param string $key The key to retrieve. 520 * @param boolean $single Whether to return a a single value or array if there is only one value. 521 * @return mixed The result or false if does not exist. 599 * Returns a property of the post. 600 * 601 * @since 3.4.0 602 * 603 * @param string $key The property to return. 604 * @param bool $single Optional. Whether to unwrap a single value from its array. Default true. 605 * @return mixed The value, or false if the key is empty. 522 606 */ 523 607 public function get( $key, $single = true ) { … … 559 643 } 560 644 645 /** 646 * Returns the post's citation, or one property of it. 647 * 648 * For an attachment, the citation is built from the attachment itself. 649 * Citations stored as jf2 are returned as mf2. 650 * 651 * @since 3.4.0 652 * 653 * @param string|null $key Optional. Citation property to return. Default null, for the whole citation. 654 * @return mixed The citation as mf2 or a string, the property, or false if there is none. 655 */ 561 656 public function get_cite( $key = null ) { 562 657 if ( 'attachment' === get_post_type( $this->id ) ) { … … 569 664 $duration = date_interval_to_iso8601( $duration ); 570 665 } 571 $cite = jf2_to_mf2(666 $cite = \ParseThis\jf2_to_mf2( 572 667 array_filter( 573 668 array( … … 604 699 $cite = $cite[0]; 605 700 } 606 $this->delete( 'cite' ); 607 } 608 } 609 610 // If this is formatted as JF2 try to convert it to MF2 and update. 611 if ( is_array( $cite ) && ! wp_is_numeric_array( $cite ) ) { 612 $cite['type'] = 'cite'; 613 $cite = jf2_to_mf2( $cite ); 614 $property = Kind_Taxonomy::get_kind_info( $this->get_kind(), 'property' ); 615 $this->set( $property, $cite ); 701 } 702 703 // Citations stored as jf2 are returned as mf2. 704 if ( is_array( $cite ) && ! wp_is_numeric_array( $cite ) ) { 705 if ( ! isset( $cite['properties'] ) ) { 706 if ( empty( $cite['type'] ) ) { 707 $cite['type'] = $this->get_cite_type(); 708 } 709 $cite = \ParseThis\jf2_to_mf2( $cite ); 710 } elseif ( empty( $cite['type'] ) || ! is_array( $cite['type'] ) ) { 711 // Reads before 4.0.0 stored the type as 'cite'. 712 $cite['type'] = array( 'h-' . $this->get_cite_type() ); 713 } 714 } 616 715 } 617 716 … … 649 748 } 650 749 651 /* Returns a normalized cite with all possible parameters present to reduce isset checks and to ensure everything is formatted correctly 652 * For Display and Metabox purposes only. 750 /** 751 * Returns the jf2 type of the post's citation, based on its kind. 752 * 753 * Matches the type Kind_Metabox::save_post() stores: a checkin cites a 754 * place (card), eat and drink cite food, and every other kind a citation. 755 * 756 * @since 4.0.0 757 * 758 * @return string The jf2 type: 'card', 'food' or 'cite'. 759 */ 760 public function get_cite_type() { 761 $kind = $this->get_kind(); 762 if ( 'checkin' === $kind ) { 763 return 'card'; 764 } 765 if ( in_array( $kind, array( 'drink', 'eat' ), true ) ) { 766 return 'food'; 767 } 768 return 'cite'; 769 } 770 771 /** 772 * Returns a citation as jf2 with every key present and formatted for display. 773 * 774 * Missing keys are set to empty strings, which saves isset checks. For 775 * display and the metabox only. 776 * 777 * 'authors' lists every author as a card with name, url and photo. 'author' 778 * is the single author, or for several authors one card whose fields hold 779 * each author's value in turn, separated by semicolons, as the metabox 780 * shows them. 781 * 782 * @since 3.4.9 783 * @since 4.0.0 Added 'authors'. 'author' joins several authors' photos too. 784 * 785 * @param array|string|false $cite The citation, as mf2, jf2 or a URL or name. 786 * @return array The normalized citation. 653 787 */ 654 788 public function normalize_cite( $cite ) { 655 // Ensures that an empty string is always present in the cite 789 // Ensures that an empty string is always present in the cite. 656 790 $author_defaults = array( 657 791 'type' => 'card', … … 670 804 'summary' => '', 671 805 'author' => $author_defaults, 806 'authors' => array(), 672 807 'category' => '', 673 808 ); … … 680 815 $cite = $cite[0]; 681 816 } 682 if ( Parse_This_MF2_Utils::is_microformat( $cite ) ) {683 $cite = mf2_to_jf2( $cite );817 if ( \ParseThis\MF2_Utils::is_microformat( $cite ) ) { 818 $cite = \ParseThis\mf2_to_jf2( $cite ); 684 819 } 685 820 … … 698 833 } 699 834 700 if ( wp_is_numeric_array( $cite['author'] ) ) { 701 if ( 1 === count( $cite['author'] ) ) { 702 $cite['author'] = array_pop( $cite['author'] ); 703 } 704 } 705 706 if ( is_string( $cite['author'] ) ) { 707 $cite['author'] = wp_http_validate_url( $cite['author'] ) ? array( 'url' => $cite['author'] ) : array( 'name' => $cite['author'] ); 708 } 709 710 $cite['author'] = wp_parse_args( $cite['author'], $author_defaults ); 835 $cite['authors'] = self::normalize_authors( $cite['author'] ); 836 if ( 1 === count( $cite['authors'] ) ) { 837 $cite['author'] = $cite['authors'][0]; 838 } else { 839 $cite['author'] = $author_defaults; 840 foreach ( array( 'name', 'url', 'photo' ) as $key ) { 841 $values = wp_list_pluck( $cite['authors'], $key ); 842 if ( array_filter( $values ) ) { 843 $cite['author'][ $key ] = implode( '; ', $values ); 844 } 845 } 846 } 711 847 712 848 if ( is_array( $cite['publication'] ) ) { 713 849 $cite['publication'] = $cite['publication']['name']; 714 }715 716 if ( is_array( $cite['author']['name'] ) ) {717 $cite['author']['name'] = implode( ';', $cite['author']['name'] );718 }719 if ( is_array( $cite['author']['url'] ) ) {720 $cite['author']['url'] = implode( ';', $cite['author']['url'] );721 850 } 722 851 … … 733 862 } 734 863 864 865 /** 866 * Returns a citation's authors as a list of cards. 867 * 868 * Accepts one author or a list, each as a jf2 or mf2 h-card, a URL or a 869 * name. Before 4.0.0 the metabox saved several authors as one card whose 870 * name, url and photo were lists; the values are paired up by position. 871 * 872 * @since 4.0.0 873 * 874 * @param mixed $author The author or authors. 875 * @return array[] Cards with string type, name, url and photo, leaving out empty ones. 876 */ 877 public static function normalize_authors( $author ) { 878 if ( wp_is_numeric_array( $author ) ) { 879 $authors = array(); 880 foreach ( $author as $item ) { 881 $authors = array_merge( $authors, self::normalize_authors( $item ) ); 882 } 883 return $authors; 884 } 885 if ( is_string( $author ) ) { 886 $author = wp_http_validate_url( $author ) ? array( 'url' => $author ) : array( 'name' => $author ); 887 } 888 if ( ! is_array( $author ) ) { 889 return array(); 890 } 891 if ( \ParseThis\MF2_Utils::is_microformat( $author ) ) { 892 $author = \ParseThis\mf2_to_jf2( $author ); 893 } 894 895 $fields = array(); 896 foreach ( array( 'name', 'url', 'photo' ) as $key ) { 897 $value = isset( $author[ $key ] ) ? $author[ $key ] : ''; 898 if ( is_array( $value ) && isset( $value['value'] ) ) { 899 // A jf2 photo with alt text. 900 $value = $value['value']; 901 } 902 $fields[ $key ] = array_values( array_filter( (array) $value, 'is_scalar' ) ); 903 } 904 905 $authors = array(); 906 $count = max( array_map( 'count', $fields ) ); 907 for ( $i = 0; $i < $count; $i++ ) { 908 $card = array( 'type' => 'card' ); 909 foreach ( $fields as $key => $values ) { 910 $card[ $key ] = isset( $values[ $i ] ) ? trim( (string) $values[ $i ] ) : ''; 911 } 912 if ( '' !== $card['name'] . $card['url'] . $card['photo'] ) { 913 $authors[] = $card; 914 } 915 } 916 return $authors; 917 } 918 /** 919 * Stores a date property. 920 * 921 * For posts other than attachments, 'published' and 'updated' set the 922 * post's own dates. Everything else is stored in post meta. 923 * 924 * @since 3.4.0 925 * 926 * @param string $key Property to set, such as 'published' or 'start'. 927 * @param mixed $value A date, in any form kind_safe_datetime() accepts. 928 * @return int|bool|WP_Error The result of updating the post or its meta, 929 * or false if the value is not a valid date. 930 */ 735 931 public function set_datetime_property( $key, $value ) { 736 932 // In an attachment the post date properties reflect when the item was uploaded not when the piece was created. 737 if ( ! $value instanceof DateTime ) { 738 $value = new DateTime( $value ); 739 } 933 // A mutable copy, as the timezone is changed below. 934 $value = kind_safe_datetime( $value, null, false ); 740 935 if ( ! $value ) { 741 936 return false; … … 755 950 } 756 951 952 /** 953 * Stores the post's duration as an ISO 8601 duration. 954 * 955 * @since 3.4.0 956 * 957 * @param mixed $value A duration, in any form kind_safe_interval() accepts. 958 * @return int|bool The result of updating the meta, or false if the value is not a valid duration. 959 */ 757 960 public function set_duration( $value ) { 758 if ( ! $value instanceof DateInterval ) { 759 $value = new DateInterval( $value ); 760 } 961 $value = kind_safe_interval( $value ); 761 962 if ( ! $value ) { 762 963 return false; … … 769 970 770 971 /** 771 * Set author 772 * 773 * @param array $value Author microformat. 774 * @return boolean|WP_Error 972 * Stores the author of an attachment. 973 * 974 * Other posts use their WordPress author, so nothing is stored for them. 975 * 976 * @since 3.4.0 977 * 978 * @param array $value Author, as mf2. 979 * @return int|bool|null The result of updating the meta, or null if the post is not an attachment. 775 980 */ 776 981 public function set_author( $value ) { 777 // Attachments may have been uploaded by a user but may have metadata for original author 982 // Attachments may have been uploaded by a user but may have metadata for original author. 778 983 if ( 'attachment' === get_post_type( $this->id ) ) { 779 984 return update_post_meta( $this->id, 'mf2_author', $value ); … … 781 986 } 782 987 988 /** 989 * Stores a property, or several. 990 * 991 * @since 3.4.0 992 * 993 * @param string|array $key Property to set, or an array of property => value pairs. 994 * @param mixed $value Optional. The value. Default null. 995 * @return mixed The result of storing the value. True when setting an array, 996 * and null if the key or value is empty. 997 */ 783 998 public function set( $key, $value = null ) { 784 999 if ( is_array( $key ) ) { … … 804 1019 case 'featured': 805 1020 if ( wp_http_validate_url( $value ) ) { 806 $featured = attachment_url_to_postid( $value );1021 $featured = self::get_attachment_id( $value ); 807 1022 if ( $featured ) { 808 1023 $value = $featured; … … 832 1047 return wp_update_post( $args, true ); 833 1048 case 'audio': 834 /* All media is handled identically. 835 */ 1049 // All media is handled identically. 836 1050 case 'video': 837 /* All Media is handled identically. 838 */ 1051 // All media is handled identically. 839 1052 case 'photo': 840 if ( Parse_This_MF2::is_microformat( $value ) ) {841 $url = Parse_This_MF2::get_plaintext( $value, 'url' );842 $id = attachment_url_to_postid( $url );1053 if ( \ParseThis\MF2_Utils::is_microformat( $value ) ) { 1054 $url = \ParseThis\MF2_Utils::get_plaintext( $value, 'url' ); 1055 $id = self::get_attachment_id( $url ); 843 1056 if ( $id ) { 844 $value = mf2_to_jf2( $value );1057 $value = \ParseThis\mf2_to_jf2( $value ); 845 1058 unset( $value['type'] ); 846 1059 $attachment = new Kind_Post( $id ); … … 852 1065 return update_post_meta( $this->id, 'mf2_' . $key, array( $url ) ); 853 1066 } 854 /* If it is not a microformat handle as default. 855 */ 1067 // If it is not a microformat handle as default. 856 1068 default: 857 1069 return update_post_meta( $this->id, 'mf2_' . $key, $value ); … … 859 1071 } 860 1072 1073 /** 1074 * Deletes a property stored in post meta. 1075 * 1076 * @since 3.4.0 1077 * 1078 * @param string $key Property to delete. 1079 * @return bool True on success, false on failure. 1080 */ 861 1081 public function delete( $key ) { 862 1082 return delete_post_meta( $this->id, 'mf2_' . $key ); -
indieweb-post-kinds/trunk/includes/class-kind-taxonomy.php
r3011061 r3738687 1 1 <?php 2 3 2 /** 4 * Post Kind Taxonomy Class3 * Kind_Taxonomy class. 5 4 * 6 * Registers the taxonomy and sets its behavior. 5 * @package Post_Kinds 6 * @since 2.0.2 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Registers the kind taxonomy and sets its behavior. 7 13 * 8 * @ package Post Kinds14 * @since 2.0.2 9 15 */ 10 16 final class Kind_Taxonomy { 11 private static $kinds = array(); // Store a Post_Kind class which is a definition of a specific kind 12 17 /** 18 * Registered kinds, keyed by slug. 19 * 20 * @since 3.1.0 21 * @var Post_Kind[] 22 */ 23 private static $kinds = array(); 24 25 /** 26 * Whether the built-in kinds have been registered. 27 * 28 * @since 4.0.0 29 * @var bool 30 */ 31 private static $builtins_loaded = false; 32 33 /** 34 * Loads the built-in kinds and adds the taxonomy's hooks. 35 * 36 * @since 2.0.2 37 */ 13 38 public static function init() { 14 $wp_version = get_bloginfo( 'version' ); 15 require_once plugin_dir_path( __FILE__ ) . '/register-kinds.php'; 39 self::load_kinds(); 16 40 17 41 // Add the Correct Archive Title to Kind Archives. 18 if ( version_compare( $wp_version, '5.5', '>' ) ) { 19 add_filter( 'get_the_archive_title', array( self::class, 'kind_archive_title' ), 10 ); 20 } else { 21 add_filter( 'get_the_archive_title', array( self::class, 'kind_archive_title' ), 10, 3 ); 22 } 42 add_filter( 'get_the_archive_title', array( self::class, 'kind_archive_title' ), 10 ); 23 43 24 44 add_filter( 'get_the_archive_title_prefix', array( self::class, 'kind_archive_prefix' ), 10 ); … … 26 46 add_filter( 'document_title_parts', array( self::class, 'document_title_parts' ), 10 ); 27 47 48 // Keep the taxonomy's default term in step with the Default Kind setting. 49 add_action( 'add_option_kind_default', array( self::class, 'sync_default_term' ) ); 50 add_action( 'update_option_kind_default', array( self::class, 'sync_default_term' ) ); 51 28 52 // Add Kind Permalinks. 29 53 add_filter( 'post_link', array( self::class, 'kind_permalink' ), 10, 3 ); 30 54 add_filter( 'post_type_link', array( self::class, 'kind_permalink' ), 10, 3 ); 31 55 32 // Query Variable to Exclude Kinds from Feed 56 // Query Variable to Exclude Kinds from Feed. 33 57 add_filter( 'query_vars', array( self::class, 'query_vars' ) ); 34 58 add_action( 'pre_get_posts', array( self::class, 'kind_filter_query' ) ); … … 37 61 add_action( 'pre_get_posts', array( self::class, 'kind_firehose_query' ), 99 ); 38 62 39 // Add Dropdown 63 // Add Dropdown. 40 64 add_action( 'restrict_manage_posts', array( self::class, 'kind_dropdown' ), 10, 2 ); 41 65 … … 43 67 add_filter( 'webmention_links', array( self::class, 'webmention_links' ), 11, 2 ); 44 68 45 // Add Links to Enclosures if Appropriate 69 // Add Links to Enclosures if Appropriate. 46 70 add_filter( 'enclosure_links', array( self::class, 'enclosure_links' ), 11, 2 ); 47 71 … … 51 75 // Trigger Webmention on Change in Post Status. 52 76 add_filter( 'transition_post_status', array( self::class, 'transition' ), 10, 3 ); 53 // On Post Save Set Post Format 77 // On Post Save Set Post Format. 54 78 add_action( 'save_post', array( self::class, 'post_formats' ), 99, 3 ); 55 79 56 // Create hook triggered by change of kind 80 // Create hook triggered by change of kind. 57 81 add_action( 'set_object_terms', array( self::class, 'set_object_terms' ), 10, 6 ); 58 82 … … 69 93 } 70 94 71 /** Template Redirect 72 * 95 /** 96 * Uses the kind photos template for photo archives, if the theme has one. 97 * 98 * @since 3.4.14 99 * 100 * @param string $template Path of the template to include. 101 * @return string Path of the template to include. 73 102 */ 74 103 public static function template_include( $template ) { 75 104 global $wp_query; 76 105 77 // Only use this template for archive pages 106 // Only use this template for archive pages. 78 107 if ( is_singular() ) { 79 108 return $template; … … 92 121 /** 93 122 * Register the Route. 123 * 124 * @since 3.3.4 94 125 */ 95 126 public static function register_routes() { … … 107 138 ), 108 139 ), 140 // For editor tools: only users who can write posts need it. 109 141 'permission_callback' => function () { 110 return current_user_can( ' read' );142 return current_user_can( 'edit_posts' ); 111 143 }, 112 144 ), … … 115 147 } 116 148 149 /** 150 * Returns a kind's registration info for the post-kinds/1.0/fields route. 151 * 152 * @since 3.3.4 153 * 154 * @param WP_REST_Request $request Request, with the kind slug (or all) in kind. 155 * @return Post_Kind|Post_Kind[]|WP_Error The kind, all kinds, or an error for an unknown kind. 156 */ 117 157 public static function read( $request ) { 118 158 $kind = $request->get_param( 'kind' ); 119 return self::get_kind_info( $kind, 'all' ); 159 $info = self::get_kind_info( $kind, 'all' ); 160 if ( false === $info ) { 161 return new WP_Error( 'rest_post_kind_not_found', __( 'Unknown post kind.', 'indieweb-post-kinds' ), array( 'status' => 404 ) ); 162 } 163 return $info; 120 164 } 121 165 … … 123 167 /** 124 168 * Add our query variables to query_vars list. 169 * 170 * @since 3.2.4 125 171 * 126 172 * @access public … … 140 186 * Filter the query for our post kinds. 141 187 * 142 * @access public 143 * 144 * @param $query 188 * @since 3.4.14 189 * 190 * @access public 191 * 192 * @param WP_Query $query The query. 193 * @return WP_Query The query. 145 194 */ 146 195 public static function kind_photo_filter( $query ) { 147 // check if the user is requesting an admin page 196 // check if the user is requesting an admin page. 148 197 if ( is_admin() || ! $query->is_main_query() ) { 149 198 return $query; 150 199 } 151 200 $photos = get_query_var( 'kind_photos' ); 152 // Return if not set 201 // Return if not set. 153 202 if ( $photos ) { 154 203 $query->set( 'posts_per_page', 30 ); … … 156 205 $query->is_comment_feed = false; 157 206 $query->is_home = false; 207 // Set for posts with images in their content or a photo property; see Kind_Media_Metadata::update_photo_flag(). 158 208 $query->set( 159 209 'meta_query', 160 210 array( 161 'relation' => 'OR',162 211 array( 163 'key' => '_content_img_ids', 164 'compare' => 'EXISTS', 165 ), 166 array( 167 'key' => 'mf2_photo', 212 'key' => '_kind_has_photo', 168 213 'compare' => 'EXISTS', 169 214 ), … … 177 222 * Filter the query for our post kinds. 178 223 * 179 * @access public 180 * 181 * @param $query 224 * @since 3.2.4 225 * 226 * @access public 227 * 228 * @param WP_Query $query The query. 229 * @return WP_Query The query. 182 230 */ 183 231 public static function kind_filter_query( $query ) { 184 // check if the user is requesting an admin page 232 // check if the user is requesting an admin page. 185 233 if ( is_admin() || ! $query->is_main_query() ) { 186 234 return $query; 187 235 } 188 236 $exclude = get_query_var( 'exclude' ); 189 // Return if both are not set 237 // Return if both are not set. 190 238 if ( ! taxonomy_exists( $exclude ) ) { 191 239 return $query; … … 214 262 * Filter the main query for post kinds. 215 263 * 216 * @access public 217 * 218 * @param $query 264 * @since 3.6.0 265 * 266 * @access public 267 * 268 * @param WP_Query $query The query. 269 * @return WP_Query The query. 219 270 */ 220 271 public static function kind_firehose_query( $query ) { 221 272 222 // check if the user is requesting an admin page 273 // check if the user is requesting an admin page. 223 274 if ( is_admin() || ! $query->is_main_query() ) { 224 275 return $query; … … 246 297 } 247 298 248 $query->set( 249 'tax_query', 299 $tax_query = array( 250 300 array( 251 'relation' => 'OR', 252 array( 253 'taxonomy' => 'kind', 254 'field' => 'slug', 255 'terms' => $firehose, 256 'operator' => 'IN', 257 ), 258 array( 259 'taxonomy' => 'kind', 260 'operator' => 'NOT EXISTS', 261 ), 262 ) 263 ); 301 'taxonomy' => 'kind', 302 'field' => 'slug', 303 'terms' => $firehose, 304 'operator' => 'IN', 305 ), 306 ); 307 // Posts without a kind are shown too. The upgrade routine gives them one, 308 // so the costly NOT EXISTS clause is only needed until it completes. 309 if ( Kind_Upgrade::is_pending() ) { 310 $tax_query['relation'] = 'OR'; 311 $tax_query[] = array( 312 'taxonomy' => 'kind', 313 'operator' => 'NOT EXISTS', 314 ); 315 } 316 $query->set( 'tax_query', $tax_query ); 264 317 return $query; 265 318 } … … 268 321 * Allows for some pre-defined aliases 269 322 * 270 * @access public 271 * 272 * @param $query 323 * @since 3.4.19 324 * 325 * @access public 326 * 327 * @param WP_Query $query The query. 328 * @return WP_Query The query. 273 329 */ 274 330 public static function kind_alias_filter( $query ) { … … 292 348 /** 293 349 * Register our REST API endpoint for post kinds. 350 * 351 * @since 3.1.0 294 352 * 295 353 * @access public … … 300 358 'kind', 301 359 array( 302 'get_callback' => array( self::class, 'get_ post_kind_slug' ),360 'get_callback' => array( self::class, 'get_rest_post_kind' ), 303 361 'update_callback' => array( self::class, 'set_rest_post_kind' ), 304 362 'schema' => array( 305 'kind' => __( 'Post Kind', 'indieweb-post-kinds' ), 306 'type' => 'string', 363 'description' => __( 'The post kind, or false if the post has none.', 'indieweb-post-kinds' ), 364 // False is only returned, never accepted: the enum allows kind slugs alone. 365 'type' => array( 'string', 'boolean' ), 366 'enum' => self::get_kind_list(), 367 'context' => array( 'view', 'edit' ), 307 368 ), 308 369 ) … … 312 373 /** 313 374 * Filter template hierarchy to include our template files. 375 * 376 * @since 3.1.2 314 377 * 315 378 * @access public … … 332 395 * Generate a sample permalink for a post. 333 396 * 397 * @since 3.2.2 398 * 334 399 * @access public 335 400 * … … 348 413 if ( is_numeric( $permalink[1] ) && $post_id === (int) $permalink[1] ) { 349 414 $excerpt = self::get_excerpt( $post ); 350 $excerpt = sanitize_title( mb_strimwidth( wp_strip_all_tags( $excerpt ), 0, 40 ) ); // phpcs:ignore415 $excerpt = sanitize_title( mb_strimwidth( wp_strip_all_tags( $excerpt ), 0, 40 ) ); 351 416 if ( ! empty( $excerpt ) ) { 352 417 $permalink[1] = wp_unique_post_slug( $excerpt, $post_id, $post->post_status, $post->post_type, $post->post_parent ); … … 359 424 * Generate a post title. 360 425 * 361 * @access public 362 * 363 * @param int!WP_Post $post Post ID or Post Object. 364 * @return string 426 * @since 3.3.3 427 * 428 * @access public 429 * 430 * @param int|WP_Post $post Post ID or post object. 431 * @param int $length Optional. Maximum length of the title. Default 40. 432 * @return string|null The title, or null if the post does not exist. 365 433 */ 366 434 public static function generate_title( $post, $length = 40 ) { … … 374 442 $kind_post = new Kind_Post( $post ); 375 443 $cite = $kind_post->get_cite(); 376 if ( Parse_This_MF2::is_microformat( $cite ) ) { 444 // Citations are stored as a list; use the first. 445 if ( wp_is_numeric_array( $cite ) && 1 === count( $cite ) ) { 446 $cite = $cite[0]; 447 } 448 if ( class_exists( '\\ParseThis\\MF2_Utils' ) && \ParseThis\MF2_Utils::is_microformat( $cite ) ) { 377 449 if ( array_key_exists( 'name', $cite['properties'] ) ) { 378 450 $excerpt = $cite['properties']['name']; … … 383 455 } 384 456 } 385 return mb_strimwidth( $excerpt, 0, $length, '...' ); // phpcs:ignore457 return mb_strimwidth( $excerpt, 0, $length, '...' ); 386 458 } 387 459 … … 389 461 * Filter the post title. Add a symbol at the end if generated title. 390 462 * 391 * @access public 392 * 393 * @param string $title Current post title 394 * @param int $post_id Post ID 463 * @since 3.0.9 464 * 465 * @access public 466 * 467 * @param string $title Current post title. 468 * @param int $post_id Post ID. 395 469 * @return string 396 470 */ … … 406 480 $title = sprintf( '[%1$s] %2$s', $post_kind, self::generate_title( $post_id, 60 ) ); 407 481 } 482 } elseif ( empty( $title ) && is_comment_feed() ) { 483 // Comment feed items read "Comment on <title> by <author>"; name untitled posts. 484 $title = self::generate_title( $post_id, 60 ); 408 485 } 409 486 return $title; … … 413 490 * Filter the single post title. 414 491 * 415 * @access public 416 * 417 * @param string $title Current post title 418 * @param WP_Post $post Post object 492 * @since 3.3.3 493 * 494 * @access public 495 * 496 * @param string $title Current post title. 497 * @param WP_Post $post Post object. 419 498 * @return string 420 499 */ … … 438 517 * Filter the post excerpt. 439 518 * 519 * @since 3.2.0 520 * 440 521 * @access public 441 522 * … … 456 537 * Set our post object terms. 457 538 * 539 * @since 3.0.8 540 * 458 541 * @access public 459 542 * … … 461 544 * @param array $terms Array of object terms. 462 545 * @param array $tt_ids Array of term taxonomy IDs. 463 * @param string $taxonomy Taxonomy slug 546 * @param string $taxonomy Taxonomy slug. 464 547 * @param bool $append If terms should be appended or overwrite. 465 548 * @param array $old_tt_ids Array of original trem taxonomy IDs. … … 475 558 $new_term = $new_term instanceof WP_Term ? $new_term->slug : ''; 476 559 if ( $old_term !== $new_term ) { 477 // Trigger a hook on a changed kind identifying old and new so actions can be performed 560 /** 561 * Fires when a post's kind changes. 562 * 563 * @since 3.0.8 564 * 565 * @param int $object_id Post ID. 566 * @param string $old_term Slug of the previous kind, or an empty string if there was none. 567 * @param string $new_term Slug of the new kind, or an empty string if the kind was removed. 568 */ 478 569 do_action( 'change_kind', $object_id, $old_term, $new_term ); 479 570 } … … 483 574 /** 484 575 * To Be Run on Plugin Activation. 576 */ 577 /** 578 * Registers the built-in kinds, once. 579 * 580 * Called from init() and on activation. A plugin is activated after 581 * plugins_loaded and init have run for the request, so neither the kinds 582 * nor the register_post_kind() function (kind-functions.php) would 583 * otherwise be loaded when the kind terms are created. 584 * 585 * @since 4.0.0 586 */ 587 public static function load_kinds() { 588 if ( self::$builtins_loaded ) { 589 return; 590 } 591 self::$builtins_loaded = true; 592 if ( ! function_exists( 'register_post_kind' ) ) { 593 require_once plugin_dir_path( __FILE__ ) . 'kind-functions.php'; 594 } 595 require plugin_dir_path( __FILE__ ) . 'register-kinds.php'; 596 } 597 598 /** 599 * Creates the kind terms on activation. 600 * 601 * Stops with an error if the IndieWeb Taxonomy plugin, which Post Kinds 602 * replaces, is active. 603 * 604 * @since 2.0.2 485 605 */ 486 606 public static function activate_kinds() { 487 607 if ( function_exists( 'iwt_plugin_notice' ) ) { 488 deactivate_plugins( plugin_basename( __FILE__ ) ); 489 wp_die( 'You have Indieweb Taxonomy activated. Post Kinds replaces this plugin. Please disable Taxonomy before activating' ); 490 } 608 deactivate_plugins( plugin_basename( dirname( __DIR__ ) . '/indieweb-post-kinds.php' ) ); 609 wp_die( esc_html__( 'Post Kinds replaces the IndieWeb Taxonomy plugin. Deactivate IndieWeb Taxonomy, then activate Post Kinds.', 'indieweb-post-kinds' ) ); 610 } 611 self::load_kinds(); 491 612 self::register(); 492 613 self::kind_defaultterms(); … … 494 615 } 495 616 617 /** 618 * Returns the rewrite regex for the pagination part of a URL. 619 * 620 * @since 3.5.0 621 * 622 * @return string The regex, such as 'page/?([0-9]{1,})'. 623 */ 496 624 public static function get_pagination_regex() { 497 625 global $wp_rewrite; … … 499 627 } 500 628 629 /** 630 * Returns the rewrite regex for the feed part of a URL. 631 * 632 * @since 3.5.0 633 * 634 * @param bool $with_base Optional. Whether to start with the feed base ('feed/'). Default true. 635 * @return string The regex, such as 'feed/(feed|rdf|rss|rss2|atom)'. 636 */ 501 637 public static function get_feed_regex( $with_base = true ) { 502 638 global $wp_rewrite; 503 // Build a regex to match the feed section of URLs, something like (feed|atom|rss|rss2)/? 639 // Build a regex to match the feed section of URLs, something like (feed|atom|rss|rss2)/?. 504 640 $feedregex2 = ''; 505 641 foreach ( (array) $wp_rewrite->feeds as $feed_name ) { … … 511 647 } 512 648 649 /** 650 * Joins regex parts into a rewrite rule pattern. 651 * 652 * @since 3.5.0 653 * 654 * @param string[] $elements Parts of the pattern, in order. 655 * @return string The parts joined by slashes and ending in '?$', or an empty string if there are none. 656 */ 513 657 public static function generate_permastruct( $elements ) { 514 658 if ( empty( $elements ) || ! is_array( $elements ) ) { … … 520 664 521 665 /** 666 * Returns the start of kind archive URLs, as in /kind/like/. 667 * 668 * Set as the Kind base on Settings > Permalinks. 669 * 670 * @since 4.0.0 671 * 672 * @return string The base. Default 'kind'. 673 */ 674 public static function get_kind_base() { 675 $base = get_option( 'kind_base', '' ); 676 $base = is_string( $base ) && '' !== $base ? $base : 'kind'; 677 /** 678 * Filters the start of kind archive URLs, as in /kind/like/. 679 * 680 * The Kind base on Settings > Permalinks sets it. Flush rewrite rules after 681 * changing this. 682 * 683 * @since 4.0.0 684 * 685 * @param string $base The base. Default the Kind base setting, or 'kind'. 686 */ 687 return (string) apply_filters( 'kind_base_slug', $base ); 688 } 689 690 /** 522 691 * Register the custom taxonomy for kinds. 692 * 693 * @since 2.0.2 523 694 */ 524 695 public static function register() { … … 540 711 'add_or_remove_items' => _x( 'Add or remove kinds', 'add or remove items', 'indieweb-post-kinds' ), 541 712 'choose_from_most_used' => _x( 'Choose from the most used kinds', 'choose most used', 'indieweb-post-kinds' ), 542 'not found' => _x( 'No kinds found', 'no kinds found', 'indieweb-post-kinds' ),713 'not_found' => _x( 'No kinds found', 'no kinds found', 'indieweb-post-kinds' ), 543 714 'no_terms' => _x( 'No kinds', 'no kinds', 'indieweb-post-kinds' ), 544 715 'back_to_items' => __( 'Back to Kinds', 'indieweb-post-kinds' ), … … 546 717 'item_link_description' => __( 'A link to a kind', 'indieweb-post-kinds' ), 547 718 ); 719 720 // The slug is used as the name too: core finds the default term by name, and a translated name would not match. 721 $default_kind = get_option( 'kind_default', 'note' ); 548 722 549 723 $args = array( … … 560 734 'show_admin_column' => true, 561 735 'meta_box_cb' => array( self::class, 'select_metabox' ), 562 'rewrite' => true,736 'rewrite' => array( 'slug' => self::get_kind_base() ), 563 737 'query_var' => true, 564 738 'default_term' => array( 565 'name' => __( 'Article', 'indieweb-post-kinds' ),566 'slug' => 'article',739 'name' => $default_kind, 740 'slug' => $default_kind, 567 741 ), 568 742 569 743 ); 570 744 register_taxonomy( 'kind', array( 'post' ), $args ); 571 add_permastruct( 'kind_date', 'kind/%kind%/%year%/%monthnum%/' ); 572 745 add_permastruct( 'kind_date', self::get_kind_base() . '/%kind%/%year%/%monthnum%/' ); 746 747 /** 748 * Filters the taxonomies that get kind archive rewrite rules. 749 * 750 * Each taxonomy gets rules for /kind/{kind}/{taxonomy}/{term}/, where kind is the Kind base. Rewrite rules 751 * are flushed when the plugin is activated or updated; flush them yourself 752 * (Settings > Permalinks > Save) after changing this. 753 * 754 * @since 3.4.15 755 * 756 * @param string[] $taxonomies Taxonomy names. Default 'post_tag', 'category' and 'series'. 757 */ 573 758 $kind_rewrite_taxonomies = apply_filters( 'kind_rewrite_taxonomies', array( 'post_tag', 'category', 'series' ) ); 574 759 575 760 // For each kind, support filtering by other taxonomies. 576 761 foreach ( $kind_rewrite_taxonomies as $taxonomy ) { 577 add_permastruct( 'kind_' . $taxonomy, 'kind/%kind%/' . $taxonomy . '/%' . $taxonomy . '%' ); 578 } 579 762 add_permastruct( 'kind_' . $taxonomy, self::get_kind_base() . '/%kind%/' . $taxonomy . '/%' . $taxonomy . '%' ); 763 } 764 765 /** 766 * Filters the URL slug for archives that exclude kinds, as in /exclude/kind/{kinds}/. 767 * 768 * Flush rewrite rules after changing this. 769 * 770 * @since 3.4.15 771 * 772 * @param string $slug Slug. Default 'exclude'. 773 */ 580 774 $kind_exclude_slug = apply_filters( 'kind_exclude_slug', 'exclude' ); 581 775 … … 584 778 add_permastruct( 'kind_excludes', $kind_exclude_slug . '/%kind_exclude%/%kind_exclude_terms%' ); 585 779 780 /** 781 * Filters the URL slug for the archive of posts with photos. 782 * 783 * Flush rewrite rules after changing this. 784 * 785 * @since 3.4.15 786 * 787 * @param string $slug Slug. Default 'photos'. 788 */ 586 789 $kind_photos_slug = apply_filters( 'kind_photos_slug', 'photos' ); 790 /** 791 * Filters the URL slug for the firehose feed, which includes every kind. 792 * 793 * Flush rewrite rules after changing this. 794 * 795 * @since 3.6.0 796 * 797 * @param string $slug Slug. Default 'firehose'. 798 */ 587 799 $kind_firehose_slug = apply_filters( 'kind_firehose_slug', 'firehose' ); 588 800 589 801 $year_regex = '([0-9]{4})'; 590 $month_regex = '( 0-9]{2})';802 $month_regex = '([0-9]{2})'; 591 803 $day_regex = '([0-9]{1,})'; 592 804 $pagination_regex = self::get_pagination_regex(); 593 805 $feed_regex = self::get_feed_regex(); 594 806 $feed_regex2 = self::get_feed_regex( false ); 595 $tax_regex = '([^/]*)'; 596 $term_regex = '([^/]*)'; 597 598 // Year Archives for Photos 807 808 // Year Archives for Photos. 599 809 add_rewrite_rule( 600 810 self::generate_permastruct( array( $kind_photos_slug, $year_regex, $pagination_regex ) ), … … 608 818 ); 609 819 610 // Year and Month Archive for Photos 820 // Year and Month Archive for Photos. 611 821 add_rewrite_rule( 612 822 self::generate_permastruct( array( $kind_photos_slug, $year_regex, $month_regex, $pagination_regex ) ), … … 620 830 ); 621 831 622 // Year and Month And Day Archive for Photos 832 // Year and Month And Day Archive for Photos. 623 833 add_rewrite_rule( 624 834 self::generate_permastruct( array( $kind_photos_slug, $year_regex, $month_regex, $day_regex, $pagination_regex ) ), … … 632 842 ); 633 843 634 // Month And Day Archive for Photos 844 // Month And Day Archive for Photos. 635 845 add_rewrite_rule( 636 846 self::generate_permastruct( array( $kind_photos_slug, $month_regex, $day_regex, $pagination_regex ) ), … … 698 908 699 909 /** 910 * Points the kind taxonomy's default term at the Default Kind setting. 911 * 912 * Core sets the default term when the taxonomy is registered; this applies a 913 * change to the setting within the same request. 914 * 915 * @since 4.0.0 916 */ 917 public static function sync_default_term() { 918 $term = get_term_by( 'slug', get_option( 'kind_default', 'note' ), 'kind' ); 919 if ( $term ) { 920 update_option( 'default_term_kind', $term->term_id ); 921 } 922 } 923 924 /** 700 925 * Sets up Default Terms for Kind Taxonomy. 926 * 927 * Creates a term for each registered kind that has none, and updates the 928 * name and description of terms that differ from the kind's, for example 929 * after a translation changes. Terms that already match are not written. 930 * 931 * @since 2.0.2 701 932 */ 702 933 public static function kind_defaultterms() { 703 $terms = self::get_kind_list(); 704 foreach ( $terms as $term ) { 705 if ( ! get_term_by( 'slug', $term, 'kind' ) ) { 934 $existing = get_terms( 935 array( 936 'taxonomy' => 'kind', 937 'hide_empty' => false, 938 ) 939 ); 940 $existing = is_wp_error( $existing ) ? array() : array_column( $existing, null, 'slug' ); 941 foreach ( self::get_kind_list() as $term ) { 942 if ( ! isset( $existing[ $term ] ) ) { 706 943 self::create_post_kind( $term ); 707 } else { 944 continue; 945 } 946 $kind = self::get_post_kind_info( $term ); 947 if ( $kind && ( $existing[ $term ]->name !== $kind->singular_name || $existing[ $term ]->description !== $kind->description ) ) { 708 948 self::update_post_kind( $term ); 709 949 } … … 714 954 * Update our post kind if the term already exists. 715 955 * 956 * @since 3.1.0 957 * 716 958 * @access private 717 959 * 718 * @param $term960 * @param string $term Post kind slug. 719 961 */ 720 962 private static function update_post_kind( $term ) { … … 740 982 * Create our post kind term. 741 983 * 984 * @since 3.1.0 985 * 742 986 * @access private 743 987 * 744 * @param $term988 * @param string $term Post kind slug. 745 989 */ 746 990 private static function create_post_kind( $term ) { 747 991 $kind = self::get_post_kind_info( $term ); 748 992 if ( $kind ) { 993 // The first argument is the term name; wp_insert_term() has no 'name' argument. 749 994 wp_insert_term( 750 $kind->s lug,995 $kind->singular_name, 751 996 'kind', 752 997 array( 753 'name' => $kind->singular_name,754 998 'description' => $kind->description, 755 999 'slug' => $term, … … 762 1006 * Filter our post kind permalink. 763 1007 * 1008 * @since 2.0.2 1009 * 764 1010 * @access public 765 1011 * … … 773 1019 return $permalink; } 774 1020 775 // Get post 1021 // Get post. 776 1022 $post = get_post( $post_id ); 777 1023 if ( ! $post ) { 778 1024 return $permalink; } 779 1025 780 // Get taxonomy terms 781 $terms = wp_get_object_terms( $post->ID, 'kind' );782 if ( ! is_wp_error( $terms ) && ! empty( $terms ) && is_object( $terms[0] ) ) {1026 // Get taxonomy terms; get_the_terms() uses the term cache the main query primes. 1027 $terms = get_the_terms( $post, 'kind' ); 1028 if ( is_array( $terms ) && ! empty( $terms ) && is_object( $terms[0] ) ) { 783 1029 $taxonomy_slug = $terms[0]->slug; 784 1030 } else { … … 789 1035 /** 790 1036 * Fetch the current post kind terms from the current query. 1037 * 1038 * @since 3.0.3 791 1039 * 792 1040 * @access public … … 812 1060 * Prefix Introduced in WP5.5 813 1061 * 1062 * @since 3.5.0 1063 * 814 1064 * @access public 815 1065 * … … 831 1081 * Original Title and Prefix introduced in WP5.5. 832 1082 * 1083 * @since 2.0.2 1084 * 833 1085 * @access public 834 1086 * … … 839 1091 */ 840 1092 public static function kind_archive_title( $title, $original_title = null, $prefix = null ) { 841 $return = array(); 842 $wp_version = get_bloginfo( 'version' ); 843 1093 $return = array(); 1094 1095 /** 1096 * This filter is documented in wp-includes/general-template.php 1097 */ 844 1098 $prefix = apply_filters( 'get_the_archive_title_prefix', $prefix ); 845 1099 … … 884 1138 /** 885 1139 * Filters the post kind archive description. 1140 * 1141 * @since 2.5.2 886 1142 * 887 1143 * @access public … … 911 1167 * Sets Post Format for Post Kind. 912 1168 * 913 * @param int $post_id Post ID 914 * @param WP_Post $post Post Object 915 * @param boolean $update, 1169 * @since 2.5.0 1170 * 1171 * @param int $post_id Post ID. 1172 * @param WP_Post $post Post object. 1173 * @param bool $update Whether this is an existing post being updated. 916 1174 */ 917 1175 public static function post_formats( $post_id, $post, $update ) { … … 925 1183 * Fiters the `<title>` tag parts for the post kind. 926 1184 * 1185 * @since 3.1.3 1186 * 927 1187 * @access public 928 1188 * … … 932 1192 public static function document_title_parts( $parts ) { 933 1193 if ( is_tax() || is_category() || is_tag() ) { 934 $terms = self::get_terms_from_query(); 1194 $terms = self::get_terms_from_query(); 1195 $return = array(); 935 1196 foreach ( $terms as $term ) { 936 1197 if ( 'kind' === $term->taxonomy ) { … … 950 1211 * Callback for the taxonomy meta box for our post kinds taxonomy. 951 1212 * 1213 * @since 2.0.2 1214 * 952 1215 * @access public 953 1216 * … … 955 1218 */ 956 1219 public static function select_metabox( $post ) { 957 $include = get_option( 'kind_termslist' ); 958 $include = array_merge( $include, array( 'note', 'reply', 'article' ) ); 959 // If Simple Location is Enabled, include the check-in type 960 // Filter Kinds 1220 $include = array_merge( Kind_Config::get_termslist(), array( 'note', 'reply', 'article' ) ); 1221 /** 1222 * Filters the kinds offered in the editor and listed by the Kind Menu widget. 1223 * 1224 * By default these are the kinds selected in the settings, plus note, reply and article. 1225 * 1226 * @since 2.0.2 1227 * 1228 * @param string[] $include Kind slugs. 1229 */ 961 1230 $include = array_unique( apply_filters( 'kind_include', $include ) ); 962 // Note cannot be removed or disabled without hacking the code 1231 // Note cannot be removed or disabled without hacking the code. 963 1232 if ( ! in_array( 'note', $include, true ) ) { 964 1233 $include[] = 'note'; 965 1234 } 966 if ( isset( $_GET['kind'] ) ) { 967 $default = get_term_by( 'slug', $_GET['kind'], 'kind' ); 1235 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Preselects the kind on the new post screen; nothing is saved. 1236 $requested = ( isset( $_GET['kind'] ) && is_string( $_GET['kind'] ) ) ? sanitize_key( wp_unslash( $_GET['kind'] ) ) : ''; 1237 if ( '' !== $requested ) { 1238 $default = get_term_by( 'slug', $requested, 'kind' ); 968 1239 } elseif ( 'publish' === get_post_status( $post ) ) { 969 // On existing published posts without a kind fall back on article which most closely mimics the behavior of an unclassified post 1240 // On existing published posts without a kind fall back on article which most closely mimics the behavior of an unclassified post. 970 1241 $default = get_term_by( 'slug', 'article', 'kind' ); 971 1242 } else { … … 1003 1274 * Register our post kinds. 1004 1275 * 1276 * @since 3.1.0 1277 * 1005 1278 * @access public 1006 1279 * 1007 1280 * @param string $slug Post kind slug. 1008 * @param array $args Post kind arguments { 1009 * @param string $singular_name Name for one instance of the kind. 1010 * @param string $name General name for the kind plural. 1011 * @param string $verb The string for the verb or action (liked this). 1012 * @param string $property Microformats 2 property, superseded by properties 1013 * @param array $properties Properties is an array outlining the fields for the particular kind. See Kind Fields class for more details 1014 * @param string $format Post Format that maps to this. 1015 * @param string $description Description of the Kind 1016 * @param url $description-url Link to more information 1017 * @param string $title Should this kind have an explicit title. 1018 * @param boolean $show Show in Settings. 1281 * @param array $args { 1282 * Post kind arguments. Arguments that are not listed are kept and can 1283 * be read as properties of the Post_Kind. 1284 * 1285 * @type string $singular_name Name for one instance of the kind. Default the slug. 1286 * @type string $name General name for the kind, plural. Default the slug. 1287 * @type string $verb The verb or action, such as 'Liked'. Default the slug. 1288 * @type string $property Microformats 2 property, such as 'like-of'. Default empty. 1289 * @type array $properties The fields for the kind. See Kind_Fields. 1290 * @type string $format Post format that maps to this kind. Default 'standard'. 1291 * @type string $icon Icon name. Default the slug. 1292 * @type string $description Description of the kind. 1293 * @type string $description_url Link to more information. 1294 * @type string $shortlink Shortlink code for the kind. 1295 * @type bool $title Whether the kind should have an explicit title. 1296 * @type bool $show Whether to show the kind in Settings. Default false. 1019 1297 * } 1020 * @return bool 1298 * @return bool True if the kind was registered, false if it already exists. 1021 1299 */ 1022 1300 public static function register_post_kind( $slug, $args ) { 1023 1301 $kind = new Post_Kind( $slug, $args ); 1024 // Do not allow reregistering existing kinds 1302 // Do not allow reregistering existing kinds. 1025 1303 if ( isset( self::$kinds[ $slug ] ) ) { 1026 1304 return false; … … 1032 1310 /** 1033 1311 * Retrieve info on a provided post kind. 1312 * 1313 * @since 3.1.0 1034 1314 * 1035 1315 * @access public … … 1045 1325 } 1046 1326 1047 // Enable a hidden post kind1048 1049 1327 /** 1050 1328 * Enables a hidden post kind. 1329 * 1330 * @since 3.1.0 1051 1331 * 1052 1332 * @access public … … 1067 1347 * Retrieve list of registered post kinds. 1068 1348 * 1349 * @since 3.1.0 1350 * 1069 1351 * @access public 1070 1352 * @return array … … 1077 1359 * Returns all translated strings. 1078 1360 * 1361 * @since 2.5.2 1362 * 1079 1363 * @param string $kind Post Kind to return. 1080 * @param string $property The individual property 1364 * @param string $property The individual property. 1081 1365 * @return string|array Return kind-property. If either is set to all, return all. 1082 1366 */ … … 1095 1379 return $k; 1096 1380 } 1097 if ( ! array_key_exists( $property, get_object_vars( $k )) ) {1381 if ( ! $k->has( $property ) ) { 1098 1382 return false; 1099 1383 } … … 1103 1387 /** 1104 1388 * Add available webmention links. 1389 * 1390 * @since 2.3.5 1105 1391 * 1106 1392 * @access public … … 1125 1411 /** 1126 1412 * Add available enclosure links. 1413 * 1414 * @since 3.0.4 1127 1415 * 1128 1416 * @access public … … 1150 1438 * Generate a dropdown of our post kinds for the post list table view. 1151 1439 * 1440 * @since 2.5.2 1441 * 1152 1442 * @access public 1153 1443 * @param string $post_type Current post type being listed. … … 1157 1447 if ( 'post' === $post_type ) { 1158 1448 $taxonomy = 'kind'; 1159 $selected = isset( $_GET[ $taxonomy ] ) ? $_GET[ $taxonomy ] : ''; 1449 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- A list-table filter; nothing is saved. 1450 $selected = ( isset( $_GET[ $taxonomy ] ) && is_string( $_GET[ $taxonomy ] ) ) ? sanitize_key( wp_unslash( $_GET[ $taxonomy ] ) ) : ''; 1160 1451 $kind_taxonomy = get_taxonomy( $taxonomy ); 1161 1452 wp_dropdown_categories( … … 1179 1470 * Set our post kind terms upon publish transition status. 1180 1471 * 1181 * @access public 1182 * @param int $post_id Current post ID 1472 * @since 2.0.2 1473 * 1474 * @access public 1475 * 1476 * @param int $post_id Current post ID. 1183 1477 * @param WP_Post|null $post Current post object. 1184 1478 */ … … 1187 1481 return; 1188 1482 } 1189 $ count= wp_get_post_terms( $post_id, 'kind' );1190 if ( is_countable( $count ) && $count <= 0) {1483 $terms = wp_get_post_terms( $post_id, 'kind' ); 1484 if ( ! is_wp_error( $terms ) && empty( $terms ) ) { 1191 1485 set_post_kind( $post_id, get_option( 'kind_default' ) ); 1192 1486 } … … 1195 1489 /** 1196 1490 * Maybe set our post kind terms upon post status transition. 1491 * 1492 * @since 2.0.2 1197 1493 * 1198 1494 * @access public … … 1211 1507 * Filter the classes on a post's markup. 1212 1508 * 1509 * @since 2.0.2 1510 * 1213 1511 * @access public 1214 1512 * … … 1227 1525 * Returns a pretty, translated version of a post kind slug. 1228 1526 * 1527 * @since 2.1.1 1528 * 1229 1529 * @param string $slug A post format slug. 1230 1530 * @return string The translated post format name. … … 1232 1532 public static function get_post_kind_string( $slug ) { 1233 1533 $string = self::get_kind_info( $slug, 'singular_name' ); 1234 return $s lug ? $slug : '';1534 return $string ? $string : ''; 1235 1535 } 1236 1536 1237 1537 /** 1238 1538 * Returns a link to a post kind index. 1539 * 1540 * @since 2.1.1 1239 1541 * 1240 1542 * @param string $kind The post kind slug. … … 1252 1554 * Retrieve a total count statistic for a given post kind. 1253 1555 * 1556 * @since 3.1.3 1557 * 1254 1558 * @access public 1255 1559 * … … 1267 1571 /** 1268 1572 * Returns the post kind slug for the current post. 1573 * 1574 * @since 2.1.1 1269 1575 * 1270 1576 * @access public … … 1288 1594 * Returns the post kind name for the current post. 1289 1595 * 1290 * @access public 1291 * 1292 * @param WP_Post|int|null $post 1293 * @return array|bool|string 1596 * @since 2.1.1 1597 * 1598 * @access public 1599 * 1600 * @param WP_Post|int|null $post Optional. Post ID or post object. Default the current post. 1601 * @return string|false The kind's translated name, or false if the post has no kind. 1294 1602 */ 1295 1603 public static function get_post_kind( $post = null ) { … … 1306 1614 * Check if a post has any of the given kinds, or any kind. 1307 1615 * 1616 * @since 2.1.1 1617 * 1308 1618 * @uses has_term() 1309 1619 * … … 1325 1635 * Assign a kind to a post. 1326 1636 * 1637 * @since 2.1.1 1638 * 1327 1639 * @param int|WP_Post $post The post for which to assign a kind. 1328 * @param string $kind A kind to assign. Using an empty string or array will default to article. 1329 * @return mixed WP_Error on error. Array of affected term IDs on success. 1640 * @param string $kind Optional. A registered kind's slug. Default 'article'. 1641 * @return array|false|WP_Error Array of affected term IDs on success. WP_Error if the post 1642 * or the kind is not valid, or false on failure. 1330 1643 */ 1331 1644 public static function set_post_kind( $post, $kind = 'article' ) { … … 1341 1654 1342 1655 /** 1343 * Update callback for REST API endpoint. 1344 * 1345 * @access public 1346 * 1347 * @param string $kind Post kind being processed. 1348 * @param $post_array 1349 */ 1350 public static function set_rest_post_kind( $kind, $post_array ) { 1351 if ( isset( $post_array['id'] ) ) { 1352 self::set_post_kind( $post_array['id'], $kind ); 1353 } 1656 * Update callback for the kind REST field. 1657 * 1658 * @since 3.1.0 1659 * 1660 * @access public 1661 * 1662 * @param string $kind Post kind to set. 1663 * @param WP_Post|array $post Post being updated. 1664 * @return true|WP_Error True on success, WP_Error if the kind could not be set. 1665 */ 1666 public static function set_rest_post_kind( $kind, $post ) { 1667 // WordPress passes the post being updated as a WP_Post object. 1668 $post_id = $post instanceof WP_Post ? $post->ID : ( is_array( $post ) && isset( $post['id'] ) ? (int) $post['id'] : 0 ); 1669 if ( ! $post_id ) { 1670 return new WP_Error( 'rest_invalid_post', __( 'Invalid post.', 'indieweb-post-kinds' ), array( 'status' => 400 ) ); 1671 } 1672 if ( ! current_user_can( 'edit_post', $post_id ) ) { 1673 return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to set the kind of this post.', 'indieweb-post-kinds' ), array( 'status' => rest_authorization_required_code() ) ); 1674 } 1675 $result = self::set_post_kind( $post_id, $kind ); 1676 if ( is_wp_error( $result ) ) { 1677 $result->add_data( array( 'status' => 400 ) ); 1678 return $result; 1679 } 1680 return true; 1681 } 1682 1683 /** 1684 * Get callback for the kind REST field. 1685 * 1686 * @since 4.0.0 1687 * 1688 * @access public 1689 * 1690 * @param array $post Prepared post data, as an array with an id key. 1691 * @return string|false The post's kind slug, or false if it has none. 1692 */ 1693 public static function get_rest_post_kind( $post ) { 1694 // The prepared post is an array; passing it to get_post() would cast it to 1. 1695 $post_id = is_array( $post ) && isset( $post['id'] ) ? (int) $post['id'] : 0; 1696 return $post_id ? self::get_post_kind_slug( $post_id ) : false; 1354 1697 } 1355 1698 … … 1357 1700 * Whether or not to display the before kind content. 1358 1701 * 1702 * @since 2.6.6 1703 * 1359 1704 * @access public 1360 1705 * @return mixed|void 1361 1706 */ 1362 1707 public static function before_kind() { 1708 /** 1709 * Filters whether to show the kind's icon or name before the response. 1710 * 1711 * Return false to hide it everywhere and remove the display option from the settings page. 1712 * 1713 * @since 2.6.6 1714 * 1715 * @param bool $display Whether to show it. Default true. 1716 */ 1363 1717 return apply_filters( 'kind_icon_display', true ); 1364 1718 } … … 1367 1721 * Display before Kind - either icon text or no display. 1368 1722 * 1723 * @since 2.6.6 1724 * 1369 1725 * @param string $kind The slug for the kind of the current post. 1370 * @param string $display Override display ;1726 * @param string $display Override display. 1371 1727 * @return string Marked up kind information. 1372 1728 */ … … 1378 1734 $display = get_option( 'kind_display' ); 1379 1735 } 1380 $text = '<span class="kind-display-text">' . self::get_kind_info( $kind, 'verb') . '</span> ';1736 $text = '<span class="kind-display-text">' . esc_html( self::get_kind_info( $kind, 'verb' ) ) . '</span> '; 1381 1737 $icon = self::get_icon( $kind ); 1382 // Hide Icon in Feed View 1738 // Hide Icon in Feed View. 1383 1739 if ( 'text' !== $display && is_feed() ) { 1384 1740 $icon = ''; … … 1399 1755 * Retrieve the icon SVG for a provided post kind. 1400 1756 * 1757 * @since 2.4.0 1758 * 1401 1759 * @access public 1402 1760 * … … 1406 1764 */ 1407 1765 public static function get_icon( $kind, $echo = false ) { 1766 // Icons are shown per post, per widget item and per settings row; read each file once. 1767 static $svgs = array(); 1768 $kind = sanitize_key( $kind ); 1769 if ( ! isset( $svgs[ $kind ] ) ) { 1770 $svg = sprintf( '%1$ssvgs/%2$s.svg', plugin_dir_path( __DIR__ ), $kind ); 1771 $svgs[ $kind ] = file_exists( $svg ) ? (string) file_get_contents( $svg ) : ''; 1772 // A default size for places without the plugin's stylesheet, such as feed readers. CSS overrides it. 1773 $svgs[ $kind ] = preg_replace( '/^<svg\b/', '<svg width="1em" height="1em"', $svgs[ $kind ] ); 1774 } 1775 if ( '' === $svgs[ $kind ] ) { 1776 return ''; 1777 } 1408 1778 $name = self::get_kind_info( $kind, 'singular_name' ); 1409 $svg = sprintf( '%1$ssvgs/%2$s.svg', plugin_dir_path( __DIR__ ), $kind ); 1410 $attributes = apply_filters( 'post_kinds_icon_attributes', 'style="display: inline-block; max-height: 1rem; margin-right: 0.5rem"' ); 1411 if ( file_exists( $svg ) ) { 1412 $return = sprintf( '<span class="svg-icon svg-%1$s" aria-label="%2$s" title="%2$s" %3$s><span aria-hidden="true">%4$s</span></span>', esc_attr( $kind ), esc_attr( $name ), $attributes, file_get_contents( $svg ) ); 1413 } else { 1414 return ''; 1415 } 1779 /** 1780 * Filters the HTML attributes added to the span that wraps a kind icon. 1781 * 1782 * The string is output as-is, so it must be safe, escaped markup. 1783 * 1784 * @since 3.5.8 1785 * @since 4.0.0 The default is empty; the stylesheet sizes the icon. 1786 * 1787 * @param string $attributes HTML attributes. Default empty. 1788 */ 1789 $attributes = (string) apply_filters( 'post_kinds_icon_attributes', '' ); 1790 $attributes = '' === $attributes ? '' : ' ' . $attributes; 1791 $return = sprintf( '<span class="svg-icon svg-%1$s" aria-label="%2$s" title="%2$s"%3$s><span aria-hidden="true">%4$s</span></span>', esc_attr( $kind ), esc_attr( $name ), $attributes, $svgs[ $kind ] ); 1416 1792 if ( $echo ) { 1417 echo $return; // phpcs:ignore 1793 echo $return; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The plugin's own SVG icon; the kind and name are escaped, and the attributes come from the post_kinds_icon_attributes filter. 1418 1794 } 1419 1795 return $return; -
indieweb-post-kinds/trunk/includes/class-kind-view.php
r3001401 r3738687 1 1 <?php 2 2 /** 3 * Post Kind View Class3 * Kind_View class. 4 4 * 5 * Includes Helper Functions to Set Up Display Behavior and Allows Calling of View Templates 5 * @package Post_Kinds 6 * @since 2.0.2 6 7 */ 7 8 8 // The Kind_View class sets up the kind display behavior for kinds 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Sets up how kinds are displayed, and locates and renders the view templates. 13 * 14 * @since 2.0.2 15 */ 9 16 class Kind_View { 17 /** 18 * Adds the hooks that display kind markup. 19 * 20 * @since 2.0.2 21 */ 10 22 public static function init() { 11 23 24 /** 25 * Filters whether to add response markup to post content, excerpts and feeds automatically. 26 * 27 * Return false to place it in the theme instead, for example with 28 * Kind_View::get_display(). 29 * 30 * @since 3.6.0 31 * 32 * @param bool $display Whether to add it. Default true. 33 */ 12 34 if ( apply_filters( 'kind_content_display', true ) ) { 13 35 add_filter( 'the_content', array( static::class, 'content_response' ), 9 ); … … 22 44 /** 23 45 * Filters the attachment image attributes for image post kinds. 46 * 47 * @since 2.6.1 24 48 * 25 49 * @access public … … 55 79 } 56 80 57 /* 58 * Function will locate the correct template and return it. 59 * 60 * @param string $slug Post Kind Slug. 61 * @param string $name Post Kind Term Name. 62 * @return string|null File if located or null if unable to find. 81 /** 82 * Finds the view file for a kind, in the theme or the plugin. 83 * 84 * @since 3.4.0 85 * 86 * @param string $slug View slug, such as 'kind'. 87 * @param string $name Kind slug, such as 'like'. 88 * @return string|null Path of the view file, an empty string if no kind is given, 89 * or null if no view file exists. 63 90 */ 64 91 public static function locate_view( $slug, $name ) { … … 67 94 return ''; 68 95 } 69 $templates[] = "{$slug}-{$name}.php"; 70 $templates[] = "{$slug}.php"; 96 $templates = array( "{$slug}-{$name}.php", "{$slug}.php" ); 97 /** 98 * Filters the folders searched for view files before the plugin's own views folder. 99 * 100 * @since 3.3.0 101 * 102 * @param string[] $paths Folder paths, each with a trailing slash. Default the theme's kind_views folder. 103 */ 71 104 $look = apply_filters( 'kind_view_paths', array( get_theme_file_path( 'kind_views/' ) ) ); 72 105 $look[] = plugin_dir_path( __DIR__ ) . 'views/'; … … 94 127 * Function will return the output. 95 128 * 129 * @since 2.4.0 130 * 96 131 * @access public 97 132 * 98 133 * @param string $slug Post kind slug. 99 134 * @param string $name Post kind term name. 100 * @param array $args Optional Arguments135 * @param array $args Optional. Arguments for the view. 'post_id' is the post's ID. 101 136 * @return string 102 137 */ … … 124 159 } 125 160 126 $author = self::get_hcard( $cite['author'] ); 161 // Every author's h-card, as a list such as "Ann, Bob and Cat". 162 $author = array_filter( array_map( array( static::class, 'get_hcard' ), $cite['authors'] ) ); 163 $author = empty( $author ) ? false : wp_sprintf( '%l', $author ); 127 164 128 165 $url = $cite['url']; … … 140 177 * Return the post kind display. 141 178 * 179 * @since 2.1.1 180 * 142 181 * @access public 143 182 * … … 151 190 if ( 'post' === get_post_type( $post_id ) ) { 152 191 $kind = get_post_kind_slug( $post_id ); 153 $content = self::get_view_part( 'kind', $kind ); 192 $content = self::get_view_part( 'kind', $kind, array( 'post_id' => $post_id ) ); 193 /** 194 * Filters the response markup for a post. 195 * 196 * @since 2.0.2 197 * 198 * @param string $content Rendered view markup. 199 * @param int $post_id Post ID. 200 */ 154 201 return apply_filters( 'kind_response_display', $content, $post_id ); 155 202 } … … 159 206 * Echo the output of get_display. 160 207 * 208 * @since 2.1.1 209 * 161 210 * @access public 162 211 * @param int|null $post_id Post ID. 163 212 */ 164 213 public static function display( $post_id = null ) { 165 echo self::get_display( $post_id ); // phpcs:ignore 214 echo self::get_display( $post_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The view escapes its output as it is built. 166 215 } 167 216 168 217 /** 169 218 * Output the post kind content to the post content. 219 * 220 * @since 2.0.2 170 221 * 171 222 * @access public … … 183 234 } 184 235 global $wp_current_filter; 185 // Don't allow to be added to the_content more than once (prevent infinite loops) 236 // Don't allow to be added to the_content more than once (prevent infinite loops). 186 237 $done = false; 187 238 foreach ( $wp_current_filter as $filter ) { … … 203 254 * Output the post kind content to the feed item content. 204 255 * 256 * @since 3.0.5 257 * 205 258 * @access public 206 259 * … … 219 272 * Output the post kind content to the content if the `jsonfeed` plugin is active. 220 273 * 274 * @since 3.0.5 275 * 221 276 * @access public 222 277 * … … 228 283 229 284 $kind_post = new Kind_Post( $post ); 230 $kind = $kind_post->get_kind(); 231 $type = Kind_Taxonomy::get_kind_info( $kind, 'property' ); 232 $cite = mf2_to_jf2( $kind_post->get_cite() ); 285 $cite = \ParseThis\mf2_to_jf2( $kind_post->get_cite() ); 233 286 234 287 if ( is_string( $cite ) ) { … … 248 301 * Append the post kind display to excerpts. 249 302 * 303 * @since 2.3.3 304 * 250 305 * @access public 251 306 * … … 270 325 * This function will remove the www.prefix if it is part of the URL. 271 326 * 327 * @since 2.3.7 328 * 272 329 * @access public 273 330 * … … 283 340 * Converts an array of attributes and output them as a string. 284 341 * 342 * @since 2.1.1 343 * 285 344 * @access public 286 345 * … … 301 360 /** 302 361 * Converts a URL into a complete `<a>` link with link text. 362 * 363 * @since 2.1.1 303 364 * 304 365 * @access public … … 316 377 $atr = self::get_attributes( $atr ); 317 378 } 318 $return = '<a ' . $atr . ' href="' . $url . '">' . $name. '</a>';379 $return = '<a ' . $atr . ' href="' . esc_url( $url ) . '">' . esc_html( $name ) . '</a>'; 319 380 return $return; 320 381 } … … 323 384 * Create formatted HTML output for a field. 324 385 * 325 * @access public 326 * 327 * @param string $field Content to put in the markup 386 * @since 2.2.0 387 * 388 * @access public 389 * 390 * @param string $field Content to put in the markup. 328 391 * @param string $attr Attributes to add to the tag markup. 329 392 * @param string $type HTML tag type to create. Default span. … … 333 396 public static function get_formatted( $field, $attr, $type = 'span' ) { 334 397 if ( ! isset( $field ) ) { 335 return $string; 336 } 398 return ''; 399 } 400 $type = tag_escape( $type ); 337 401 $string = '<' . $type . $attr . '>' . $field . '</' . $type . '>'; 338 402 return $string; … … 341 405 /** 342 406 * Return post kind-wrapped oEmbed content for a provided URL. 407 * 408 * @since 2.1.1 343 409 * 344 410 * @access public … … 363 429 'dailymotion.com', 364 430 'flickr.com', 365 'imgur.com', 431 'imgur.com', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent -- A domain whose links are embedded with oEmbed, not a remote asset. 366 432 'issuu.com', 367 433 'kickstarter.com', … … 385 451 'youtube.com', 386 452 ); 453 /** 454 * Filters the domains whose cited URLs are embedded with oEmbed. 455 * 456 * @since 3.3.6 457 * 458 * @param string[] $approvelist Domain names. 459 */ 387 460 $approvelist = apply_filters( 'post_kind_embed_approvelist', $approvelist ); 388 461 if ( ! in_array( $host, $approvelist, true ) ) { 389 462 return ''; 390 463 } 391 if ( isset( $GLOBALS['wp_embed'] ) ) { 392 $embed = $GLOBALS['wp_embed']->autoembed( $url ); 393 } 464 if ( ! isset( $GLOBALS['wp_embed'] ) ) { 465 return ''; 466 } 467 $embed = $GLOBALS['wp_embed']->autoembed( $url ); 394 468 if ( 0 === strcmp( $embed, $url ) ) { 395 469 $embed = ''; 396 470 } else { 397 $embed = sprintf( '<div class="kind-embed">%1$s<a class="u-url" href="%2$s"></a></div>', $embed, $url ); 471 // The embed is the oEmbed markup WordPress generated. 472 $embed = sprintf( '<div class="kind-embed">%1$s<a class="u-url" href="%2$s"></a></div>', $embed, esc_url( $url ) ); 398 473 } 399 474 return $embed; … … 402 477 /** 403 478 * Returns an array of domains with the post type terminologies 479 * 480 * @since 2.1.1 404 481 * 405 482 * @access public … … 423 500 $domain = self::extract_domain_name( $url ); 424 501 if ( array_key_exists( $domain, $strings ) ) { 502 /** 503 * Filters the phrase for what a known site's URL points to, such as 'a video' for YouTube. 504 * 505 * It runs only for the sites listed above; other URLs get "a post". 506 * 507 * @since 2.0.2 508 * 509 * @param string $string Translated phrase. 510 */ 425 511 return apply_filters( 'kind_post_type_string', $strings[ $domain ] ); 426 512 } else { … … 431 517 /** 432 518 * Retrieve/Generate the h-card. 519 * 520 * @since 2.1.1 433 521 * 434 522 * @param mixed $author The author to generate Accepts an array or optionally other info. … … 451 539 452 540 /** 453 * Filter for alternate retrieval types 454 * 455 * This could be using WordPress's gravatar system, retrieval by pure URL, etc. 456 * 457 * @param string|boolean $author Defaults to false, but may return string. 458 * @param mixed $author Data on the author, type optional. Defaults to array. 459 * @param array $args Arguments passed to get_hcard. 541 * Filters the author data before it is turned into an h-card. 542 * 543 * Use it to look up author details another way, for example from a URL 544 * or Gravatar. 545 * 546 * @since 2.3.0 547 * 548 * @param mixed $author Author data, usually an array with name, url and photo. 549 * @param array $args Arguments passed to Kind_View::get_hcard(). 460 550 */ 461 551 $author = apply_filters( 'get_hcard_data', $author, $args ); … … 472 562 473 563 /** 474 * Filter for alternate presentation 475 * 476 * @param string|boolean $card Defaults to false, but may return string. 477 * @param mixed $author Data on the author, type optional. Defaults to array. 478 * @param array $args Arguments passed to get_hcard. 564 * Filters the h-card markup for an author. 565 * 566 * Return non-empty markup to replace the plugin's own h-card. The markup is 567 * output as-is, so it must be escaped. 568 * 569 * @since 2.3.0 570 * 571 * @param string $card H-card markup. Default empty, which uses the plugin's markup. 572 * @param array $author Author data, with name, url and photo. 573 * @param array $args Arguments passed to Kind_View::get_hcard(). 479 574 */ 480 575 $card = apply_filters( 'get_hcard', '', $author, $args ); … … 482 577 return $card; 483 578 } 484 // Temporarily drop multi-data on display485 if ( ! empty( $author['url'] ) && is_array( $author['url'] )) {486 $ author['url'] = $author['url'][0];487 }488 489 if ( is_array( $author['name'] ) ) { 490 $author['name'] = $author['name'][0];491 }492 493 if ( empty( $author['name']) && ! empty( $author['url'] ) ) {579 // One card is one author: use the first of any list. Kind_Post::normalize_authors() splits lists into cards. 580 foreach ( array( 'name', 'url', 'photo' ) as $key ) { 581 $value = isset( $author[ $key ] ) ? $author[ $key ] : ''; 582 $author[ $key ] = is_array( $value ) ? (string) reset( $value ) : (string) $value; 583 } 584 585 // Escape everything here: author data often comes from other sites. 586 $author['url'] = esc_url( $author['url'] ); 587 $author['photo'] = esc_url( $author['photo'] ); 588 if ( '' === trim( wp_strip_all_tags( $author['name'] ) ) && ! empty( $author['url'] ) ) { 494 589 $author['name'] = __( 'an author', 'indieweb-post-kinds' ); 495 590 } 591 $author['name'] = esc_html( wp_strip_all_tags( $author['name'] ) ); 592 $args['width'] = absint( $args['width'] ); 593 $args['height'] = absint( $args['height'] ); 496 594 497 595 // If no filter generated the card, generate the card. … … 502 600 } 503 601 if ( empty( $author['url'] ) ) { 504 return sprintf( '<img src="%1 s" class="h-card u-photo p-author" alt="%2s" width=%3s height=%4s/>', $author['photo'], $author['name'], $args['width'], $args['height'] );602 return sprintf( '<img src="%1$s" class="h-card u-photo p-author" alt="%2$s" width="%3$d" height="%4$d" />', $author['photo'], $author['name'], $args['width'], $args['height'] ); 505 603 } else { 506 return sprintf( '<a class="h-card p-author" href="%1 s"><img class="u-photo" src="%2s" alt="%3s" width=%4s height=%5s/></a>', $author['url'], $author['photo'], $author['name'], $args['width'], $args['height'] );604 return sprintf( '<a class="h-card p-author" href="%1$s"><img class="u-photo" src="%2$s" alt="%3$s" width="%4$d" height="%5$d" /></a>', $author['url'], $author['photo'], $author['name'], $args['width'], $args['height'] ); 507 605 } 508 606 break; 509 607 case 'name': 510 return sprintf( '<span class="h-card p-author">%1 s</span>', $author['name'] );608 return sprintf( '<span class="h-card p-author">%1$s</span>', $author['name'] ); 511 609 case 'both': 512 610 if ( ! empty( $author['photo'] ) ) { 513 611 if ( empty( $author['url'] ) ) { 514 return sprintf( '<span class="h-card p-author"><img src="%1 s" class="u-photo" alt="%2s" width=%3s height=%4s />%5s</span>', $author['photo'], $author['name'], $args['width'], $args['height'], $author['name'] );612 return sprintf( '<span class="h-card p-author"><img src="%1$s" class="u-photo" alt="%2$s" width="%3$d" height="%4$d" />%5$s</span>', $author['photo'], $author['name'], $args['width'], $args['height'], $author['name'] ); 515 613 } else { 516 return sprintf( '<a href="%1 s" class="h-card p-author"><img class="u-photo" src="%2s" alt="%3s" width=%4s height=%5s />%6s</a>', $author['url'], $author['photo'], $author['name'], $args['width'], $args['height'], $author['name'] );614 return sprintf( '<a href="%1$s" class="h-card p-author"><img class="u-photo" src="%2$s" alt="%3$s" width="%4$d" height="%5$d" />%6$s</a>', $author['url'], $author['photo'], $author['name'], $args['width'], $args['height'], $author['name'] ); 517 615 } 616 } elseif ( ! empty( $author['url'] ) ) { 617 return sprintf( '<a href="%1$s" class="h-card p-author">%2$s</a>', $author['url'], $author['name'] ); 518 618 } else { 519 return sprintf( '<span class="h-card p-author">%1 s</span>', $author['name'] );619 return sprintf( '<span class="h-card p-author">%1$s</span>', $author['name'] ); 520 620 } 521 621 break; … … 529 629 * Retrieve a title for a given citation. 530 630 * 631 * @since 2.3.0 632 * 531 633 * @access public 532 634 * … … 538 640 return false; 539 641 } 540 if ( empty( $cite['url'] ) ) { 541 if ( ! isset( $cite['name'] ) ) { 642 // FIXME: Temporary Fix for array functionality. 643 $url = isset( $cite['url'] ) ? $cite['url'] : ''; 644 $url = esc_url( is_array( $url ) ? (string) reset( $url ) : (string) $url ); 645 $name = isset( $cite['name'] ) ? $cite['name'] : null; 646 $name = is_array( $name ) ? (string) reset( $name ) : $name; 647 if ( empty( $url ) ) { 648 if ( null === $name ) { 542 649 return ''; 543 650 } 544 return sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 545 } 546 // FIXME: Temporary Fix for array functionality 547 if ( is_array( $cite['url'] ) ) { 548 $cite['url'] = $cite['url'][0]; 549 } 550 if ( ! array_key_exists( 'name', $cite ) ) { 551 // $cite['name'] = self::get_post_type_string( $cite['url'] ); 552 $cite['name'] = $cite['url']; 553 } 554 return sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $cite['url'], $cite['name'] ); 651 return sprintf( '<span class="p-name">%1$s</span>', esc_html( wp_strip_all_tags( (string) $name ) ) ); 652 } 653 if ( null === $name || '' === $name ) { 654 $name = $url; 655 } 656 return sprintf( '<a href="%1$s" class="p-name u-url">%2$s</a>', $url, esc_html( wp_strip_all_tags( (string) $name ) ) ); 555 657 } 556 658 557 659 /** 558 660 * Retrieve site name for given citation. 661 * 662 * @since 2.3.0 559 663 * 560 664 * @access public … … 570 674 return false; 571 675 } 572 return sprintf( '<span class="p-publication">%1s</span>', $cite['publication'] ); 573 } 574 575 /** 576 * Returns a requested Rating item. 577 * 578 * The returned value will be a printf-ready translated string. 579 * 580 * @access 581 * 582 * @param int $rating Rating Value. 583 * @return mixed|string 676 $publication = is_array( $cite['publication'] ) ? (string) reset( $cite['publication'] ) : (string) $cite['publication']; 677 return sprintf( '<span class="p-publication">%1$s</span>', esc_html( wp_strip_all_tags( $publication ) ) ); 678 } 679 680 /** 681 * Returns the citation's summary as escaped plain text. 682 * 683 * Summaries are plain text: tags are stripped, and the text is marked up as 684 * p-summary. Line breaks are kept. 685 * 686 * @since 4.0.0 687 * 688 * @access public 689 * 690 * @param array $cite Normalized citation (see Kind_Post::normalize_cite()). 691 * @return string The summary markup, or an empty string if there is none. 692 */ 693 public static function get_summary( $cite ) { 694 if ( ! is_array( $cite ) || empty( $cite['summary'] ) ) { 695 return ''; 696 } 697 $summary = $cite['summary']; 698 if ( is_array( $summary ) ) { 699 foreach ( array( 'value', 'text', 'html' ) as $key ) { 700 if ( isset( $summary[ $key ] ) ) { 701 $summary = $summary[ $key ]; 702 break; 703 } 704 } 705 $summary = is_array( $summary ) ? (string) reset( $summary ) : $summary; 706 } 707 $summary = trim( wp_strip_all_tags( (string) $summary ) ); 708 if ( '' === $summary ) { 709 return ''; 710 } 711 return sprintf( '<blockquote class="p-summary">%1$s</blockquote>', nl2br( esc_html( $summary ) ) ); 712 } 713 714 /** 715 * Returns a rating as a row of stars. 716 * 717 * @since 3.7.0 718 * 719 * @access public 720 * 721 * @param int|string $rating Rating value. Values above 10 show 10 stars. 722 * @return string Plain text: one star per point, or an empty string. 584 723 */ 585 724 public static function rating_text( $rating ) { 586 if ( ! $rating || 0 === $rating ) { 587 return ''; 588 } 589 590 $ret = ''; 591 for ( $i = $rating; $i > 0; $i-- ) { 592 $ret .= '⭐'; 593 } 594 595 return $ret; 596 } 597 598 /** 599 * Returns a requested RSVP option item. 600 * 601 * The returned value will be a printf-ready translated string. 602 * 603 * @access 604 * 605 * @param string $type RSVP type to return. 606 * @return mixed|string 607 */ 608 public static function rsvp_text( $type ) { 609 if ( ! $type ) { 610 return ''; 611 } 725 $rating = is_numeric( $rating ) ? (int) $rating : 0; 726 if ( $rating <= 0 ) { 727 return ''; 728 } 729 return str_repeat( '⭐', min( $rating, 10 ) ); 730 } 731 732 /** 733 * Returns the text for an RSVP. 734 * 735 * Called with only a type, it returns a printf-ready string whose 736 * %1$s and %2$s are the event URL and name, as before 4.0.0; the caller 737 * must escape both. With a URL or name, it returns the escaped sentence 738 * with the event linked. 739 * 740 * @since 2.6.1 741 * 742 * @access public 743 * 744 * @param string $type RSVP value: yes, no, maybe, interested or remote. 745 * @param string|null $url Optional. Event URL. 746 * @param string|null $name Optional. Event name. Defaults to the URL. 747 * @return string The RSVP text, or an empty string for an unknown type. 748 */ 749 public static function rsvp_text( $type, $url = null, $name = null ) { 612 750 $rsvp = array( 613 /* translators: URL for link to event and name ofevent */614 'yes' => __( 'Attending <a href="%1$s" class="u-in-reply-to">%2$s</a>', 'indieweb-post-kinds' ),615 /* translators: URL for link to event and name ofevent */616 'maybe' => __( 'Might be attending <a href="%1$s" class="u-in-reply-to">%2$s</a>', 'indieweb-post-kinds' ),617 /* translators: URL for link to event and name ofevent */618 'no' => __( 'Unable to Attend <a href="%1$s" class="u-in-reply-to">%2$s</a>', 'indieweb-post-kinds' ),619 /* translators: URL for link to event and name ofevent */620 'interested' => __( 'Interested in Attending <a href="%1$s" class=u-in-reply-to">%2$s</a>', 'indieweb-post-kinds' ),621 /* translators: URL for link to event and name ofevent */622 'remote' => __( 'Attending <a href="%1$s" class="u-in-reply-to">%2$s</a>remotely', 'indieweb-post-kinds' ),751 /* translators: %s: Link to the event */ 752 'yes' => __( 'Attending %s', 'indieweb-post-kinds' ), 753 /* translators: %s: Link to the event */ 754 'maybe' => __( 'Might be attending %s', 'indieweb-post-kinds' ), 755 /* translators: %s: Link to the event */ 756 'no' => __( 'Unable to Attend %s', 'indieweb-post-kinds' ), 757 /* translators: %s: Link to the event */ 758 'interested' => __( 'Interested in Attending %s', 'indieweb-post-kinds' ), 759 /* translators: %s: Link to the event */ 760 'remote' => __( 'Attending %s remotely', 'indieweb-post-kinds' ), 623 761 ); 624 return $rsvp[ $type ]; 762 if ( ! is_string( $type ) || ! array_key_exists( $type, $rsvp ) ) { 763 return ''; 764 } 765 if ( null === $url && null === $name ) { 766 return sprintf( esc_html( $rsvp[ $type ] ), '<a href="%1$s" class="u-in-reply-to">%2$s</a>' ); 767 } 768 $url = esc_url( (string) $url ); 769 $name = ( null === $name || '' === $name ) ? $url : $name; 770 $name = esc_html( wp_strip_all_tags( (string) $name ) ); 771 if ( '' === $url ) { 772 $link = sprintf( '<span class="p-in-reply-to">%1$s</span>', $name ); 773 } else { 774 $link = sprintf( '<a href="%1$s" class="u-in-reply-to">%2$s</a>', $url, $name ); 775 } 776 return sprintf( esc_html( $rsvp[ $type ] ), $link ); 625 777 } 626 778 627 779 /** 628 780 * Returns a requested read status option item. 781 * 782 * @since 3.0.7 629 783 * 630 784 * @access public … … 642 796 'finished' => __( 'Finished Reading: ', 'indieweb-post-kinds' ), 643 797 ); 644 return $read[ $type ];798 return ( is_string( $type ) && isset( $read[ $type ] ) ) ? $read[ $type ] : ''; 645 799 } 646 800 … … 648 802 * Return a string for a requested duration. 649 803 * 650 * @access public 651 * 652 * @param string|Dateinterval $interval Duration to display. 653 * @return string 654 * @throws Exception 804 * @since 2.6.1 805 * @since 4.0.0 Returns escaped markup. 806 * 807 * @access public 808 * 809 * @param string|DateInterval $interval Duration to display. 810 * @return string The duration as an escaped dt-duration time element, or an empty string if it is not a valid duration. 655 811 */ 656 812 public static function display_duration( $interval ) { 657 if ( ! $interval instanceof DateInterval ) { 658 $interval = new DateInterval( $interval ); 659 } 813 $interval = kind_safe_interval( $interval ); 660 814 if ( ! $interval ) { 661 815 return ''; … … 695 849 } 696 850 697 return sprintf( '<time class="dt-duration" datetime="%1$s">%2$s</time>', date_interval_to_iso8601( $interval ), implode( ' ', $duration) );851 return sprintf( '<time class="dt-duration" datetime="%1$s">%2$s</time>', esc_attr( date_interval_to_iso8601( $interval ) ), esc_html( implode( ' ', $duration ) ) ); 698 852 } 699 853 } // End Class -
indieweb-post-kinds/trunk/includes/class-mf2-post.php
r2973430 r3738687 1 1 <?php 2 /** 3 * MF2_Post class. 4 * 5 * @package Post_Kinds 6 * @since 3.1.0 7 */ 8 9 defined( 'ABSPATH' ) || exit; 10 2 11 /** 3 12 * MF2 Post Class 4 13 * 5 * @package Post Kinds 6 * Assists in retrieving/saving microformats 2 properties from a post 14 * Assists in retrieving/saving microformats 2 properties from a post. 15 * 16 * @since 3.1.0 17 * @deprecated 4.0.0 Use Kind_Post. MF2_Post will be removed in a future major version. 7 18 */ 8 19 class MF2_Post implements ArrayAccess { 20 /** 21 * Post ID. 22 * 23 * @since 3.1.0 24 * @var int 25 */ 9 26 public $uid; 27 28 /** 29 * ID of the post author. 30 * 31 * @since 3.1.0 32 * @var int|string 33 */ 10 34 public $post_author; 35 36 /** 37 * Post type. 38 * 39 * @since 3.3.0 40 * @var string 41 */ 11 42 public $post_type; 43 44 /** 45 * Author, as an h-card. 46 * 47 * @since 3.1.0 48 * @var array|string|false 49 */ 12 50 public $author; 51 52 /** 53 * Publication the post appeared in. 54 * 55 * @since 3.1.0 56 * @var string 57 */ 13 58 public $publication; 59 60 /** 61 * Published date, in DATE_W3C format. 62 * 63 * @since 3.1.0 64 * @var string 65 */ 14 66 public $published; 67 68 /** 69 * Updated date, in DATE_W3C format. 70 * 71 * @since 3.1.0 72 * @var string 73 */ 15 74 public $updated; 75 76 /** 77 * Content, with 'html' and 'value' versions. 78 * 79 * @since 3.1.0 80 * @var array 81 */ 16 82 public $content; 83 84 /** 85 * Summary (excerpt). 86 * 87 * @since 3.1.0 88 * @var string 89 */ 17 90 public $summary; 91 92 /** 93 * ID of the parent post. 94 * 95 * @since 3.1.0 96 * @var int 97 */ 18 98 public $post_parent; 99 100 /** 101 * Post kind slug, or media type for an attachment. 102 * 103 * @since 3.1.0 104 * @var string|false|null 105 */ 19 106 public $kind; 107 108 /** 109 * Permalink, or file URL for an attachment. 110 * 111 * @since 3.1.0 112 * @var string|false 113 */ 20 114 public $url; 115 116 /** 117 * Post title. 118 * 119 * @since 3.1.0 120 * @var string 121 */ 21 122 public $name; 123 124 /** 125 * Category and tag names. 126 * 127 * @since 3.1.0 128 * @var string[] 129 */ 22 130 public $category = array(); 131 132 /** 133 * URL of the featured image. 134 * 135 * @since 3.1.0 136 * @var string 137 */ 23 138 public $featured; 139 140 /** 141 * Properties stored in mf2_ prefixed post meta, without the prefix. 142 * 143 * @since 3.1.0 144 * @var array 145 */ 24 146 private $mf2 = array(); 25 147 148 /** 149 * Reads a post's properties. 150 * 151 * @since 3.1.0 152 * @deprecated 4.0.0 Use Kind_Post. 153 * 154 * @param int|WP_Post|string $post Post ID, post object, or the URL of a post or attachment. 155 */ 26 156 public function __construct( $post ) { 157 _deprecated_function( __METHOD__, '4.0.0', 'Kind_Post' ); 27 158 if ( is_numeric( $post ) ) { 28 159 $this->uid = (int) $post; … … 75 206 } 76 207 208 /** 209 * Returns the published date. 210 * 211 * For an attachment, this is the date stored in post meta. 212 * 213 * @since 3.3.0 214 * 215 * @return string|false The date, or false if there is none. 216 */ 77 217 public function get_published() { 78 218 if ( 'attachment' === $this->post_type ) { 79 return $this-> meta['published'] ?? '';219 return $this->get_single( $this->mf2['published'] ?? '' ); 80 220 } 81 221 return get_the_date( DATE_W3C, $this->uid ); 82 222 } 83 223 224 /** 225 * Returns the updated date. 226 * 227 * For an attachment, this is the date stored in post meta. 228 * 229 * @since 3.3.0 230 * 231 * @return string|false The date, or false if there is none. 232 */ 84 233 public function get_updated() { 85 234 if ( 'attachment' === $this->post_type ) { 86 return $this-> meta['updated'] ?? '';235 return $this->get_single( $this->mf2['updated'] ?? '' ); 87 236 } 88 237 return get_the_modified_date( DATE_W3C, $this->uid ); 89 238 } 90 239 240 /** 241 * Returns the publication the post appeared in. 242 * 243 * For posts other than attachments, this is the site title. 244 * 245 * @since 3.3.0 246 * 247 * @return string The publication, or an empty string if an attachment has none. 248 */ 91 249 public function get_publication() { 92 250 if ( 'attachment' !== $this->post_type ) { 93 251 return get_bloginfo( 'title' ); 94 252 } 95 return $this->meta['publication'] ?? ''; 96 } 97 253 return $this->get_single( $this->mf2['publication'] ?? '' ); 254 } 255 256 /** 257 * Whether a property exists, for ArrayAccess. 258 * 259 * @since 3.2.0 260 * 261 * @param string $offset Property name. 262 * @return bool 263 */ 264 #[\ReturnTypeWillChange] 98 265 public function offsetExists( $offset ) { 99 266 $vars = get_object_vars( $this ); … … 104 271 } 105 272 273 /** 274 * Returns a property, for ArrayAccess. 275 * 276 * @since 3.2.0 277 * 278 * @param string $offset Property name. 279 * @return mixed The value, or null if it does not exist. 280 */ 281 #[\ReturnTypeWillChange] 106 282 public function offsetGet( $offset ) { 107 283 $vars = get_object_vars( $this ); … … 115 291 } 116 292 293 /** 294 * Sets a property, for ArrayAccess. 295 * 296 * @since 3.2.0 297 * 298 * @param string $offset Property name. 299 * @param mixed $value The value. 300 */ 301 #[\ReturnTypeWillChange] 117 302 public function offsetSet( $offset, $value ) { 118 303 $this->set( $offset, $value ); 119 304 } 120 305 306 /** 307 * Deletes a property stored in post meta, for ArrayAccess. 308 * 309 * @since 3.2.0 310 * 311 * @param string $offset Property name. 312 */ 313 #[\ReturnTypeWillChange] 121 314 public function offsetUnset( $offset ) { 122 315 $this->delete( $offset ); 123 316 } 124 317 318 /** 319 * Returns the names of a post's categories and tags. 320 * 321 * 'Uncategorized' is left out. 322 * 323 * @since 3.2.0 324 * 325 * @param int $post_id Post ID. 326 * @return string[] Category and tag names. 327 */ 125 328 public function get_categories( $post_id ) { 126 329 $category = array(); 127 // Get a list of categories and extract their names 330 // Get a list of categories and extract their names. 128 331 $post_categories = get_the_terms( $post_id, 'category' ); 129 332 if ( ! empty( $post_categories ) && ! is_wp_error( $post_categories ) ) { … … 131 334 } 132 335 133 // Get a list of tags and extract their names 336 // Get a list of tags and extract their names. 134 337 $post_tags = get_the_terms( $post_id, 'post_tag' ); 135 338 if ( ! empty( $post_tags ) && ! is_wp_error( $post_tags ) ) { … … 142 345 } 143 346 347 /** 348 * Returns the post kind, or the media type of an attachment. 349 * 350 * @since 3.1.0 351 * 352 * @return string|false|null The kind slug, or null for an attachment that is not image, video or audio. 353 */ 144 354 private function get_post_kind() { 145 355 if ( is_attachment( $this->uid ) ) { … … 166 376 } 167 377 378 /** 379 * Returns the post object. 380 * 381 * @since 3.1.0 382 * 383 * @return WP_Post|null The post, or null if it does not exist. 384 */ 168 385 public function get_post() { 169 386 return get_post( $this->uid ); … … 172 389 /** 173 390 * Is prefix in string. 391 * 392 * @since 3.1.0 174 393 * 175 394 * @param string $source The source string. … … 183 402 /** 184 403 * Returns True if Array is Multidimensional. 404 * 405 * @since 3.1.0 185 406 * 186 407 * @param array $arr array. … … 196 417 } 197 418 419 /** 420 * Sanitizes HTML with the allowed tags from the plugin settings. 421 * 422 * @since 3.1.0 423 * 424 * @param mixed $value The value. Anything but a string is returned unchanged. 425 * @return mixed The sanitized value. 426 */ 198 427 public static function sanitize_content( $value ) { 199 428 if ( ! is_string( $value ) ) { … … 207 436 } 208 437 438 /** 439 * Sanitizes a URL or text, recursively. 440 * 441 * @since 3.1.0 442 * 443 * @param string|array $value The value. 444 * @return string|array The sanitized value. 445 */ 209 446 public function sanitize_text( $value ) { 210 447 if ( is_array( $value ) ) { … … 222 459 * Retrieve author 223 460 * 461 * @since 3.1.0 462 * 224 463 * @return boolean|array The result or false if does not exist. 225 464 */ 226 465 public function get_author() { 227 466 if ( ! $this->post_author ) { 228 return $this-> meta['author'] ?? false;229 } 230 // Attachments may have been uploaded by a user but may have metadata for original author 467 return $this->get_single( $this->mf2['author'] ?? false ); 468 } 469 // Attachments may have been uploaded by a user but may have metadata for original author. 231 470 if ( 'attachment' === $this->post_type ) { 232 return $this-> meta['author'] ?? '';471 return $this->get_single( $this->mf2['author'] ?? '' ); 233 472 } 234 473 return array( … … 243 482 244 483 /** 245 * Sets an array with only the mf2 prefixed meta. 246 * 484 * Returns the properties stored in mf2_ prefixed post meta, without the prefix. 485 * 486 * @since 3.1.0 247 487 */ 248 488 private function get_mf2meta() { … … 251 491 return array(); 252 492 } 253 if ( isset( $meta['response'] ) ) { 254 $response = maybe_unserialize( $meta['response'] ); 255 // Retrieve from the old response array and store in new location. 256 if ( ! empty( $response ) ) { 493 // Very old versions stored the citation in the response meta. Read it as 494 // mf2_cite when there is no newer citation; the upgrade routine moves it. 495 if ( isset( $meta['response'] ) && ! isset( $meta['mf2_cite'] ) ) { 496 $response = maybe_unserialize( $meta['response'][0] ); 497 if ( is_array( $response ) ) { 257 498 $new = array(); 258 // Convert to new format and update.259 499 if ( ! empty( $response['title'] ) ) { 260 500 $new['name'] = $response['title']; … … 270 510 } 271 511 if ( ! empty( $response['author'] ) ) { 272 $new['card'] = array(); 273 $new['card']['name'] = $response['author']; 512 $new['card'] = array( 'name' => $response['author'] ); 274 513 if ( ! empty( $response['icon'] ) ) { 275 514 $new['card']['photo'] = $response['icon']; 276 515 } 277 516 } 278 $new = array_unique( $new ); 279 $new['card'] = array_unique( $new['card'] ); 280 if ( isset( $new ) ) { 281 update_post_meta( $this->uid, 'mf2_cite', $new ); 282 delete_post_meta( $this->uid, 'response' ); 283 $meta['cite'] = $new; 517 if ( $new ) { 518 $meta['mf2_cite'] = array( $new ); 284 519 } 285 520 } … … 291 526 unset( $meta[ $key ] ); 292 527 $key = str_replace( 'mf2_', '', $key ); 293 // Do not save microput prefixed instructions 528 // Do not save microput prefixed instructions. 294 529 if ( self::str_prefix( $key, 'mp-' ) ) { 295 530 continue; … … 312 547 * Map Properties Based on Post Type 313 548 * 549 * @since 3.3.0 314 550 */ 315 551 private function get_post_type_properties() { … … 325 561 * Retrieve value 326 562 * 327 * @param string $key The key to retrieve. 328 * @param boolean $single Whether to return a a single value or array if there is only one value. 563 * @since 3.1.0 564 * 565 * @param string|null $key Optional. The key to retrieve. Default null, for every property. 566 * @param bool $single Optional. Whether to unwrap a single value from its array. Default true. 329 567 * @return boolean|string|array The result or false if does not exist. 330 568 */ … … 374 612 } 375 613 614 /** 615 * Whether the post has a property. 616 * 617 * @since 3.1.0 618 * 619 * @param string $key Property name. 620 * @return bool 621 */ 376 622 public function has_key( $key ) { 377 623 $keys = array_merge( get_object_vars( $this ), $this->mf2 ); … … 379 625 } 380 626 627 /** 628 * Unwraps single-item arrays, recursively. 629 * 630 * @since 3.1.0 631 * 632 * @param mixed $value The value to unwrap. 633 * @param bool $discard Optional. Whether to keep only the first item of a list 634 * with more than one item. Default false. 635 * @return mixed The value, with single-item arrays replaced by their item. 636 */ 381 637 private function single_array( $value, $discard = false ) { 382 638 if ( ! is_array( $value ) ) { … … 395 651 } 396 652 653 /** 654 * Stores a property, or several. 655 * 656 * @since 3.1.0 657 * 658 * @param string|array $key Property name, an array of property => value pairs, or an mf2 item. 659 * @param mixed $value Optional. The value. Default null. 660 * @return mixed The result of storing the value, or false or null if nothing was stored. 661 */ 397 662 public function set( $key, $value = null ) { 398 663 if ( ! $key ) { … … 542 807 } 543 808 809 /** 810 * Deletes a property stored in post meta. 811 * 812 * @since 3.1.0 813 * 814 * @param string $key Property name. 815 * @return bool True on success, false on failure. 816 */ 544 817 public function delete( $key ) { 545 818 return delete_post_meta( $this->uid, 'mf2_' . $key ); 546 819 } 547 820 821 /** 822 * Converts mf2 to jf2. 823 * 824 * @since 3.1.0 825 * 826 * @param array $cite The mf2 item. 827 * @return array The jf2 item. 828 */ 548 829 public function mf2_to_jf2( $cite ) { 549 830 return mf2_to_jf2( $cite ); 550 831 } 551 832 833 /** 834 * Returns the first item of an array. 835 * 836 * @since 3.1.0 837 * 838 * @param mixed $value The value. 839 * @return mixed The first item, or the value if it is not an array. 840 */ 552 841 public function get_single( $value ) { 553 842 if ( is_array( $value ) ) { … … 557 846 } 558 847 848 /** 849 * Converts jf2 to mf2. 850 * 851 * @since 3.1.0 852 * 853 * @param array $item The jf2 item. 854 * @param string $type Optional. jf2 type to use if the item has none. Default 'cite'. 855 * @return array The mf2 item. 856 */ 559 857 public function jf2_to_mf2( $item, $type = 'cite' ) { 560 858 if ( is_array( $item ) && isset( $item['type'] ) && ! isset( $item['properties'] ) ) { … … 565 863 } 566 864 567 // Retrieve the right property to use for the link preview based on the kind. 568 // It will return an array of properties or false if it cannot find what it needs. 569 // Also will update old posts with new settings 865 /** 866 * Returns a property for the link preview, as jf2. 867 * 868 * Updates a property stored in the format used before 2.7.0. 869 * 870 * @since 3.1.0 871 * 872 * @param string $property Property name. 873 * @return array|false The property, or false if there is none. 874 */ 570 875 public function fetch( $property ) { 571 876 572 // If the property is not set then exit 877 // If the property is not set then exit. 573 878 if ( ! $property || ! $this->has_key( $property ) ) { 574 879 return false; … … 578 883 $return = array_shift( $return ); 579 884 } 580 // If it is in fact a string it is the pre 2.7.0 format and should be updated 885 // If it is in fact a string it is the pre 2.7.0 format and should be updated. 581 886 if ( is_string( $return ) ) { 582 887 if ( $this->has_key( 'cite' ) ) { … … 596 901 } 597 902 903 /** 904 * Returns the IDs of media attached to a post. 905 * 906 * @since 3.1.0 907 * 908 * @param string $type MIME type, such as 'image'. 909 * @param int|WP_Post $post Post ID or post object. 910 * @return int[] Attachment IDs. 911 */ 598 912 public function get_attached_media( $type, $post ) { 599 913 $posts = get_attached_media( $type, $post ); … … 601 915 } 602 916 917 /** 918 * Returns the post's audio. 919 * 920 * @since 3.1.0 921 * 922 * @return int[]|false Attachment IDs, or false if there are none. 923 */ 603 924 public function get_audios() { 604 925 // Check if the post itself if an audio attachment. … … 615 936 } 616 937 938 /** 939 * Returns the post's videos. 940 * 941 * @since 3.1.0 942 * 943 * @return int[]|false Attachment IDs, or false if there are none. 944 */ 617 945 public function get_videos() { 618 946 // Check if the post itself if an audio attachment. … … 629 957 } 630 958 959 /** 960 * Returns the post's images. 961 * 962 * @since 3.1.0 963 * 964 * @param bool $content_allow Optional. Whether to return images in the content. Default false. 965 * @return int[]|false Attachment IDs, or false if there are none. 966 */ 631 967 public function get_images( $content_allow = false ) { 632 968 // Check if the post itself is an image attachment. … … 636 972 $post_content = $this->content['html'] ?? ''; 637 973 if ( $post_content ) { 974 // Recorded when the post is saved; see Kind_Media_Metadata::save_post(). 638 975 $att_ids = get_post_meta( $this->uid, '_content_img_ids', true ); 639 if ( false === $att_ids ) {640 $att_ids = Kind_Media_Metadata::get_img_from_content( $post->post_content );641 update_post_meta( $this->uid, '_content_img_ids', $att_ids );642 }643 976 if ( $att_ids ) { 644 977 return $content_allow ? $att_ids : array(); … … 648 981 } 649 982 } 650 // If there is a featured image return only that. Otherwise return all images 983 // If there is a featured image return only that. Otherwise return all images. 651 984 $featured = get_post_thumbnail_id( $this->uid ); 652 985 if ( $featured ) { … … 655 988 $att_ids = $this->get_attached_media( 'image', $this->uid ); 656 989 $photos = $this->get( 'photo', false ); 657 if ( is_array( $photos ) ) { 658 if ( ! wp_is_numeric_array( $photos ) ) { 659 $photos = array( $photos ); 660 } 661 662 $photos = $this->sideload_images( $photos ); 663 $this->set( 'photo', $photos ); 990 if ( is_array( $photos ) && ! wp_is_numeric_array( $photos ) ) { 991 $photos = array( $photos ); 664 992 } 665 993 $att_ids = array_merge( $att_ids, $this->get_attachments_from_urls( $photos ) ); … … 670 998 } 671 999 672 private function media_sideload_image( $url, $post_id, $description = null ) { 673 // To prevent sideloading the same image multiple times check for the original URL which will now be stored 674 $ids = get_posts( 675 array( 676 'post_type' => 'attachment', 677 'suppress_filters' => false, 678 'nopaging' => true, 679 'meta_key' => 'mf2_url', 680 'meta_value' => $url, 681 'fields' => 'ids', 682 ) 683 ); 684 if ( ! empty( $ids ) ) { 685 return $ids[0]; 686 } 687 $id = media_sideload_image( $url, $post_id, $description, 'id' ); 688 if ( $id ) { 689 update_post_meta( $id, 'mf2_url', $url ); 690 } 691 return $id; 692 } 693 694 695 private function sideload_images( $photos ) { 696 require_once ABSPATH . 'wp-admin/includes/media.php'; 697 require_once ABSPATH . 'wp-admin/includes/file.php'; 698 require_once ABSPATH . 'wp-admin/includes/image.php'; 699 foreach ( $photos as $key => $value ) { 700 if ( is_string( $value ) ) { 701 if ( ! wp_http_validate_url( $value ) ) { 702 continue; 703 } elseif ( ! attachment_url_to_postid( $value ) ) { 704 $id = self::media_sideload_image( $value, $this->uid ); 705 if ( $id ) { 706 $photos[ $key ] = wp_get_attachment_url( $id ); 707 } 708 } 709 } 710 // Attempt to normalize old data 711 if ( is_array( $value ) ) { 712 $value = mf2_to_jf2( $value ); 713 $id = attachment_url_to_postid( $value['url'] ); 714 if ( ! $id ) { 715 $id = self::media_sideload_image( $value['url'], $this->uid ); 716 if ( $id ) { 717 $value['url'] = wp_get_attachment_url( $id ); 718 } 719 } 720 $args = array( 721 'ID' => $id, 722 'post_title' => $value['name'] ?? '', 723 'post_excerpt' => $value['summary'] ?? '', 724 ); 725 $args = array_filter( $args ); 726 wp_update_post( $args ); 727 unset( $value['name'] ); 728 unset( $value['summary'] ); 729 foreach ( $value as $k => $v ) { 730 update_post_meta( $id, 'mf2_' . $k, $v ); 731 } 732 $photos[ $key ] = $value['url']; 733 } 734 } 735 return $photos; 736 } 737 1000 /** 1001 * Looks up the attachment IDs for media URLs. 1002 * 1003 * @since 3.1.0 1004 * 1005 * @param string|array $urls A URL, or a list of URLs or arrays with a 'url' key. 1006 * @return int[] Attachment IDs found. 1007 */ 738 1008 public function get_attachments_from_urls( $urls ) { 739 1009 if ( is_string( $urls ) ) { -
indieweb-post-kinds/trunk/includes/class-post-kind.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Post Kind Class3 * Post_Kind class. 4 4 * 5 * @package Post Kind6 * Used to define a Post Kind object5 * @package Post_Kinds 6 * @since 3.1.0 7 7 */ 8 8 9 final class Post_Kind { 10 public $id; // Term ID 11 public $slug; // Kind Slug 12 public $name; // Name of Kind - Plural 13 public $singular_name; // Name of Kind - Singular 14 public $verb; // The string for the verb or action (liked this) 15 public $format; // Post Format that Maps to This 16 public $icon; // Icon 9 defined( 'ABSPATH' ) || exit; 10 11 /** 12 * Defines a single post kind, as registered with register_post_kind(). 13 * 14 * @since 3.1.0 15 */ 16 final class Post_Kind implements JsonSerializable { 17 /** 18 * Term ID. 19 * 20 * @since 3.1.0 21 * @var int 22 */ 23 public $id; 24 25 /** 26 * Kind slug. 27 * 28 * @since 3.1.0 29 * @var string 30 */ 31 public $slug; 32 33 /** 34 * General name for the kind, plural. 35 * 36 * @since 3.1.0 37 * @var string 38 */ 39 public $name; 40 41 /** 42 * Name for one instance of the kind. 43 * 44 * @since 3.1.0 45 * @var string 46 */ 47 public $singular_name; 48 49 /** 50 * The verb or action, such as 'Liked'. 51 * 52 * @since 3.1.0 53 * @var string 54 */ 55 public $verb; 56 57 /** 58 * Post format that maps to this kind. 59 * 60 * @since 3.1.0 61 * @var string 62 */ 63 public $format; 64 65 /** 66 * Icon name. 67 * 68 * @since 3.1.0 69 * @var string 70 */ 71 public $icon; 72 73 /** 74 * Description of the kind. 75 * 76 * @since 3.1.0 77 * @var string 78 */ 17 79 public $description; 80 81 /** 82 * Link to more information about the kind. 83 * 84 * @since 3.1.0 85 * @var string 86 */ 18 87 public $description_url; 19 public $title; // Should this Kind Have an Explicit Title 20 public $show; // Show in Settings 21 public $property; // Primary Property 22 public $properties; // Array of Properties 23 public $shortlink; // Shortlink Coding per http://tantek.pbworks.com/w/page/21743973/Whistle#design 24 88 89 /** 90 * Whether the kind should have an explicit title. 91 * 92 * @since 3.1.0 93 * @var bool 94 */ 95 public $title; 96 97 /** 98 * Whether to show the kind in Settings. 99 * 100 * @since 3.1.0 101 * @var bool 102 */ 103 public $show; 104 105 /** 106 * Primary microformats 2 property, such as 'like-of'. 107 * 108 * @since 3.1.0 109 * @var string 110 */ 111 public $property; 112 113 /** 114 * The fields for the kind. See Kind_Fields. 115 * 116 * @since 3.1.0 117 * @var array 118 */ 119 public $properties; 120 121 /** 122 * Shortlink code, per http://tantek.pbworks.com/w/page/21743973/Whistle#design. 123 * 124 * @since 3.4.0 125 * @var string 126 */ 127 public $shortlink; 128 129 /** 130 * Arguments given at registration that are not declared properties, such as 131 * those a custom kind adds. Kept here rather than as dynamic properties, 132 * which are deprecated in PHP 8.2, and still readable as $kind->name. 133 * 134 * @since 4.0.0 135 * @var array 136 */ 137 private $extra = array(); 138 139 /** 140 * Defines a kind. 141 * 142 * @since 3.1.0 143 * 144 * @param string $slug Kind slug. 145 * @param array $args Optional. Kind arguments. See Kind_Taxonomy::register_post_kind(). 146 */ 25 147 public function __construct( $slug, $args = array() ) { 26 148 $this->slug = $slug; … … 28 150 } 29 151 152 /** 153 * Sets the kind's properties from its arguments, filling in defaults. 154 * 155 * Arguments that are not declared properties are kept as extra arguments. 156 * 157 * @since 3.1.0 158 * 159 * @param array $args Kind arguments. See Kind_Taxonomy::register_post_kind(). 160 */ 30 161 public function set_props( $args ) { 31 162 $defaults = array( … … 35 166 'format' => 'standard', 36 167 'icon' => $this->slug, 37 'show' => 'false',168 'show' => false, 38 169 'property' => '', 39 170 ); 40 171 $args = wp_parse_args( $args, $defaults ); 41 172 foreach ( $args as $property_name => $property_value ) { 42 $this->$property_name = $property_value; 173 if ( 'extra' !== $property_name && property_exists( $this, $property_name ) ) { 174 $this->$property_name = $property_value; 175 } else { 176 $this->extra[ $property_name ] = $property_value; 177 } 43 178 } 44 179 } 180 181 /** 182 * Reads an extra argument given at registration. 183 * 184 * @since 4.0.0 185 * 186 * @param string $name Argument name. 187 * @return mixed The value, or null if it was not given. 188 */ 189 public function __get( $name ) { 190 return isset( $this->extra[ $name ] ) ? $this->extra[ $name ] : null; 191 } 192 193 /** 194 * Whether an extra argument was given at registration. 195 * 196 * @since 4.0.0 197 * 198 * @param string $name Argument name. 199 * @return bool 200 */ 201 public function __isset( $name ) { 202 return isset( $this->extra[ $name ] ); 203 } 204 205 /** 206 * Sets an extra argument, without creating a dynamic property. 207 * 208 * @since 4.0.0 209 * 210 * @param string $name Argument name. 211 * @param mixed $value Value. 212 */ 213 public function __set( $name, $value ) { 214 $this->extra[ $name ] = $value; 215 } 216 217 /** 218 * Whether the kind has a declared property or an extra argument of this name. 219 * 220 * @since 4.0.0 221 * 222 * @param string $name Property or argument name. 223 * @return bool 224 */ 225 public function has( $name ) { 226 return ( 'extra' !== $name && property_exists( $this, $name ) ) || array_key_exists( $name, $this->extra ); 227 } 228 229 /** 230 * Returns the kind's declared properties and extra arguments, for JSON. 231 * 232 * @since 4.0.0 233 * 234 * @return array 235 */ 236 #[\ReturnTypeWillChange] 237 public function jsonSerialize() { 238 $vars = get_object_vars( $this ); 239 unset( $vars['extra'] ); 240 return array_merge( $this->extra, $vars ); 241 } 45 242 } -
indieweb-post-kinds/trunk/includes/compat.php
r2973430 r3738687 1 1 <?php 2 3 // get_feed_build_date introduced in WordPress 5.2 4 if ( ! function_exists( 'get_feed_build_date' ) ) { 5 6 function get_feed_build_date( $format ) { 7 global $wp_query; 8 9 if ( empty( $wp_query ) || ! $wp_query->have_posts() ) { 10 // Fallback to last time any post was modified or published. 11 return get_lastpostmodified( 'GMT' ); 12 } 13 14 // Extract the post modified times from the posts. 15 $modified_times = wp_list_pluck( $wp_query->posts, 'post_modified_gmt' ); 16 17 // If this is a comment feed, check those objects too. 18 if ( $wp_query->is_comment_feed() && $wp_query->comment_count ) { 19 // Extract the comment modified times from the comments. 20 $comment_times = wp_list_pluck( $wp_query->comments, 'comment_date_gmt' ); 21 22 // Add the comment times to the post times for comparison. 23 $modified_times = array_merge( $modified_times, $comment_times ); 24 } 25 26 // Determine the maximum modified time. 27 $max_modified_time = max( 28 array_map( 29 function ( $time ) use ( $format ) { 30 return mysql2date( $format, $time, false ); 31 }, 32 $modified_times 33 ) 34 ); 35 36 /** 37 * Filters the date the last post or comment in the query was modified. 38 * 39 * @since 5.2.0 40 * 41 * @param string $max_modified_time Date the last post or comment was modified in the query. 42 * @param string $format The date format requested in get_feed_build_date. 43 */ 44 return apply_filters( 'get_feed_build_date', $max_modified_time, $format ); 2 /** 3 * Polyfills for functions added to WordPress or PHP after the plugin's minimums 4 * (WordPress 6.2, PHP 7.4). 5 * 6 * Each polyfill is a copy of the core function, guarded by function_exists(), 7 * and notes the version that introduced it so it can be removed once the 8 * minimum catches up. ClassicPress may already provide some of them. 9 * 10 * @package Post_Kinds 11 */ 12 13 defined( 'ABSPATH' ) || exit; 14 15 // wp_get_admin_notice() was introduced in WordPress 6.4. 16 if ( ! function_exists( 'wp_get_admin_notice' ) ) { 17 /** 18 * Creates and returns the markup for an admin notice. 19 * 20 * @since 6.4.0 21 * 22 * @param string $message The message. 23 * @param array $args { 24 * Optional. An array of arguments for the admin notice. Default empty array. 25 * 26 * @type string $type Optional. The type of admin notice. 27 * For example, 'error', 'success', 'warning', 'info'. 28 * Default empty string. 29 * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. 30 * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. 31 * @type string[] $additional_classes Optional. A string array of class names. Default empty array. 32 * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. 33 * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. 34 * } 35 * @return string The markup for an admin notice. 36 */ 37 function wp_get_admin_notice( $message, $args = array() ) { 38 $defaults = array( 39 'type' => '', 40 'dismissible' => false, 41 'id' => '', 42 'additional_classes' => array(), 43 'attributes' => array(), 44 'paragraph_wrap' => true, 45 ); 46 47 $args = wp_parse_args( $args, $defaults ); 48 49 /** 50 * Filters the arguments for an admin notice. 51 * 52 * @since 6.4.0 53 * 54 * @param array $args The arguments for the admin notice. 55 * @param string $message The message for the admin notice. 56 */ 57 $args = apply_filters( 'wp_admin_notice_args', $args, $message ); 58 $id = ''; 59 $classes = 'notice'; 60 $attributes = ''; 61 62 if ( is_string( $args['id'] ) ) { 63 $trimmed_id = trim( $args['id'] ); 64 65 if ( '' !== $trimmed_id ) { 66 $id = 'id="' . $trimmed_id . '" '; 67 } 68 } 69 70 if ( is_string( $args['type'] ) ) { 71 $type = trim( $args['type'] ); 72 73 if ( str_contains( $type, ' ' ) ) { 74 _doing_it_wrong( 75 __FUNCTION__, 76 sprintf( 77 /* translators: %s: The "type" key. */ 78 esc_html__( 'The %s key must be a string without spaces.', 'default' ), 79 '<code>type</code>' 80 ), 81 '6.4.0' 82 ); 83 } 84 85 if ( '' !== $type ) { 86 $classes .= ' notice-' . $type; 87 } 88 } 89 90 if ( true === $args['dismissible'] ) { 91 $classes .= ' is-dismissible'; 92 } 93 94 if ( is_array( $args['additional_classes'] ) && ! empty( $args['additional_classes'] ) ) { 95 $classes .= ' ' . implode( ' ', $args['additional_classes'] ); 96 } 97 98 if ( is_array( $args['attributes'] ) && ! empty( $args['attributes'] ) ) { 99 $attributes = ''; 100 foreach ( $args['attributes'] as $attr => $val ) { 101 if ( is_bool( $val ) ) { 102 $attributes .= $val ? ' ' . $attr : ''; 103 } elseif ( is_int( $attr ) ) { 104 $attributes .= ' ' . esc_attr( trim( $val ) ); 105 } elseif ( $val ) { 106 $attributes .= ' ' . $attr . '="' . esc_attr( trim( $val ) ) . '"'; 107 } 108 } 109 } 110 111 if ( false !== $args['paragraph_wrap'] ) { 112 $message = "<p>$message</p>"; 113 } 114 115 $markup = sprintf( '<div %1$sclass="%2$s"%3$s>%4$s</div>', $id, $classes, $attributes, $message ); 116 117 /** 118 * Filters the markup for an admin notice. 119 * 120 * @since 6.4.0 121 * 122 * @param string $markup The HTML markup for the admin notice. 123 * @param string $message The message for the admin notice. 124 * @param array $args The arguments for the admin notice. 125 */ 126 return apply_filters( 'wp_admin_notice_markup', $markup, $message, $args ); 45 127 } 46 128 } 47 129 48 // Backcompat for wp_filter_content_tags introduced in 5.5. 49 if ( ! function_exists( 'wp_filter_content_tags' ) ) { 50 function wp_filter_content_tags( $content ) { 51 return wp_make_content_images_responsive( $content ); // phpcs:ignore 130 // wp_admin_notice() was introduced in WordPress 6.4. 131 if ( ! function_exists( 'wp_admin_notice' ) ) { 132 /** 133 * Outputs an admin notice. 134 * 135 * @since 6.4.0 136 * 137 * @param string $message The message to output. 138 * @param array $args { 139 * Optional. An array of arguments for the admin notice. Default empty array. 140 * 141 * @type string $type Optional. The type of admin notice. 142 * For example, 'error', 'success', 'warning', 'info'. 143 * Default empty string. 144 * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. 145 * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. 146 * @type string[] $additional_classes Optional. A string array of class names. Default empty array. 147 * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. 148 * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. 149 * } 150 */ 151 function wp_admin_notice( $message, $args = array() ) { 152 /** 153 * Fires before an admin notice is output. 154 * 155 * @since 6.4.0 156 * 157 * @param string $message The message for the admin notice. 158 * @param array $args The arguments for the admin notice. 159 */ 160 do_action( 'wp_admin_notice', $message, $args ); 161 162 echo wp_kses_post( wp_get_admin_notice( $message, $args ) ); 52 163 } 53 164 } 165 166 // wp_trigger_error() was introduced in WordPress 6.4 (ClassicPress 2.2.0). 167 if ( ! function_exists( 'wp_trigger_error' ) ) { 168 /** 169 * Generates a user-level error/warning/notice/deprecation message. 170 * 171 * Generates the message when `WP_DEBUG` is true. 172 * 173 * @since 6.4.0 174 * 175 * @param string $function_name The function that triggered the error. 176 * @param string $message The message explaining the error. 177 * The message can contain allowed HTML 'a' (with href), 'code', 178 * 'br', 'em', and 'strong' tags and http or https protocols. 179 * If it contains other HTML tags or protocols, the message should be escaped 180 * before passing to this function to avoid being stripped {@see wp_kses()}. 181 * @param int $error_level Optional. The designated error type for this error. 182 * Only works with E_USER family of constants. Default E_USER_NOTICE. 183 * 184 * @throws WP_Exception If the error level is E_USER_ERROR. Exception where WP_Exception does not exist. 185 */ 186 function wp_trigger_error( $function_name, $message, $error_level = E_USER_NOTICE ) { 187 188 // Bail out if WP_DEBUG is not turned on. 189 if ( ! WP_DEBUG ) { 190 return; 191 } 192 193 /** 194 * Fires when the given function triggers a user-level error/warning/notice/deprecation message. 195 * 196 * Can be used for debug backtracking. 197 * 198 * @since 6.4.0 199 * 200 * @param string $function_name The function that was called. 201 * @param string $message A message explaining what has been done incorrectly. 202 * @param int $error_level The designated error type for this error. 203 */ 204 do_action( 'wp_trigger_error_run', $function_name, $message, $error_level ); 205 206 if ( ! empty( $function_name ) ) { 207 $message = sprintf( '%s(): %s', $function_name, $message ); 208 } 209 210 $message = wp_kses( 211 $message, 212 array( 213 'a' => array( 'href' => true ), 214 'br' => array(), 215 'code' => array(), 216 'em' => array(), 217 'strong' => array(), 218 ), 219 array( 'http', 'https' ) 220 ); 221 222 if ( E_USER_ERROR === $error_level ) { 223 // WP_Exception was also introduced in WordPress 6.4. 224 $exception = class_exists( 'WP_Exception' ) ? 'WP_Exception' : 'Exception'; 225 throw new $exception( $message ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped 226 } 227 228 trigger_error( $message, $error_level ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error, WordPress.Security.EscapeOutput.OutputNotEscaped 229 } 230 } -
indieweb-post-kinds/trunk/includes/kind-functions.php
r3014138 r3738687 1 1 <?php 2 2 /** 3 * Kind Functions3 * Global functions for registering, reading and displaying kinds. 4 4 * 5 * Global Scoped Functions for Handling Kinds. 6 * 7 * @package Post Kinds 5 * @package Post_Kinds 6 * @since 2.0.2 8 7 */ 9 8 10 /** 11 * 12 */ 13 function register_post_kind( $slug, $args ) { 14 Kind_Taxonomy::register_post_kind( $slug, $args ); 15 } 16 17 function set_post_kind_visibility( $slug, $show = true ) { 18 Kind_Taxonomy::set_post_kind_visibility( $slug, $show ); 19 } 20 21 /** 22 * Retrieves an array of post kind slugs. 23 * 24 * @return array The array of post kind slugs. 25 */ 26 function get_post_kind_slugs() { 27 return Kind_Taxonomy::get_post_kind_slugs(); 28 } 29 30 /** 31 * Returns a pretty, translated version of a post kind slug 32 * 33 * @param string $slug A post format slug. 34 * @return string The translated post format name. 35 */ 36 function get_post_kind_string( $slug ) { 37 return Kind_Taxonomy::get_post_kind_string( $slug ); 38 } 39 40 /** 41 * Returns a link to a post kind index. 42 * 43 * @param string $kind The post kind slug. 44 * @return string The post kind term link. 45 */ 46 function get_post_kind_link( $kind ) { 47 return Kind_Taxonomy::get_post_kind_link( $kind ); 48 } 49 50 /** 51 * Returns the post kind slug for the current post. 52 * 53 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post. 54 * @return string The post kind slug. 55 */ 56 function get_post_kind_slug( $post = null ) { 57 return Kind_Taxonomy::get_post_kind_slug( $post ); 58 } 59 60 /** 61 * Returns the post kind name for the current post. 62 * 63 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post. 64 * @return string The post kind name. 65 */ 66 function get_post_kind( $post = null ) { 67 return Kind_Taxonomy::get_post_kind( $post ); 68 } 69 70 /** 71 * Check if a post has any of the given kinds, or any kind. 72 * 73 * @uses has_term() 74 * 75 * @param string|array $kinds Optional. The kind to check. 76 * @param object|int $post Optional. The post to check. If not supplied, defaults to the current post if used in the loop. 77 * @return bool True if the post has any of the given kinds (or any kind, if no kind specified), false otherwise. 78 */ 79 function has_post_kind( $kinds = array(), $post = null ) { 80 return Kind_Taxonomy::has_post_kind( $kinds, $post ); 81 } 82 83 /** 84 * Assign a kind to a post 85 * 86 * @param int|object $post The post for which to assign a kind. 87 * @param string $kind A kind to assign. Using an empty string or array will default to note. 88 * @return mixed WP_Error on error. Array of affected term IDs on success. 89 */ 90 function set_post_kind( $post, $kind ) { 91 return Kind_Taxonomy::set_post_kind( $post, $kind ); 92 } 93 94 /** 95 * Return the Displayed Response for a Specific Kind 96 * 97 * @param $slug 98 * @param $name 99 * @param $args 100 * @return string 101 */ 102 103 function get_kind_view_part( $slug, $name = null, $args = null ) { 104 Kind_View::get_view_part( $slug, $name, $args ); 105 } 106 107 function kind_display( $post_id = null ) { 108 echo Kind_View::get_display( $post_id ); // phpcs:ignore 109 } 110 111 function kind_flatten_array( $a ) { 112 if ( ! is_array( $a ) ) { 9 defined( 'ABSPATH' ) || exit; 10 11 if ( ! function_exists( 'register_post_kind' ) ) { 12 /** 13 * Registers a post kind. 14 * 15 * @since 3.1.0 16 * 17 * @param string $slug Post kind slug. 18 * @param array $args Post kind arguments. See Kind_Taxonomy::register_post_kind(). 19 */ 20 function register_post_kind( $slug, $args ) { 21 Kind_Taxonomy::register_post_kind( $slug, $args ); 22 } 23 } 24 25 if ( ! function_exists( 'set_post_kind_visibility' ) ) { 26 /** 27 * Shows or hides a post kind in Settings. 28 * 29 * @since 3.1.0 30 * 31 * @param string $slug Post kind slug. 32 * @param bool $show Optional. Whether to show the kind. Default true. 33 */ 34 function set_post_kind_visibility( $slug, $show = true ) { 35 Kind_Taxonomy::set_post_kind_visibility( $slug, $show ); 36 } 37 } 38 39 if ( ! function_exists( 'get_post_kind_slugs' ) ) { 40 /** 41 * Retrieves an array of post kind slugs. 42 * 43 * @since 0.30 44 * 45 * @return array The array of post kind slugs. 46 */ 47 function get_post_kind_slugs() { 48 return Kind_Taxonomy::get_kind_list(); 49 } 50 } 51 52 if ( ! function_exists( 'get_post_kind_string' ) ) { 53 /** 54 * Returns a pretty, translated version of a post kind slug. 55 * 56 * @since 0.30 57 * 58 * @param string $slug A post kind slug. 59 * @return string The translated post kind name, or an empty string if the kind is not registered. 60 */ 61 function get_post_kind_string( $slug ) { 62 return Kind_Taxonomy::get_post_kind_string( $slug ); 63 } 64 } 65 66 if ( ! function_exists( 'get_post_kind_link' ) ) { 67 /** 68 * Returns a link to a post kind index. 69 * 70 * @since 0.30 71 * 72 * @param string $kind The post kind slug. 73 * @return string|WP_Error|false The post kind term link, a WP_Error from get_term_link(), 74 * or false if the kind has no term. 75 */ 76 function get_post_kind_link( $kind ) { 77 return Kind_Taxonomy::get_post_kind_link( $kind ); 78 } 79 } 80 81 if ( ! function_exists( 'get_post_kind_slug' ) ) { 82 /** 83 * Returns the post kind slug for the current post. 84 * 85 * @since 0.30 86 * 87 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post. 88 * @return string|false The post kind slug, or false if the post has no kind. 89 */ 90 function get_post_kind_slug( $post = null ) { 91 return Kind_Taxonomy::get_post_kind_slug( $post ); 92 } 93 } 94 95 if ( ! function_exists( 'get_post_kind' ) ) { 96 /** 97 * Returns the post kind name for the current post. 98 * 99 * @since 0.30 100 * 101 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post. 102 * @return string|false The translated post kind name, or false if the post has no kind. 103 */ 104 function get_post_kind( $post = null ) { 105 return Kind_Taxonomy::get_post_kind( $post ); 106 } 107 } 108 109 if ( ! function_exists( 'has_post_kind' ) ) { 110 /** 111 * Check if a post has any of the given kinds, or any kind. 112 * 113 * @since 0.30 114 * 115 * @uses has_term() 116 * 117 * @param string|array $kinds Optional. The kind to check. 118 * @param WP_Post|int $post Optional. The post to check. If not supplied, defaults to the current post if used in the loop. 119 * @return bool True if the post has any of the given kinds (or any kind, if no kind specified), false otherwise. 120 */ 121 function has_post_kind( $kinds = array(), $post = null ) { 122 return Kind_Taxonomy::has_post_kind( $kinds, $post ); 123 } 124 } 125 126 if ( ! function_exists( 'set_post_kind' ) ) { 127 /** 128 * Assign a kind to a post. 129 * 130 * @since 0.30 131 * 132 * @param int|WP_Post $post The post for which to assign a kind. 133 * @param string $kind A registered kind's slug. 134 * @return array|false|WP_Error Array of affected term IDs on success. WP_Error if the post 135 * or the kind is not valid, or false on failure. 136 */ 137 function set_post_kind( $post, $kind ) { 138 return Kind_Taxonomy::set_post_kind( $post, $kind ); 139 } 140 } 141 142 if ( ! function_exists( 'get_kind_view_part' ) ) { 143 /** 144 * Returns the rendered view for a kind. 145 * 146 * @since 2.5.0 147 * 148 * @param string $slug View slug, such as 'kind'. 149 * @param string $name Optional. Kind slug, such as 'like'. Default null. 150 * @param array|null $args Optional. Arguments for the view. See Kind_View::get_view_part(). Default null. 151 * @return string The view's markup. 152 */ 153 function get_kind_view_part( $slug, $name = null, $args = null ) { 154 return Kind_View::get_view_part( $slug, $name, $args ); 155 } 156 } 157 158 if ( ! function_exists( 'kind_display' ) ) { 159 /** 160 * Outputs the kind markup for a post. 161 * 162 * For themes that place the markup themselves instead of adding it to the content. 163 * 164 * @since 2.6.1 165 * 166 * @param int|null $post_id Optional. Post ID. Default the current post. 167 */ 168 function kind_display( $post_id = null ) { 169 echo Kind_View::get_display( $post_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The view escapes its output as it is built. 170 } 171 } 172 173 if ( ! function_exists( 'kind_flatten_array' ) ) { 174 /** 175 * Removes empty values from an array and unwraps single-item lists, recursively. 176 * 177 * @since 2.7.5 178 * 179 * @param mixed $a The value to flatten. 180 * @return mixed The flattened value. An empty string if nothing is left. 181 */ 182 function kind_flatten_array( $a ) { 183 if ( ! is_array( $a ) ) { 184 return $a; 185 } 186 if ( wp_is_numeric_array( $a ) ) { 187 $a = array_values( array_filter( array_map( 'kind_flatten_array', $a ) ) ); 188 } else { 189 $a = array_filter( $a ); 190 } 191 if ( empty( $a ) ) { 192 return ''; 193 } 194 if ( 1 === count( $a ) && isset( $a[0] ) ) { 195 return $a[0]; 196 } 113 197 return $a; 114 198 } 115 if ( wp_is_numeric_array( $a ) ) { 116 $array = array_map( 'kind_flatten_array', $a ); 117 } 118 $array = array_filter( $a ); 119 if ( 1 === count( $a ) ) { 120 return $a[0]; 121 } 122 } 123 124 // Return any sort of src urls in content 125 function kind_src_url_in_content( $content ) { 126 if ( ! $content ) { 199 } 200 201 if ( ! function_exists( 'kind_src_url_in_content' ) ) { 202 /** 203 * Returns every src attribute value in some markup. 204 * 205 * @since 3.0.0 206 * 207 * @param string $content The markup. 208 * @return string[]|int The src URLs, or 0 if there are none. 209 */ 210 function kind_src_url_in_content( $content ) { 211 if ( ! $content ) { 212 return 0; 213 } 214 if ( preg_match_all( '@src="([^"]+)"@', $content, $output ) ) { 215 return array_pop( $output ); 216 } 127 217 return 0; 128 218 } 129 if ( preg_match_all( '@src="([^"]+)"@', $content, $output ) ) { 130 return array_pop( $output ); 131 } 132 return 0; 133 } 134 135 /** 136 * Get a Marketed Up Link to the Post. 137 * 138 * @param WP_Post|null Post to Display. 139 * @param string|array Classes to add to link 140 * @param string|array Classes to add to the date 141 * @return string Marked up link to a post. 142 **/ 143 144 function kind_get_the_link( $post = null, $cls = null, $date_cls = null ) { 145 $post = get_post( $post ); 146 $kind = get_post_kind_slug( $post ); 147 148 if ( is_array( $cls ) ) { 149 $cls = implode( ' ', $cls ); 150 } 151 152 if ( is_array( $date_cls ) ) { 153 $date_cls = implode( ' ', $date_cls ); 154 } 155 156 $time_string = '<time class="%3$s" datetime="%1$s">%2$s</time>'; 157 $time_string = sprintf( 158 $time_string, 159 esc_attr( get_the_date( DATE_W3C, $post ) ), 160 get_the_date( '', $post ), 161 $date_cls 162 ); 163 return sprintf( '<a class="%4$s" href="%2$s">%1$s</a> - %3$s', kind_get_the_title( $post, $kind ), get_the_permalink( $post ), $time_string, esc_attr( $cls ) ); 164 } 165 166 167 /** 168 * Get a Generated Title for a Post as Most Post Kinds do Not Have an Explicit Title. 169 * 170 * @param WP_Post|null Post to Display. 171 * @param array $args Arguments. 172 * @return string Marked up link to a post. 173 **/ 174 function kind_get_the_title( $post = null, $args = array() ) { 175 $defaults = array( 176 'photo_size' => array( 32, 32 ), 177 ); 178 179 $args = wp_parse_args( $args, $defaults ); 180 181 $post = get_post( $post ); 182 $kind = get_post_kind_slug( $post ); 183 $title = get_the_title( $post ); 184 $before = Kind_Taxonomy::get_before_kind( $kind ); 185 $content = ''; 186 $kind_post = new Kind_Post( $post ); 187 188 if ( ! empty( $title ) ) { 189 return $title; 190 } 191 192 if ( in_array( $kind, array( 'audio', 'video', 'photo' ), true ) ) { 193 switch ( $kind ) { 194 case 'photo': 195 $photos = $kind_post->get_photo(); 196 $before = wp_get_attachment_image( 197 $photos[0], 198 $args['photo_size'], 199 false, 200 array( 201 'class' => 'kind-photo-thumbnail', 202 ) 203 ); 204 } 205 } elseif ( ! in_array( $kind, array( 'note', 'article' ), true ) ) { 206 $cite = $kind_post->get_cite( 'name' ); 207 if ( false === $cite ) { 208 $content = Kind_View::get_post_type_string( $kind_post->get_cite( 'url' ) ); 219 } 220 221 if ( ! function_exists( 'kind_get_the_link' ) ) { 222 /** 223 * Returns a marked up link to a post, followed by its date. 224 * 225 * @since 3.5.9 226 * 227 * @param WP_Post|int|null $post Optional. Post to display. Default the current post. 228 * @param string|array|null $cls Optional. Classes to add to the link. Default null. 229 * @param string|array|null $date_cls Optional. Classes to add to the date. Default null. 230 * @return string Marked up link to a post. 231 */ 232 function kind_get_the_link( $post = null, $cls = null, $date_cls = null ) { 233 $post = get_post( $post ); 234 $kind = get_post_kind_slug( $post ); 235 236 if ( is_array( $cls ) ) { 237 $cls = implode( ' ', $cls ); 238 } 239 240 if ( is_array( $date_cls ) ) { 241 $date_cls = implode( ' ', $date_cls ); 242 } 243 244 $time_string = '<time class="%3$s" datetime="%1$s">%2$s</time>'; 245 $time_string = sprintf( 246 $time_string, 247 esc_attr( get_the_date( DATE_W3C, $post ) ), 248 esc_html( get_the_date( '', $post ) ), 249 esc_attr( $date_cls ) 250 ); 251 return sprintf( '<a class="%4$s" href="%2$s">%1$s</a> - %3$s', kind_get_the_title( $post, $kind ), esc_url( get_the_permalink( $post ) ), $time_string, esc_attr( $cls ) ); 252 } 253 } 254 255 if ( ! function_exists( 'kind_get_the_title' ) ) { 256 /** 257 * Returns a post's title, generating one if the post has none. 258 * 259 * Most post kinds do not have an explicit title. 260 * 261 * @since 3.5.9 262 * 263 * @param WP_Post|int|null $post Optional. Post to display. Default the current post. 264 * @param array $args { 265 * Optional. Arguments. 266 * 267 * @type int[]|string $photo_size Image size for a photo post's thumbnail. Default array( 32, 32 ). 268 * } 269 * @return string The title, or the kind's icon or name and generated text. 270 */ 271 function kind_get_the_title( $post = null, $args = array() ) { 272 $defaults = array( 273 'photo_size' => array( 32, 32 ), 274 ); 275 276 $args = wp_parse_args( $args, $defaults ); 277 278 $post = get_post( $post ); 279 $kind = get_post_kind_slug( $post ); 280 $title = get_the_title( $post ); 281 $before = Kind_Taxonomy::get_before_kind( $kind ); 282 $content = ''; 283 $kind_post = new Kind_Post( $post ); 284 285 if ( ! empty( $title ) ) { 286 return $title; 287 } 288 289 if ( in_array( $kind, array( 'audio', 'video', 'photo' ), true ) ) { 290 switch ( $kind ) { 291 case 'photo': 292 $photos = $kind_post->get_photo(); 293 $before = wp_get_attachment_image( 294 $photos[0], 295 $args['photo_size'], 296 false, 297 array( 298 'class' => 'kind-photo-thumbnail', 299 ) 300 ); 301 } 302 } elseif ( ! in_array( $kind, array( 'note', 'article' ), true ) ) { 303 $cite = $kind_post->get_cite( 'name' ); 304 if ( false === $cite ) { 305 $content = Kind_View::get_post_type_string( $kind_post->get_cite( 'url' ) ); 306 } else { 307 $content = $cite; 308 } 209 309 } else { 210 $content = $cite; 211 } 212 } else { 213 $content = $post->post_excerpt; 214 // If no excerpt use content 215 if ( ! $content ) { 216 $content = $post->post_content; 217 } 218 // If no content use date 219 if ( $content ) { 220 $content = mb_strimwidth( wp_strip_all_tags( $content ), 0, 40, '...' ); 221 } 222 } 223 if ( is_array( $content ) ) { 224 $content = wp_json_encode( $content ); 225 } 226 227 $content = apply_filters( 'kind_get_the_title_content', $content, $post ); 228 $before = apply_filters( 'kind_get_the_title_before', $before, $post ); 229 230 return trim( sprintf( '%1$s %2$s', $before, $content ) ); 231 } 310 $content = $post->post_excerpt; 311 // If no excerpt use content. 312 if ( ! $content ) { 313 $content = $post->post_content; 314 } 315 // If no content use date. 316 if ( $content ) { 317 $content = mb_strimwidth( wp_strip_all_tags( $content ), 0, 40, '...' ); 318 } 319 } 320 if ( is_array( $content ) ) { 321 $content = wp_json_encode( $content ); 322 } 323 // The content is text (a cited name or an excerpt), often from another site. 324 $content = esc_html( wp_strip_all_tags( (string) $content ) ); 325 326 /** 327 * Filters the text of a generated post title. 328 * 329 * The text is a cited name, an excerpt or similar, already escaped. A callback 330 * that adds markup must escape what it adds. 331 * 332 * @since 3.5.9 333 * 334 * @param string $content Escaped title text. 335 * @param WP_Post $post The post. 336 */ 337 $content = apply_filters( 'kind_get_the_title_content', $content, $post ); 338 /** 339 * Filters the markup shown before a generated post title. 340 * 341 * The markup is output as-is, so it must be escaped. 342 * 343 * @since 3.5.9 344 * 345 * @param string $before The kind's icon or name, from Kind_Taxonomy::get_before_kind(). 346 * @param WP_Post $post The post. 347 */ 348 $before = apply_filters( 'kind_get_the_title_before', $before, $post ); 349 350 return trim( sprintf( '%1$s %2$s', $before, $content ) ); 351 } 352 } -
indieweb-post-kinds/trunk/includes/register-kinds.php
r3001401 r3738687 1 1 <?php 2 /* Registers built-in Post Kinds 2 /** 3 * Registers the built-in kinds. 4 * 5 * The arguments each kind takes are documented in 6 * Kind_Taxonomy::register_post_kind(). 7 * 8 * @package Post_Kinds 9 * @since 3.3.4 3 10 */ 4 11 12 defined( 'ABSPATH' ) || exit; 13 5 14 register_post_kind( 6 15 'article', 7 16 array( 8 'singular_name' => __( 'Article', 'indieweb-post-kinds' ), // Name for one instance of the kind9 'name' => __( 'Articles', 'indieweb-post-kinds' ), // General name for the kind plural10 'verb' => ' ', // The string for the verb or action (liked this)11 'property' => '', // microformats 2 property17 'singular_name' => __( 'Article', 'indieweb-post-kinds' ), 18 'name' => __( 'Articles', 'indieweb-post-kinds' ), 19 'verb' => ' ', 20 'property' => '', 12 21 'properties' => array(), 13 'format' => '', // Post Format that maps to this22 'format' => '', 14 23 'description' => __( 'traditional long form content: a post with an explicit title and body', 'indieweb-post-kinds' ), 15 24 'description_url' => 'http://indieweb.org/article', 16 25 'shortlink' => 'b', 17 'title' => true, // Should this kind have an explicit title18 'show' => true, // Show in Settings26 'title' => true, 27 'show' => true, 19 28 ) 20 29 ); … … 23 32 'note', 24 33 array( 25 'singular_name' => __( 'Note', 'indieweb-post-kinds' ), // Name for one instance of the kind26 'name' => __( 'Notes', 'indieweb-post-kinds' ), // General name for the kind plural27 'verb' => ' ', // The string for the verb or action (liked this)28 'property' => '', // microformats 2 property34 'singular_name' => __( 'Note', 'indieweb-post-kinds' ), 35 'name' => __( 'Notes', 'indieweb-post-kinds' ), 36 'verb' => ' ', 37 'property' => '', 29 38 'properties' => array(), 30 'format' => 'aside', // Post Format that maps to this39 'format' => 'aside', 31 40 'description' => __( 'short content: a post or status update with just plain content and typically without a title', 'indieweb-post-kinds' ), 32 41 'description_url' => 'http://indieweb.org/note', 33 42 'shortlink' => 't', 34 'title' => false, // Should this kind have an explicit title35 'show' => false, // Show in Settings43 'title' => false, 44 'show' => false, 36 45 ) 37 46 ); … … 40 49 'reply', 41 50 array( 42 'singular_name' => __( 'Reply', 'indieweb-post-kinds' ), // Name for one instance of the kind43 'name' => __( 'Replies', 'indieweb-post-kinds' ), // General name for the kind plural44 'verb' => __( 'Replied to', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)45 'property' => 'in-reply-to', // microformats 2 property51 'singular_name' => __( 'Reply', 'indieweb-post-kinds' ), 52 'name' => __( 'Replies', 'indieweb-post-kinds' ), 53 'verb' => __( 'Replied to', 'indieweb-post-kinds' ), 54 'property' => 'in-reply-to', 46 55 'properties' => array( 47 56 'in-reply-to' => array( … … 97 106 ), 98 107 ), 99 'format' => 'link', // Post Format that maps to this108 'format' => 'link', 100 109 'description' => __( 'a reply to content typically on another site', 'indieweb-post-kinds' ), 101 110 'description_url' => 'http://indieweb.org/reply', 102 111 'shortlink' => 't', 103 'title' => false, // Should this kind have an explicit title104 'show' => true, // Show in Settings112 'title' => false, 113 'show' => true, 105 114 ) 106 115 ); … … 109 118 'repost', 110 119 array( 111 'singular_name' => __( 'Repost', 'indieweb-post-kinds' ), // Name for one instance of the kind112 'name' => __( 'Reposts', 'indieweb-post-kinds' ), // General name for the kind plural113 'verb' => __( 'Reposted', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)114 'property' => 'repost-of', // microformats 2 property120 'singular_name' => __( 'Repost', 'indieweb-post-kinds' ), 121 'name' => __( 'Reposts', 'indieweb-post-kinds' ), 122 'verb' => __( 'Reposted', 'indieweb-post-kinds' ), 123 'property' => 'repost-of', 115 124 'properties' => array( 116 125 'repost-of' => array( … … 166 175 ), 167 176 ), 168 'format' => '', // Post Format that maps to this177 'format' => '', 169 178 'description' => __( 'a complete reposting of content from another site', 'indieweb-post-kinds' ), 170 179 'description_url' => 'http://indieweb.org/repost', 171 180 'shortlink' => 'b', 172 'title' => true, // Should this kind have an explicit title173 'show' => true, // Show in Settings181 'title' => true, 182 'show' => true, 174 183 ) 175 184 ); … … 178 187 'like', 179 188 array( 180 'singular_name' => __( 'Like', 'indieweb-post-kinds' ), // Name for one instance of the kind181 'name' => __( 'Likes', 'indieweb-post-kinds' ), // General name for the kind plural182 'verb' => __( 'Liked', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)183 'property' => 'like-of', // microformats 2 property189 'singular_name' => __( 'Like', 'indieweb-post-kinds' ), 190 'name' => __( 'Likes', 'indieweb-post-kinds' ), 191 'verb' => __( 'Liked', 'indieweb-post-kinds' ), 192 'property' => 'like-of', 184 193 'properties' => array( 185 194 'like-of' => array( … … 235 244 ), 236 245 ), 237 'format' => 'link', // Post Format that maps to this246 'format' => 'link', 238 247 'description' => __( 'a way to pay compliments to the original post/poster of external content', 'indieweb-post-kinds' ), 239 248 'description_url' => 'http://indieweb.org/like', 240 249 'shortlink' => 'f', 241 'title' => false, // Should this kind have an explicit title242 'show' => true, // Show in Settings250 'title' => false, 251 'show' => true, 243 252 ) 244 253 ); … … 247 256 'favorite', 248 257 array( 249 'singular_name' => __( 'Favorite', 'indieweb-post-kinds' ), // Name for one instance of the kind250 'name' => __( 'Favorites', 'indieweb-post-kinds' ), // General name for the kind plural251 'verb' => __( 'Favorited', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)252 'property' => 'favorite-of', // microformats 2 property258 'singular_name' => __( 'Favorite', 'indieweb-post-kinds' ), 259 'name' => __( 'Favorites', 'indieweb-post-kinds' ), 260 'verb' => __( 'Favorited', 'indieweb-post-kinds' ), 261 'property' => 'favorite-of', 253 262 'properties' => array( 254 263 'favorite-of' => array( … … 304 313 ), 305 314 ), 306 'format' => 'link', // Post Format that maps to this315 'format' => 'link', 307 316 'description' => __( 'special to the author', 'indieweb-post-kinds' ), 308 317 'description_url' => 'http://indieweb.org/favorite', 309 318 'shortlink' => 'f', 310 'title' => false, // Should this kind have an explicit title311 'show' => true, // Show in Settings319 'title' => false, 320 'show' => true, 312 321 ) 313 322 ); … … 316 325 'bookmark', 317 326 array( 318 'singular_name' => __( 'Bookmark', 'indieweb-post-kinds' ), // Name for one instance of the kind319 'name' => __( 'Bookmarks', 'indieweb-post-kinds' ), // General name for the kind plural320 'verb' => __( 'Bookmarked', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)321 'property' => 'bookmark-of', // microformats 2 property327 'singular_name' => __( 'Bookmark', 'indieweb-post-kinds' ), 328 'name' => __( 'Bookmarks', 'indieweb-post-kinds' ), 329 'verb' => __( 'Bookmarked', 'indieweb-post-kinds' ), 330 'property' => 'bookmark-of', 322 331 'properties' => array( 323 332 'bookmark-of' => array( … … 373 382 ), 374 383 ), 375 'format' => 'link', // Post Format that maps to this384 'format' => 'link', 376 385 'description' => __( 'storing a link/bookmark for personal use or sharing with others', 'indieweb-post-kinds' ), 377 386 'description_url' => 'http://indieweb.org/bookmark', 378 387 'shortlink' => 'h', 379 'title' => false, // Should this kind have an explicit title380 'show' => true, // Show in Settings388 'title' => false, 389 'show' => true, 381 390 ) 382 391 ); … … 385 394 'photo', 386 395 array( 387 'singular_name' => __( 'Photo', 'indieweb-post-kinds' ), // Name for one instance of the kind388 'name' => __( 'Photos', 'indieweb-post-kinds' ), // General name for the kind plural389 'verb' => ' ', // The string for the verb or action (liked this)390 'property' => 'photo', // microformats 2 property391 'format' => 'image', // Post Format that maps to this396 'singular_name' => __( 'Photo', 'indieweb-post-kinds' ), 397 'name' => __( 'Photos', 'indieweb-post-kinds' ), 398 'verb' => ' ', 399 'property' => 'photo', 400 'format' => 'image', 392 401 'description' => __( 'a post with an embedded image/photo as its primary focus', 'indieweb-post-kinds' ), 393 402 'description_url' => 'http://indieweb.org/photo', 394 403 'shortlink' => 'p', 395 'title' => false, // Should this kind have an explicit title396 'show' => true, // Show in Settings404 'title' => false, 405 'show' => true, 397 406 ) 398 407 ); … … 401 410 'video', 402 411 array( 403 'singular_name' => __( 'Video', 'indieweb-post-kinds' ), // Name for one instance of the kind404 'name' => __( 'Videos', 'indieweb-post-kinds' ), // General name for the kind plural405 'verb' => ' ', // The string for the verb or action (liked this)406 'property' => 'video', // microformats 2 property407 'format' => 'video', // Post Format that maps to this412 'singular_name' => __( 'Video', 'indieweb-post-kinds' ), 413 'name' => __( 'Videos', 'indieweb-post-kinds' ), 414 'verb' => ' ', 415 'property' => 'video', 416 'format' => 'video', 408 417 'description' => __( 'a post with an embedded video as its primary focus', 'indieweb-post-kinds' ), 409 418 'description_url' => 'http://indieweb.org/video', 410 419 'shortlink' => 'a', 411 'title' => false, // Should this kind have an explicit title412 'show' => true, // Show in Settings420 'title' => false, 421 'show' => true, 413 422 ) 414 423 ); … … 417 426 'audio', 418 427 array( 419 'singular_name' => __( 'Audio', 'indieweb-post-kinds' ), // Name for one instance of the kind420 'name' => __( 'Audios', 'indieweb-post-kinds' ), // General name for the kind plural421 'verb' => ' ', // The string for the verb or action (liked this)422 'property' => 'audio', // microformats 2 property423 'format' => 'audio', // Post Format that maps to this428 'singular_name' => __( 'Audio', 'indieweb-post-kinds' ), 429 'name' => __( 'Audios', 'indieweb-post-kinds' ), 430 'verb' => ' ', 431 'property' => 'audio', 432 'format' => 'audio', 424 433 'description' => __( 'a post with an embedded audio file as its primary focus', 'indieweb-post-kinds' ), 425 434 'description_url' => 'http://indieweb.org/audio', 426 435 'shortlink' => 'a', 427 'title' => false, // Should this kind have an explicit title428 'show' => true, // Show in Settings436 'title' => false, 437 'show' => true, 429 438 ) 430 439 ); … … 433 442 'tag', 434 443 array( 435 'singular_name' => __( 'Tag', 'indieweb-post-kinds' ), // Name for one instance of the kind436 'name' => __( 'Tags', 'indieweb-post-kinds' ), // General name for the kind plural437 'verb' => __( 'Tagged', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)438 'property' => 'tag-of', // microformats 2 property439 'format' => 'link', // Post Format that maps to this444 'singular_name' => __( 'Tag', 'indieweb-post-kinds' ), 445 'name' => __( 'Tags', 'indieweb-post-kinds' ), 446 'verb' => __( 'Tagged', 'indieweb-post-kinds' ), 447 'property' => 'tag-of', 448 'format' => 'link', 440 449 'description' => __( 'allows you to tag a post as being of a specific category or tag, or for person tagging', 'indieweb-post-kinds' ), 441 450 'description_url' => 'http://indieweb.org/tag', 442 451 'shortlink' => 'd', 443 'title' => false, // Should this kind have an explicit title444 'show' => false, // Show in Settings452 'title' => false, 453 'show' => false, 445 454 ) 446 455 ); … … 449 458 'rsvp', 450 459 array( 451 'singular_name' => __( 'RSVP', 'indieweb-post-kinds' ), // Name for one instance of the kind452 'name' => __( 'RSVPs', 'indieweb-post-kinds' ), // General name for the kind plural453 'verb' => __( 'RSVPed', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)454 'property' => 'in-reply-to', // microformats 2 property460 'singular_name' => __( 'RSVP', 'indieweb-post-kinds' ), 461 'name' => __( 'RSVPs', 'indieweb-post-kinds' ), 462 'verb' => __( 'RSVPed', 'indieweb-post-kinds' ), 463 'property' => 'in-reply-to', 455 464 'properties' => array( 456 465 'rsvp' => array( … … 509 518 ), 510 519 ), 511 'format' => 'link', // Post Format that maps to this520 'format' => 'link', 512 521 'description' => __( 'a specific type of reply regarding attendance of an event', 'indieweb-post-kinds' ), 513 522 'description_url' => 'http://indieweb.org/rsvp', 514 523 'shortlink' => 't', 515 'title' => false, // Should this kind have an explicit title516 'show' => true, // Show in Settings524 'title' => false, 525 'show' => true, 517 526 ) 518 527 ); … … 521 530 'listen', 522 531 array( 523 'singular_name' => __( 'Listen', 'indieweb-post-kinds' ), // Name for one instance of the kind524 'name' => __( 'Listens', 'indieweb-post-kinds' ), // General name for the kind plural525 'verb' => __( 'Listened', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)526 'property' => 'listen-of', // microformats 2 property532 'singular_name' => __( 'Listen', 'indieweb-post-kinds' ), 533 'name' => __( 'Listens', 'indieweb-post-kinds' ), 534 'verb' => __( 'Listened', 'indieweb-post-kinds' ), 535 'property' => 'listen-of', 527 536 'properties' => array( 528 537 'listen-of' => array( … … 574 583 ), 575 584 ), 576 'format' => 'audio', // Post Format that maps to this585 'format' => 'audio', 577 586 'description' => __( 'listening to audio; sometimes called a scrobble', 'indieweb-post-kinds' ), 578 587 'description_url' => 'http://indieweb.org/listen', 579 588 'shortlink' => 'x', 580 'title' => false, // Should this kind have an explicit title581 'show' => true, // Show in Settings589 'title' => false, 590 'show' => true, 582 591 ) 583 592 ); … … 586 595 'watch', 587 596 array( 588 'singular_name' => __( 'Watch', 'indieweb-post-kinds' ), // Name for one instance of the kind589 'name' => __( 'Watches', 'indieweb-post-kinds' ), // General name for the kind plural590 'verb' => __( 'Watched', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)591 'property' => 'watch-of', // microformats 2 property597 'singular_name' => __( 'Watch', 'indieweb-post-kinds' ), 598 'name' => __( 'Watches', 'indieweb-post-kinds' ), 599 'verb' => __( 'Watched', 'indieweb-post-kinds' ), 600 'property' => 'watch-of', 592 601 'properties' => array( 593 602 'watch-of' => array( … … 639 648 ), 640 649 ), 641 'format' => 'video', // Post Format that maps to this650 'format' => 'video', 642 651 'description' => __( 'watching a movie, television show, online video, play or other visual-based event', 'indieweb-post-kinds' ), 643 652 'description_url' => 'http://indieweb.org/watch', 644 653 'shortlink' => 'x', 645 'title' => false, // Should this kind have an explicit title646 'show' => true, // Show in Settings654 'title' => false, 655 'show' => true, 647 656 ) 648 657 ); … … 651 660 'checkin', 652 661 array( 653 'singular_name' => __( 'Checkin', 'indieweb-post-kinds' ), // Name for one instance of the kind654 'name' => __( 'Checkins', 'indieweb-post-kinds' ), // General name for the kind plural655 'verb' => __( 'Checked into', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)656 'property' => 'checkin', // microformats 2 property662 'singular_name' => __( 'Checkin', 'indieweb-post-kinds' ), 663 'name' => __( 'Checkins', 'indieweb-post-kinds' ), 664 'verb' => __( 'Checked into', 'indieweb-post-kinds' ), 665 'property' => 'checkin', 657 666 'properties' => array( 658 667 'checkin' => array( … … 707 716 ), 708 717 ), 709 'format' => 'status', // Post Format that maps to this718 'format' => 'status', 710 719 'description' => __( 'identifying you are at a particular geographic location', 'indieweb-post-kinds' ), 711 720 'description_url' => 'http://indieweb.org/checkin', 712 721 'shortlink' => 'g', 713 'title' => false, // Should this kind have an explicit title714 'show' => true, // Show in Settings722 'title' => false, 723 'show' => true, 715 724 ) 716 725 ); … … 719 728 'wish', 720 729 array( 721 'singular_name' => __( 'Wish', 'indieweb-post-kinds' ), // Name for one instance of the kind722 'name' => __( 'Wishes', 'indieweb-post-kinds' ), // General name for the kind plural723 'verb' => __( 'Wished', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)724 'property' => 'wish-of', // microformats 2 property725 'format' => 'link', // Post Format that maps to this730 'singular_name' => __( 'Wish', 'indieweb-post-kinds' ), 731 'name' => __( 'Wishes', 'indieweb-post-kinds' ), 732 'verb' => __( 'Wished', 'indieweb-post-kinds' ), 733 'property' => 'wish-of', 734 'format' => 'link', 726 735 'description' => __( 'a post indicating a desire/wish. The archive of which would be a wishlist, such as a gift registry or similar', 'indieweb-post-kinds' ), 727 736 'description_url' => '', 728 737 'shortlink' => 'f', 729 'title' => false, // Should this kind have an explicit title730 'show' => false, // Show in Settings738 'title' => false, 739 'show' => false, 731 740 ) 732 741 ); … … 735 744 'play', 736 745 array( 737 'singular_name' => __( 'Play', 'indieweb-post-kinds' ), // Name for one instance of the kind738 'name' => __( 'Playing', 'indieweb-post-kinds' ), // General name for the kind plural739 'verb' => __( 'Played', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)740 'property' => 'play-of', // microformats 2 property741 'format' => 'status', // Post Format that maps to this746 'singular_name' => __( 'Play', 'indieweb-post-kinds' ), 747 'name' => __( 'Playing', 'indieweb-post-kinds' ), 748 'verb' => __( 'Played', 'indieweb-post-kinds' ), 749 'property' => 'play-of', 750 'format' => 'status', 742 751 'description' => __( 'playing a game', 'indieweb-post-kinds' ), 743 752 'description_url' => 'https://indieweb.org/game_play', 744 753 'shortlink' => 'x', 745 'title' => false, // Should this kind have an explicit title746 'show' => true, // Show in Settings754 'title' => false, 755 'show' => true, 747 756 ) 748 757 ); … … 751 760 'weather', 752 761 array( 753 'singular_name' => __( 'Weather', 'indieweb-post-kinds' ), // Name for one instance of the kind754 'name' => __( 'Weather', 'indieweb-post-kinds' ), // General name for the kind plural755 'verb' => ' ', // The string for the verb or action (liked this)756 'property' => 'weather', // microformats 2 property757 'format' => 'status', // Post Format that maps to this762 'singular_name' => __( 'Weather', 'indieweb-post-kinds' ), 763 'name' => __( 'Weather', 'indieweb-post-kinds' ), 764 'verb' => ' ', 765 'property' => 'weather', 766 'format' => 'status', 758 767 'description' => __( 'current weather conditions', 'indieweb-post-kinds' ), 759 768 'description_url' => 'https://indieweb.org/weather', 760 769 'shortlink' => 'u', 761 'title' => false, // Should this kind have an explicit title762 'show' => false, // Show in Settings770 'title' => false, 771 'show' => false, 763 772 ) 764 773 ); … … 767 776 'exercise', 768 777 array( 769 'singular_name' => __( 'Exercise', 'indieweb-post-kinds' ), // Name for one instance of the kind770 'name' => __( 'Exercise', 'indieweb-post-kinds' ), // General name for the kind plural771 'verb' => __( 'Exercised', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)772 'property' => 'exercise', // microformats 2 property773 'format' => 'status', // Post Format that maps to this778 'singular_name' => __( 'Exercise', 'indieweb-post-kinds' ), 779 'name' => __( 'Exercise', 'indieweb-post-kinds' ), 780 'verb' => __( 'Exercised', 'indieweb-post-kinds' ), 781 'property' => 'exercise', 782 'format' => 'status', 774 783 'description' => __( 'some form of physical activity or workout (examples: walk, run, cycle, hike, yoga, etc.)', 'indieweb-post-kinds' ), 775 784 'description_url' => 'https://indieweb.org/exercise', 776 785 'shortlink' => 'm', 777 'title' => false, // Should this kind have an explicit title778 'show' => false, // Show in Settings786 'title' => false, 787 'show' => false, 779 788 ) 780 789 ); … … 783 792 'trip', 784 793 array( 785 'singular_name' => __( 'Trip', 'indieweb-post-kinds' ), // Name for one instance of the kind786 'name' => __( 'Trips', 'indieweb-post-kinds' ), // General name for the kind plural787 'verb' => __( 'Travelled', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)788 'property' => 'trip', // microformats 2 property789 'format' => '', // Post Format that maps to this794 'singular_name' => __( 'Trip', 'indieweb-post-kinds' ), 795 'name' => __( 'Trips', 'indieweb-post-kinds' ), 796 'verb' => __( 'Travelled', 'indieweb-post-kinds' ), 797 'property' => 'trip', 798 'format' => '', 790 799 'description' => __( 'represents a geographic journey', 'indieweb-post-kinds' ), 791 800 'description_url' => 'https://indieweb.org/trip', 792 801 'shortlink' => 'e', 793 'title' => false, // Should this kind have an explicit title794 'show' => false, // Show in Settings802 'title' => false, 803 'show' => false, 795 804 ) 796 805 ); … … 799 808 'itinerary', 800 809 array( 801 'singular_name' => __( 'Itinerary', 'indieweb-post-kinds' ), // Name for one instance of the kind802 'name' => __( 'Itineraries', 'indieweb-post-kinds' ), // General name for the kind plural803 'verb' => __( 'Travelled', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)804 'property' => 'itinerary', // microformats 2 property805 'format' => '', // Post Format that maps to this810 'singular_name' => __( 'Itinerary', 'indieweb-post-kinds' ), 811 'name' => __( 'Itineraries', 'indieweb-post-kinds' ), 812 'verb' => __( 'Travelled', 'indieweb-post-kinds' ), 813 'property' => 'itinerary', 814 'format' => '', 806 815 'description' => __( 'parts of a scheduled trip including transit by car, plane, train, etc.', 'indieweb-post-kinds' ), 807 816 'description_url' => 'https://indieweb.org/trip', 808 817 'shortlink' => 'e', 809 'title' => false, // Should this kind have an explicit title810 'show' => false, // Show in Settings818 'title' => false, 819 'show' => false, 811 820 ) 812 821 ); … … 816 825 'eat', 817 826 array( 818 'singular_name' => __( 'Eat', 'indieweb-post-kinds' ), // Name for one instance of the kind819 'name' => __( 'Eat', 'indieweb-post-kinds' ), // General name for the kind plural820 'verb' => __( 'Ate', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)821 'property' => 'ate', // microformats 2 property822 'format' => 'status', // Post Format that maps to this827 'singular_name' => __( 'Eat', 'indieweb-post-kinds' ), 828 'name' => __( 'Eat', 'indieweb-post-kinds' ), 829 'verb' => __( 'Ate', 'indieweb-post-kinds' ), 830 'property' => 'ate', 831 'format' => 'status', 823 832 'description' => __( 'what you are eating, perhaps for a food diary', 'indieweb-post-kinds' ), 824 833 'description_url' => 'https://indieweb.org/food', 825 834 'shortlink' => 'u', 826 'title' => false, // Should this kind have an explicit title827 'show' => true, // Show in Settings835 'title' => false, 836 'show' => true, 828 837 ) 829 838 ); … … 833 842 'drink', 834 843 array( 835 'singular_name' => __( 'Drink', 'indieweb-post-kinds' ), // Name for one instance of the kind836 'name' => __( 'Drinks', 'indieweb-post-kinds' ), // General name for the kind plural837 'verb' => __( 'Drank', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)838 'property' => 'drank', // microformats 2 property839 'format' => 'status', // Post Format that maps to this844 'singular_name' => __( 'Drink', 'indieweb-post-kinds' ), 845 'name' => __( 'Drinks', 'indieweb-post-kinds' ), 846 'verb' => __( 'Drank', 'indieweb-post-kinds' ), 847 'property' => 'drank', 848 'format' => 'status', 840 849 'description' => __( 'what you are drinking, perhaps for a food dairy', 'indieweb-post-kinds' ), 841 850 'description_url' => 'https://indieweb.org/food', 842 851 'shortlink' => 'u', 843 'title' => false, // Should this kind have an explicit title844 'show' => true, // Show in Settings852 'title' => false, 853 'show' => true, 845 854 ) 846 855 ); … … 850 859 'follow', 851 860 array( 852 'singular_name' => __( 'Follow', 'indieweb-post-kinds' ), // Name for one instance of the kind853 'name' => __( 'Follows', 'indieweb-post-kinds' ), // General name for the kind plural854 'verb' => __( 'Followed', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)855 'property' => 'follow-of', // microformats 2 property856 'format' => '', // Post Format that maps to this861 'singular_name' => __( 'Follow', 'indieweb-post-kinds' ), 862 'name' => __( 'Follows', 'indieweb-post-kinds' ), 863 'verb' => __( 'Followed', 'indieweb-post-kinds' ), 864 'property' => 'follow-of', 865 'format' => '', 857 866 'description' => __( 'indicating you are now following or subscribing to another person`s activities online', 'indieweb-post-kinds' ), 858 867 'description_url' => 'https://indieweb.org/follow', 859 868 'shortlink' => 'f', 860 'title' => false, // Should this kind have an explicit title861 'show' => false, // Show in Settings869 'title' => false, 870 'show' => false, 862 871 ) 863 872 ); … … 867 876 'jam', 868 877 array( 869 'singular_name' => __( 'Jam', 'indieweb-post-kinds' ), // Name for one instance of the kind870 'name' => __( 'Jams', 'indieweb-post-kinds' ), // General name for the kind plural871 'verb' => __( 'Listened to', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)872 'property' => 'jam-of', // microformats 2 property873 'format' => 'audio', // Post Format that maps to this878 'singular_name' => __( 'Jam', 'indieweb-post-kinds' ), 879 'name' => __( 'Jams', 'indieweb-post-kinds' ), 880 'verb' => __( 'Listened to', 'indieweb-post-kinds' ), 881 'property' => 'jam-of', 882 'format' => 'audio', 874 883 'description' => __( 'a particularly personally meaningful song (a listen with added emphasis)', 'indieweb-post-kinds' ), 875 884 'description_url' => 'https://indieweb.org/jam', 876 885 'shortlink' => 'f', 877 'title' => false, // Should this kind have an explicit title878 'show' => true, // Show in Settings886 'title' => false, 887 'show' => true, 879 888 ) 880 889 ); … … 884 893 'read', 885 894 array( 886 'singular_name' => __( 'Read', 'indieweb-post-kinds' ), // Name for one instance of the kind887 'name' => __( 'Reads', 'indieweb-post-kinds' ), // General name for the kind plural888 'verb' => __( 'Read', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)889 'property' => 'read-of', // microformats 2 property890 'format' => 'status', // Post Format that maps to this895 'singular_name' => __( 'Read', 'indieweb-post-kinds' ), 896 'name' => __( 'Reads', 'indieweb-post-kinds' ), 897 'verb' => __( 'Read', 'indieweb-post-kinds' ), 898 'property' => 'read-of', 899 'format' => 'status', 891 900 'description' => __( 'reading a book, magazine, newspaper, other physical document, or online post', 'indieweb-post-kinds' ), 892 901 'description_url' => 'https://indieweb.org/read', 893 902 'shortlink' => 'x', 894 'title' => false, // Should this kind have an explicit title895 'show' => true, // Show in Settings903 'title' => false, 904 'show' => true, 896 905 ) 897 906 ); … … 901 910 'quote', 902 911 array( 903 'singular_name' => __( 'Quote', 'indieweb-post-kinds' ), // Name for one instance of the kind904 'name' => __( 'Quotes', 'indieweb-post-kinds' ), // General name for the kind plural905 'verb' => __( 'Quoted', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)906 'property' => 'quotation-of', // microformats 2 property907 'format' => 'quote', // Post Format that maps to this912 'singular_name' => __( 'Quote', 'indieweb-post-kinds' ), 913 'name' => __( 'Quotes', 'indieweb-post-kinds' ), 914 'verb' => __( 'Quoted', 'indieweb-post-kinds' ), 915 'property' => 'quotation-of', 916 'format' => 'quote', 908 917 'description' => __( 'quoted content', 'indieweb-post-kinds' ), 909 918 'description_url' => 'https://indieweb.org/quote', 910 919 'shortlink' => 't', 911 'title' => false, // Should this kind have an explicit title912 'show' => true, // Show in Settings920 'title' => false, 921 'show' => true, 913 922 ) 914 923 ); … … 918 927 'mood', 919 928 array( 920 'singular_name' => __( 'Mood', 'indieweb-post-kinds' ), // Name for one instance of the kind921 'name' => __( 'Moods', 'indieweb-post-kinds' ), // General name for the kind plural922 'verb' => __( 'Felt', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)923 'property' => 'mood', // microformats 2 property924 'format' => 'status', // Post Format that maps to this929 'singular_name' => __( 'Mood', 'indieweb-post-kinds' ), 930 'name' => __( 'Moods', 'indieweb-post-kinds' ), 931 'verb' => __( 'Felt', 'indieweb-post-kinds' ), 932 'property' => 'mood', 933 'format' => 'status', 925 934 'description' => __( 'how you are feeling (example: happy, sad, indifferent, etc.)', 'indieweb-post-kinds' ), 926 935 'description_url' => 'https://indieweb.org/mood', 927 936 'shortlink' => 't', 928 'title' => false, // Should this kind have an explicit title929 'show' => false, // Show in Settings937 'title' => false, 938 'show' => false, 930 939 ) 931 940 ); … … 935 944 'recipe', 936 945 array( 937 'singular_name' => __( 'Recipe', 'indieweb-post-kinds' ), // Name for one instance of the kind938 'name' => __( 'Recipes', 'indieweb-post-kinds' ), // General name for the kind plural939 'verb' => __( 'Cooked', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)940 'property' => 'recipe', // microformats 2 property941 'format' => '', // Post Format that maps to this946 'singular_name' => __( 'Recipe', 'indieweb-post-kinds' ), 947 'name' => __( 'Recipes', 'indieweb-post-kinds' ), 948 'verb' => __( 'Cooked', 'indieweb-post-kinds' ), 949 'property' => 'recipe', 950 'format' => '', 942 951 'description' => __( 'list of ingredients and directions for making food or drink', 'indieweb-post-kinds' ), 943 952 'description_url' => 'https://indieweb.org/recipe', 944 953 'shortlink' => 'b', 945 'title' => true, // Should this kind have an explicit title946 'show' => false, // Show in Settings954 'title' => true, 955 'show' => false, 947 956 ) 948 957 ); … … 952 961 'issue', 953 962 array( 954 'singular_name' => __( 'Issue', 'indieweb-post-kinds' ), // Name for one instance of the kind955 'name' => __( 'Issues', 'indieweb-post-kinds' ), // General name for the kind plural956 'verb' => __( 'Filed an Issue', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)957 'property' => 'in-reply-to', // microformats 2 property958 'format' => '', // Post Format that maps to this963 'singular_name' => __( 'Issue', 'indieweb-post-kinds' ), 964 'name' => __( 'Issues', 'indieweb-post-kinds' ), 965 'verb' => __( 'Filed an Issue', 'indieweb-post-kinds' ), 966 'property' => 'in-reply-to', 967 'format' => '', 959 968 'description' => __( 'Issue is a special kind of article post that is a reply to typically some source code, though potentially anything at a source control repository.', 'indieweb-post-kinds' ), 960 969 'description_url' => 'https://indieweb.org/issue', 961 970 'shortlink' => 't', 962 'title' => true, // Should this kind have an explicit title963 'show' => true, // Show in Settings971 'title' => true, 972 'show' => true, 964 973 ) 965 974 ); … … 969 978 'question', 970 979 array( 971 'singular_name' => __( 'Question', 'indieweb-post-kinds' ), // Name for one instance of the kind972 'name' => __( 'Questions', 'indieweb-post-kinds' ), // General name for the kind plural973 'verb' => __( 'Asked a question', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)974 'property' => 'question', // microformats 2 property975 'format' => '', // Post Format that maps to this980 'singular_name' => __( 'Question', 'indieweb-post-kinds' ), 981 'name' => __( 'Questions', 'indieweb-post-kinds' ), 982 'verb' => __( 'Asked a question', 'indieweb-post-kinds' ), 983 'property' => 'question', 984 'format' => '', 976 985 'description' => __( 'Question is a post type for soliciting answer replies, which are then typically up/down voted by others and then displayed underneath the question post ordered by highest positive vote count rather than time ordered.', 'indieweb-post-kinds' ), 977 986 'description_url' => 'https://indieweb.org/question', 978 987 'shortlink' => 'q', 979 'title' => false, // Should this kind have an explicit title980 'show' => false, // Show in Settings988 'title' => false, 989 'show' => false, 981 990 ) 982 991 ); … … 986 995 'sleep', 987 996 array( 988 'singular_name' => __( 'Sleep', 'indieweb-post-kinds' ), // Name for one instance of the kind989 'name' => __( 'Sleeps', 'indieweb-post-kinds' ), // General name for the kind plural990 'verb' => __( 'Slept', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)991 'property' => 'sleep', // microformats 2 property992 'format' => '', // Post Format that maps to this997 'singular_name' => __( 'Sleep', 'indieweb-post-kinds' ), 998 'name' => __( 'Sleeps', 'indieweb-post-kinds' ), 999 'verb' => __( 'Slept', 'indieweb-post-kinds' ), 1000 'property' => 'sleep', 1001 'format' => '', 993 1002 'description' => __( 'Sleep is a passive metrics post type that indicates how much time (and often a graph of how deeply) a person has slept.', 'indieweb-post-kinds' ), 994 1003 'description_url' => 'https://indieweb.org/sleep', 995 1004 'shortlink' => 'm', 996 'title' => false, // Should this kind have an explicit title997 'show' => false, // Show in Settings1005 'title' => false, 1006 'show' => false, 998 1007 ) 999 1008 ); … … 1003 1012 'event', 1004 1013 array( 1005 'singular_name' => __( 'Event', 'indieweb-post-kinds' ), // Name for one instance of the kind1006 'name' => __( 'Events', 'indieweb-post-kinds' ), // General name for the kind plural1007 'verb' => __( 'Planned', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)1008 'property' => 'event', // microformats 2 property1009 'format' => '', // Post Format that maps to this1014 'singular_name' => __( 'Event', 'indieweb-post-kinds' ), 1015 'name' => __( 'Events', 'indieweb-post-kinds' ), 1016 'verb' => __( 'Planned', 'indieweb-post-kinds' ), 1017 'property' => 'event', 1018 'format' => '', 1010 1019 'description' => __( 'An event is a type of post that in addition to a post name (event title) has a start datetime (likely end datetime), and a location.', 'indieweb-post-kinds' ), 1011 1020 'description_url' => 'https://indieweb.org/event', 1012 1021 'shortlink' => 'e', 1013 'title' => true, // Should this kind have an explicit title1014 'show' => false, // Show in Settings1022 'title' => true, 1023 'show' => false, 1015 1024 ) 1016 1025 ); … … 1019 1028 'review', 1020 1029 array( 1021 'singular_name' => __( 'Review', 'indieweb-post-kinds' ), // Name for one instance of the kind1022 'name' => __( 'Reviews', 'indieweb-post-kinds' ), // General name for the kind plural1023 'verb' => __( 'Reviewed', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)1024 'property' => 'review-of', // microformats 2 property1025 'format' => '', // Post Format that maps to this1030 'singular_name' => __( 'Review', 'indieweb-post-kinds' ), 1031 'name' => __( 'Reviews', 'indieweb-post-kinds' ), 1032 'verb' => __( 'Reviewed', 'indieweb-post-kinds' ), 1033 'property' => 'review-of', 1034 'format' => '', 1026 1035 'description' => __( 'A review is a post evaluating a product or service, usually involving a written description, sometimes with summary numerical evaluations, also known as just a rating.', 'indieweb-post-kinds' ), 1027 1036 'description_url' => 'https://indieweb.org/review', 1028 1037 'shortlink' => 't', 1029 'title' => false, // Should this kind have an explicit title1030 'show' => true, // Show in Settings1038 'title' => false, 1039 'show' => true, 1031 1040 ) 1032 1041 ); … … 1036 1045 'acquisition', 1037 1046 array( 1038 'singular_name' => __( 'Acquisition', 'indieweb-post-kinds' ), // Name for one instance of the kind1039 'name' => __( 'Acquisitions', 'indieweb-post-kinds' ), // General name for the kind plural1040 'verb' => __( 'Acquired', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)1041 'property' => 'acquired-of', // microformats 2 property1042 'format' => 'status', // Post Format that maps to this1047 'singular_name' => __( 'Acquisition', 'indieweb-post-kinds' ), 1048 'name' => __( 'Acquisitions', 'indieweb-post-kinds' ), 1049 'verb' => __( 'Acquired', 'indieweb-post-kinds' ), 1050 'property' => 'acquired-of', 1051 'format' => 'status', 1043 1052 'description' => __( 'Purchases, gifts, found things, or objects donated', 'indieweb-post-kinds' ), 1044 1053 'description_url' => 'http://indieweb.org/acquisition', 1045 1054 'shortlink' => 'f', 1046 'title' => false, // Should this kind have an explicit title1047 'show' => false, // Show in Settings1055 'title' => false, 1056 'show' => false, 1048 1057 ) 1049 1058 ); … … 1052 1061 'craft', 1053 1062 array( 1054 'singular_name' => __( 'Craft', 'indieweb-post-kinds' ), // Name for one instance of the kind1055 'name' => __( 'Crafts', 'indieweb-post-kinds' ), // General name for the kind plural1056 'verb' => __( 'Crafted', 'indieweb-post-kinds' ), // The string for the verb or action (liked this)1057 'property' => 'craft-of', // microformats 2 property1058 'format' => 'status', // Post Format that maps to this1063 'singular_name' => __( 'Craft', 'indieweb-post-kinds' ), 1064 'name' => __( 'Crafts', 'indieweb-post-kinds' ), 1065 'verb' => __( 'Crafted', 'indieweb-post-kinds' ), 1066 'property' => 'craft-of', 1067 'format' => 'status', 1059 1068 'description' => __( 'Activities like knitting, crocheting, cross stitch, wood working, restoration, 3d printing...the activity of building something.', 'indieweb-post-kinds' ), 1060 1069 'description_url' => 'https://indieweb.org/crafts', 1061 1070 'shortlink' => 'x', 1062 'title' => false, // Should this kind have an explicit title1063 'show' => true, // Show in Settings1064 ) 1065 ); 1071 'title' => false, 1072 'show' => true, 1073 ) 1074 ); -
indieweb-post-kinds/trunk/includes/time-functions.php
r2973430 r3738687 1 1 <?php 2 2 /** 3 * Time Functions3 * Global date and time functions. 4 4 * 5 * Global Scoped Functions for Handling Time. 5 * The generic helpers moved to Kind_Time in 4.0.0; their global names remain 6 * here as deprecated wrappers. 6 7 * 8 * @package Post_Kinds 9 * @since 3.0.0 7 10 */ 8 11 12 defined( 'ABSPATH' ) || exit; 13 14 if ( ! function_exists( 'kind_safe_datetime' ) ) { 15 /** 16 * Creates a date from a value without throwing on invalid input. 17 * 18 * Dates come from post meta and Micropub requests, where a malformed value 19 * would otherwise throw an uncaught exception and break the page. 20 * 21 * @since 4.0.0 22 * 23 * @param DateTimeInterface|string $value Date, or a date string. 24 * @param DateTimeZone|null $timezone Optional. Timezone for strings without one. 25 * @param bool $immutable Optional. Whether to return a DateTimeImmutable. Default true. 26 * @return DateTimeImmutable|DateTime|false The date, or false if the value is not a valid date. 27 */ 28 function kind_safe_datetime( $value, $timezone = null, $immutable = true ) { 29 if ( $value instanceof DateTimeInterface ) { 30 if ( $immutable ) { 31 return $value instanceof DateTimeImmutable ? $value : DateTimeImmutable::createFromMutable( $value ); 32 } 33 return $value instanceof DateTime ? $value : DateTime::createFromImmutable( $value ); 34 } 35 if ( ! is_string( $value ) || '' === trim( $value ) ) { 36 return false; 37 } 38 try { 39 return $immutable ? new DateTimeImmutable( $value, $timezone ) : new DateTime( $value, $timezone ); 40 } catch ( Exception $e ) { 41 return false; 42 } 43 } 44 } 45 46 if ( ! function_exists( 'kind_safe_interval' ) ) { 47 /** 48 * Creates a duration from an ISO 8601 duration without throwing on invalid input. 49 * 50 * @since 4.0.0 51 * 52 * @param DateInterval|string $value Duration, or an ISO 8601 duration such as PT3M30S. 53 * @return DateInterval|false The duration, or false if the value is not a valid duration. 54 */ 55 function kind_safe_interval( $value ) { 56 if ( $value instanceof DateInterval ) { 57 return $value; 58 } 59 if ( ! is_string( $value ) || '' === trim( $value ) ) { 60 return false; 61 } 62 try { 63 return new DateInterval( $value ); 64 } catch ( Exception $e ) { 65 return false; 66 } 67 } 68 } 69 9 70 if ( ! function_exists( 'tz_seconds_to_offset' ) ) { 71 /** 72 * Converts a UTC offset in seconds to an offset string. 73 * 74 * @since 2.3.0 75 * 76 * @param int $seconds Offset in seconds. 77 * @return string Offset such as '-05:00'. 78 */ 10 79 function tz_seconds_to_offset( $seconds ) { 11 80 return ( $seconds < 0 ? '-' : '+' ) . sprintf( '%02d:%02d', abs( $seconds / 60 / 60 ), abs( $seconds / 60 ) % 60 ); … … 14 83 15 84 if ( ! function_exists( 'tz_offset_to_seconds' ) ) { 85 /** 86 * Converts an offset string to a UTC offset in seconds. 87 * 88 * @since 2.3.0 89 * 90 * @param string $offset Offset such as '-05:00' or '-0500'. 91 * @return int Offset in seconds, or 0 if the string is not an offset. 92 */ 16 93 function tz_offset_to_seconds( $offset ) { 17 94 if ( preg_match( '/([+-])(\d{2}):?(\d{2})/', $offset, $match ) ) { 18 $sign = ( '-' ? -1 : 1 === $match[1] );95 $sign = ( '-' === $match[1] ) ? -1 : 1; 19 96 return ( ( $match[2] * 60 * 60 ) + ( $match[3] * 60 ) ) * $sign; 20 97 } else { … … 25 102 26 103 if ( ! function_exists( 'tz_seconds_to_timezone' ) ) { 104 /** 105 * Converts a UTC offset in seconds to a time zone. 106 * 107 * @since 3.2.2 108 * 109 * @param int $seconds Offset in seconds. 110 * @return DateTimeZone|false The time zone, or false if it cannot be created. 111 */ 27 112 function tz_seconds_to_timezone( $seconds ) { 28 if ( version_compare( phpversion(), '5.5.10', '<' ) ) {29 return timezone_name_from_abbr( '', $seconds, 0 );30 }31 113 if ( 0 !== $seconds ) { 32 114 $tz = timezone_open( tz_seconds_to_offset( $seconds ) ); … … 40 122 41 123 if ( ! function_exists( 'tz_timezone_to_seconds' ) ) { 124 /** 125 * Returns a time zone's UTC offset in seconds. 126 * 127 * @since 3.2.2 128 * 129 * @param string $timezone Time zone name or offset string. 130 * @return int|false Offset in seconds, or false if the time zone is not valid. 131 */ 42 132 function tz_timezone_to_seconds( $timezone ) { 43 133 $tz = timezone_open( $timezone ); … … 51 141 if ( ! function_exists( 'get_gmt_offsets' ) ) { 52 142 143 /** 144 * Returns the UTC offsets currently in use by any time zone, sorted. 145 * 146 * The list is built from every time zone, so it is kept in the object 147 * cache for the day: for the request, and across requests when the cache 148 * is persistent. Daylight saving changes it at most a few times a year. 149 * 150 * @since 3.0.0 151 * 152 * @return string[] Offsets such as '-05:00'. 153 */ 53 154 function get_gmt_offsets() { 155 $key = 'gmt_offsets_' . gmdate( 'Y-m-d' ); 156 $o = wp_cache_get( $key, 'post-kinds' ); 157 if ( is_array( $o ) ) { 158 return $o; 159 } 54 160 $o = array(); 55 161 $t_zones = timezone_identifiers_list(); 56 162 foreach ( $t_zones as $a ) { 57 $t = '';58 163 try { 59 // this throws exception for 'US/Pacific-New'164 // Some identifiers, such as 'US/Pacific-New', throw on some PHP versions. 60 165 $zone = new DateTimeZone( $a ); 61 166 $seconds = $zone->getOffset( new DateTime( 'now', $zone ) ); 62 167 $o[] = tz_seconds_to_offset( $seconds ); 63 168 } catch ( Exception $e ) { 64 die( 'Exception : ' . esc_html( $e->getMessage() ) . '<br />' );65 // what to do in catch ? , nothing just relax169 // Skip the zone rather than fail. 170 continue; 66 171 } 67 172 } 68 173 $o = array_unique( $o ); 69 174 asort( $o ); 175 wp_cache_set( $key, $o, 'post-kinds', DAY_IN_SECONDS ); 70 176 return $o; 71 177 } … … 73 179 74 180 if ( ! function_exists( 'get_default_offset' ) ) { 75 // Gets the default offset 181 /** 182 * Returns the site's UTC offset, from its gmt_offset option. 183 * 184 * @since 3.0.0 185 * 186 * @return string Offset such as '-05:00'. 187 */ 76 188 function get_default_offset() { 77 189 $tz_seconds = get_option( 'gmt_offset' ) * 3600; … … 81 193 82 194 if ( ! function_exists( 'build_iso8601_time' ) ) { 83 // Turns individual pieces of a date and time into a single ISO8601 string 195 /** 196 * Joins a date, time and offset into an ISO 8601 string. 197 * 198 * @since 3.0.0 199 * 200 * @param string $date Date in Y-m-d format. 201 * @param string $time Time in H:i:s format. 202 * @param string $offset Offset such as '-05:00'. Empty for the site's offset. 203 * @return string The date and time, or an empty string if both are empty. 204 */ 84 205 function build_iso8601_time( $date, $time, $offset ) { 85 206 if ( empty( $date ) && empty( $time ) ) { … … 94 215 95 216 if ( ! function_exists( 'build_iso8601_duration' ) ) { 96 // Given an array with the pieces of a duration build an ISO8601 duration 217 /** 218 * Builds an ISO 8601 duration from its pieces. 219 * 220 * @since 3.0.0 221 * 222 * @param int[] $values Years (Y), months (M), days (D), hours (H), minutes (I) and seconds (S). 223 * @return string The duration, or an empty string if every piece is empty. 224 */ 97 225 function build_iso8601_duration( $values ) { 98 226 $values = array_filter( $values ); … … 128 256 129 257 if ( ! function_exists( 'calculate_duration' ) ) { 130 // Given two ISO8601 time strings return a DateInterval Object 258 /** 259 * Returns the duration between two dates. 260 * 261 * @since 2.5.0 262 * @since 4.0.0 Also accepts DateTimeInterface objects, such as Kind_Post::get( 'start' ) returns. 263 * 264 * @param DateTimeInterface|string $start_string Start, as a date or a string in Y-m-d\TH:i:sP format. 265 * @param DateTimeInterface|string $end_string End, as a date or a string in Y-m-d\TH:i:sP format. 266 * @return DateInterval|false The duration, or false if either date is not valid or they are the same. 267 */ 131 268 function calculate_duration( $start_string, $end_string ) { 132 $start = array(); 133 $end = array(); 134 if ( ! is_string( $start_string ) || ! is_string( $end_string ) ) { 269 $dates = array(); 270 foreach ( array( $start_string, $end_string ) as $value ) { 271 if ( is_string( $value ) ) { 272 // Only the full format: looser parsing reads stray text, such as a single letter, as a date. 273 $value = date_create_immutable_from_format( 'Y-m-d\TH:i:sP', $value ); 274 } 275 if ( ! $value instanceof DateTimeInterface ) { 276 return false; 277 } 278 $dates[] = $value; 279 } 280 list( $start, $end ) = $dates; 281 if ( $start == $end ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- Compares the moments, not the objects. 135 282 return false; 136 283 } 137 if ( $start_string === $end_string ) { 138 return false; 139 } 140 $start = date_create_from_format( 'Y-m-d\TH:i:sP', $start_string ); 141 $end = date_create_from_format( 'Y-m-d\TH:i:sP', $end_string ); 142 if ( ( $start instanceof DateTime ) && ( $end instanceof DateTime ) ) { 143 $duration = $start->diff( $end ); 144 return $duration; 145 } 146 return false; 147 } 148 } 149 150 if ( ! function_exists( 'seconds_to_iso8601' ) ) { 151 function seconds_to_iso8601( $second ) { 152 $h = intval( $second / 3600 ); 153 $m = intval( ( $second - $h * 3600 ) / 60 ); 154 $s = $second - ( $h * 3600 + $m * 60 ); 155 $ret = 'PT'; 156 if ( $h ) { 157 $ret .= $h . 'H'; 158 } 159 if ( $m ) { 160 $ret .= $m . 'M'; 161 } 162 if ( ( ! $h && ! $m ) || $s ) { 163 $ret .= $s . 'S'; 164 } 165 return $ret; 284 return $start->diff( $end ); 166 285 } 167 286 } 168 287 169 288 if ( ! function_exists( 'date_interval_to_iso8601' ) ) { 170 171 // Return a date interval as an ISO8601 string 289 /** 290 * Converts a duration to an ISO 8601 duration string. 291 * 292 * @since 3.0.0 293 * 294 * @param \DateInterval $interval The duration. 295 * @return string The duration, such as 'PT3M30S'. 296 */ 172 297 function date_interval_to_iso8601( \DateInterval $interval ) { 173 298 // Reading all non-zero date parts. … … 202 327 } 203 328 204 function display_formatted_datetime( $date ) { 205 if ( is_string( $date ) ) { 206 $date = new DateTimeImmutable( $date ); 207 } 208 209 return $date->format( get_option( 'date_format' ) ) . ' ' . $date->format( get_option( 'time_format' ) ); 210 } 211 212 213 /** 214 * Function to divide a datetime into an array for use in a field 215 * 216 * @access public 217 * 218 * @param DateTime $datetime 219 * @return array { 220 * @type string $date Date in Y-m-d format. 221 * @type string $time Time in H:i:s format. 222 * @type DateTimeZone $timezone Timezone object. 223 * } 224 */ 225 function divide_datetime( $datetime ) { 226 if ( ! $datetime ) { 227 return false; 228 } 229 230 if ( is_string( $datetime ) ) { 231 $datetime = new DateTime( $datetime ); 232 } 233 234 if ( ! $datetime ) { 235 return false; 236 } 237 238 $time = array(); 239 $time['date'] = $datetime->format( 'Y-m-d' ); 240 if ( '0000-01-01' === $time['date'] ) { 241 $time['date'] = ''; 242 } 243 $time['time'] = $datetime->format( 'H:i:s' ); 244 $time['offset'] = get_datetime_offset( $datetime ); 245 $time['class'] = get_class( $datetime ); 246 return array_filter( $time ); 247 } 248 249 /** 250 * Function to build a datetime from individual pieces 251 * 252 * @access public 253 * 254 * @param string $date Date in Y-m-d format. 255 * @param string $time Time in H:i:s format. 256 * @param DateTimeZone $timezone Timezone object. 257 * 258 * @return DateTimeImmutable|false DateTime object or false if not valid 259 */ 260 function build_datetime( $date, $time, $offset = null ) { 261 if ( empty( $date ) && empty( $time ) ) { 262 return false; 263 } 264 if ( is_string( $offset ) ) { 265 $timezone = timezone_open( $offset ); 266 } elseif ( $offset instanceof DateTimeZone ) { 267 $timezone = $offset; 268 } else { 269 return false; 270 } 271 if ( ! $timezone ) { 272 $timezone = wp_timezone(); 273 } 274 return date_create_immutable_from_format( 'Y-m-d\TH:i:sP', $date . 'T' . $time, $timezone ); 275 } 276 277 /** 278 * Return a formatted offset from a datetime object 279 * 280 * @access public 281 * 282 * @param DateTime $datetime DateTime object or if not passed set to now and site timezone 283 * 284 * @return string|false Formatted offset or false if not valid 285 */ 286 function get_datetime_offset( $datetime = null ) { 287 if ( ! $datetime ) { 288 $datetime = new DateTimeImmutable( 'now', wp_timezone() ); 289 } 290 $seconds = $datetime->getOffset(); 291 if ( false === $seconds ) { 292 return false; 293 } 294 return ( $seconds < 0 ? '-' : '+' ) . sprintf( '%02d:%02d', abs( $seconds / 60 / 60 ), abs( $seconds / 60 ) % 60 ); 295 } 296 297 298 // Given an ISO8601 duration return an array with the piece otherwise 0 duration. 299 function divide_interval( $interval ) { 300 $default = array( 301 'Y' => 0, 302 'M' => 0, 303 'D' => 0, 304 'H' => 0, 305 'I' => 0, 306 'S' => 0, 307 ); 308 if ( ! $interval ) { 309 return $default; 310 } 311 if ( is_string( $interval ) && ! empty( $interval ) ) { 312 try { 313 $interval = new DateInterval( $interval ); 314 } catch ( \Exception $e ) { 315 return $default; 316 } 317 } 318 // Reading all non-zero date parts. 319 $return = array( 320 'Y' => $interval->y, 321 'M' => $interval->m, 322 'D' => $interval->d, 323 'H' => $interval->h, 324 'I' => $interval->i, 325 'S' => $interval->s, 326 ); 327 return wp_parse_args( $return, $default ); 328 } 329 330 331 // Given an array with the pieces of a duration build an ISO8601 duration 332 function build_interval( $values ) { 333 $date = wp_array_slice_assoc( $values, array( 'Y', 'M', 'D' ) ); 334 $time = wp_array_slice_assoc( $values, array( 'H', 'I', 'S' ) ); 335 if ( ! $date || ! $time ) { 336 return ''; 337 } 338 $spec = 'P'; 339 // Adding each part to the spec-string. 340 foreach ( $date as $key => $value ) { 341 $spec .= $value . $key; 342 } 343 if ( count( $time ) > 0 ) { 344 $spec .= 'T'; 345 foreach ( $time as $key => $value ) { 346 if ( 'I' === $key ) { 347 $spec .= $value . 'M'; 348 } else { 349 $spec .= $value . $key; 350 } 351 } 352 } 353 return $spec; 354 } 329 if ( ! function_exists( 'display_formatted_datetime' ) ) { 330 /** 331 * Deprecated wrapper; see Kind_Time::display_formatted_datetime(). 332 * 333 * @since 3.2.0 334 * @deprecated 4.0.0 Use Kind_Time::display_formatted_datetime(). Will be removed in 5.0.0. 335 * 336 * @param DateTimeInterface|string $date Date, or a string kind_safe_datetime() accepts. 337 * @return string The formatted date and time, or an empty string if the date is not valid. 338 */ 339 function display_formatted_datetime( $date ) { 340 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::display_formatted_datetime()' ); 341 return Kind_Time::display_formatted_datetime( $date ); 342 } 343 } 344 345 if ( ! function_exists( 'divide_datetime' ) ) { 346 /** 347 * Deprecated wrapper; see Kind_Time::divide_datetime(). 348 * 349 * @since 3.3.4 350 * @deprecated 4.0.0 Use Kind_Time::divide_datetime(). Will be removed in 5.0.0. 351 * 352 * @param DateTimeInterface|string $datetime Date, or a string kind_safe_datetime() accepts. 353 * @return array|false The date's parts. See Kind_Time::divide_datetime(). 354 */ 355 function divide_datetime( $datetime ) { 356 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::divide_datetime()' ); 357 return Kind_Time::divide_datetime( $datetime ); 358 } 359 } 360 361 if ( ! function_exists( 'build_datetime' ) ) { 362 /** 363 * Deprecated wrapper; see Kind_Time::build_datetime(). 364 * 365 * @since 3.3.4 366 * @deprecated 4.0.0 Use Kind_Time::build_datetime(). Will be removed in 5.0.0. 367 * 368 * @param string $date Date in Y-m-d format. 369 * @param string $time Time in H:i or H:i:s format. 370 * @param string|DateTimeZone|null $offset Optional. Offset or time zone. Default the site's time zone. 371 * @return DateTimeImmutable|false The date, or false if it is not valid. 372 */ 373 function build_datetime( $date, $time, $offset = null ) { 374 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::build_datetime()' ); 375 return Kind_Time::build_datetime( $date, $time, $offset ); 376 } 377 } 378 379 if ( ! function_exists( 'get_datetime_offset' ) ) { 380 /** 381 * Deprecated wrapper; see Kind_Time::get_datetime_offset(). 382 * 383 * @since 3.3.4 384 * @deprecated 4.0.0 Use Kind_Time::get_datetime_offset(). Will be removed in 5.0.0. 385 * 386 * @param DateTimeInterface|null $datetime Optional. Date. Default now, in the site's time zone. 387 * @return string|false The offset, or false if it cannot be read. 388 */ 389 function get_datetime_offset( $datetime = null ) { 390 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::get_datetime_offset()' ); 391 return Kind_Time::get_datetime_offset( $datetime ); 392 } 393 } 394 395 if ( ! function_exists( 'divide_interval' ) ) { 396 /** 397 * Deprecated wrapper; see Kind_Time::divide_interval(). 398 * 399 * @since 3.3.4 400 * @deprecated 4.0.0 Use Kind_Time::divide_interval(). Will be removed in 5.0.0. 401 * 402 * @param DateInterval|string|null $interval Duration, or an ISO 8601 duration string. 403 * @return int[] The duration's parts. See Kind_Time::divide_interval(). 404 */ 405 function divide_interval( $interval ) { 406 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::divide_interval()' ); 407 return Kind_Time::divide_interval( $interval ); 408 } 409 } 410 411 if ( ! function_exists( 'build_interval' ) ) { 412 /** 413 * Deprecated wrapper; see Kind_Time::build_interval(). 414 * 415 * @since 3.3.4 416 * @deprecated 4.0.0 Use Kind_Time::build_interval(). Will be removed in 5.0.0. 417 * 418 * @param int[] $values Years (Y), months (M), days (D), hours (H), minutes (I) and seconds (S). 419 * @return string The duration, or an empty string if every piece is 0. 420 */ 421 function build_interval( $values ) { 422 _deprecated_function( __FUNCTION__, '4.0.0', 'Kind_Time::build_interval()' ); 423 return Kind_Time::build_interval( $values ); 424 } 425 } -
indieweb-post-kinds/trunk/indieweb-post-kinds.php
r3067251 r3738687 4 4 * 5 5 * @link http://indieweb.org/Post_Kinds_Plugin 6 * @package Post Kinds6 * @package Post_Kinds 7 7 * Plugin Name: Post Kinds 8 8 * Plugin URI: https://wordpress.org/plugins/indieweb-post-kinds/ 9 9 * Description: Ever want to reply to someone else's post with a post on your own site? Or to "like" someone else's post, but with your own site? 10 * Version: 3.7.3 10 * Version: 4.0.0 11 * Requires at least: 6.2 12 * Requires PHP: 7.4 13 * License: GPLv2 or later 14 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 15 * Author: David Shanske 12 16 * Author URI: https://david.shanske.com … … 14 18 * Domain Path: /languages 15 19 */ 20 21 defined( 'ABSPATH' ) || exit; 16 22 17 23 if ( ! defined( 'POST_KINDS_KSES' ) ) { … … 40 46 add_action( 'upgrader_process_complete', array( 'Post_Kinds_Plugin', 'upgrader_process_complete' ), 10, 2 ); 41 47 42 if ( ! file_exists( plugin_dir_path( __FILE__ ) . 'lib/parse-this/parse-this.php' ) ) {43 add_action( 'admin_notices', array( 'Post_Kinds_Plugin', 'parse_this_error' ) );44 }45 48 46 49 if ( Post_Kinds_Plugin::show_editor_error() ) { … … 51 54 add_action( 'init', array( 'Post_Kinds_Plugin', 'init' ) ); 52 55 56 /** 57 * Loads the plugin, its assets and its global functions. 58 */ 53 59 class Post_Kinds_Plugin { 60 /** 61 * Plugin version, read from the plugin header on init. 62 * 63 * @since 2.4.0 64 * @var string 65 */ 54 66 public static $version; 55 67 68 /** 69 * Sets the version and registers the kind taxonomy. 70 * 71 * @since 2.4.0 72 */ 56 73 public static function init() { 57 74 self::$version = get_file_data( __FILE__, array( 'Version' => 'Version' ) )['Version']; … … 61 78 } 62 79 80 /** 81 * Outputs an admin notice when Parse This is not available at all. 82 * 83 * @since 3.1.0 84 */ 63 85 public static function parse_this_error() { 64 $class = 'notice notice-error'; 65 $message = __( 'Parse This is not installed. Please advise the developer', 'indieweb-post-kinds' ); 66 printf( '<div class="%1$s"><p>%2$s</p></div>', esc_attr( $class ), esc_html( $message ) ); 67 } 68 86 wp_admin_notice( 87 esc_html__( 'Parse This is not installed. Please advise the developer', 'indieweb-post-kinds' ), 88 array( 'type' => 'error' ) 89 ); 90 } 91 92 /** 93 * Outputs an admin notice when another plugin loaded a copy of Parse This 94 * older than 2.0.0. 95 * 96 * Post Kinds still works, because its bundled 2.0 API is loaded alongside the 97 * older copy, but the older copy serves the parse-this REST route used by the 98 * reply metabox. 99 * 100 * @since 4.0.0 101 */ 102 public static function parse_this_outdated_notice() { 103 if ( ! current_user_can( 'activate_plugins' ) ) { 104 return; 105 } 106 wp_admin_notice( 107 sprintf( 108 /* translators: %s: Name of the plugin, or path of the file, that loaded the older copy of Parse This. */ 109 esc_html__( 'Post Kinds: %s is loading a copy of Parse This older than version 2.0. Post Kinds will keep working, but URL lookups in the post editor use that older copy. Updating that plugin, or installing the Parse This plugin (version 2.0 or later), resolves this.', 'indieweb-post-kinds' ), 110 '<strong>' . esc_html( self::parse_this_source() ) . '</strong>' 111 ), 112 array( 'type' => 'warning' ) 113 ); 114 } 115 116 /** 117 * Returns whether the loaded copy of Parse This is version 2.0.0 or later. 118 * 119 * Copies older than 2.0.0 do not define PARSE_THIS_VERSION. 120 * 121 * @since 4.0.0 122 * 123 * @return bool 124 */ 125 public static function parse_this_is_current() { 126 return defined( 'PARSE_THIS_VERSION' ) && version_compare( PARSE_THIS_VERSION, '2.0.0', '>=' ); 127 } 128 129 /** 130 * Returns a name for whatever loaded Parse This: the plugin's name, or the 131 * file's path relative to the content directory if it isn't a plugin. 132 * 133 * @since 4.0.0 134 * 135 * @return string 136 */ 137 public static function parse_this_source() { 138 if ( ! function_exists( 'parse_this_loader' ) ) { 139 return ''; 140 } 141 $file = wp_normalize_path( ( new ReflectionFunction( 'parse_this_loader' ) )->getFileName() ); 142 $dir = trailingslashit( wp_normalize_path( WP_PLUGIN_DIR ) ); 143 if ( 0 === strpos( $file, $dir ) ) { 144 $slug = strtok( substr( $file, strlen( $dir ) ), '/' ); 145 if ( ! function_exists( 'get_plugins' ) ) { 146 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 147 } 148 foreach ( get_plugins() as $basename => $data ) { 149 if ( 0 === strpos( $basename, $slug . '/' ) ) { 150 return $data['Name']; 151 } 152 } 153 return $slug; 154 } 155 return str_replace( trailingslashit( wp_normalize_path( WP_CONTENT_DIR ) ), '', $file ); 156 } 157 158 /** 159 * Whether to check for the block editor and warn about it. 160 * 161 * Not on ClassicPress, or when the Classic Editor plugin is active. 162 * 163 * @since 3.3.4 164 * 165 * @return bool 166 */ 69 167 public static function show_editor_error() { 70 // Do not show this error in ClassicPress 168 // Do not show this error in ClassicPress. 71 169 if ( self::is_classicpress() ) { 72 170 return false; 73 171 } 74 // Do not show if less than Version 575 if ( version_compare( get_bloginfo( 'version' ), '5.0' ) <= 0 ) {76 return false;77 }78 172 if ( class_exists( 'Classic_Editor' ) ) { 79 173 return false; … … 82 176 } 83 177 178 /** 179 * Whether the site runs ClassicPress. 180 * 181 * @since 3.6.0 182 * 183 * @return bool 184 */ 84 185 public static function is_classicpress() { 85 186 return function_exists( 'classicpress_version' ); 86 187 } 87 188 189 /** 190 * Outputs an admin notice on screens that use the block editor. 191 * 192 * Post Kinds works only with the Classic Editor. The notice is not shown 193 * if the Classic Editor or Disable Gutenberg plugin is active. 194 * 195 * @since 3.2.6 196 * 197 * @return string|void An empty string if no notice is needed. 198 */ 88 199 public static function classic_editor_error() { 89 200 if ( ! self::post_uses_gutenberg() ) { … … 101 212 102 213 214 /** 215 * Flushes the rewrite rules after Post Kinds is updated. 216 * 217 * @since 3.4.19 218 * 219 * @param WP_Upgrader $upgrade_object The upgrader. 220 * @param array $options Details of the update. 221 */ 103 222 public static function upgrader_process_complete( $upgrade_object, $options ) { 104 223 $current_plugin_path_name = plugin_basename( __FILE__ ); … … 112 231 } 113 232 233 /** 234 * Creates the kind terms and flushes the rewrite rules on activation. 235 * 236 * @since 3.3.0 237 */ 114 238 public static function activate() { 115 239 Kind_Taxonomy::activate_kinds(); … … 117 241 } 118 242 243 /** 244 * Flushes the rewrite rules on deactivation. 245 * 246 * @since 3.3.0 247 */ 119 248 public static function deactivate() { 120 249 flush_rewrite_rules(); 121 250 } 122 251 252 /** 253 * Loads the translations, global functions and Parse This, and adds the hooks. 254 * 255 * @since 2.5.2 256 */ 123 257 public static function plugins_loaded() { 124 258 $cls = get_called_class(); 125 259 load_plugin_textdomain( 'indieweb-post-kinds', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); 126 260 127 // Add WordPress Compatibility File for Functions Introduced Post 4.9.9.261 // Polyfills for functions newer than the plugin's minimums. 128 262 require_once plugin_dir_path( __FILE__ ) . 'includes/compat.php'; 129 // Parse this Compat File.130 require_once plugin_dir_path( __FILE__ ) . 'lib/parse-this/includes/compat-functions.php';131 263 132 264 // Add Kind Global Functions. … … 136 268 require_once plugin_dir_path( __FILE__ ) . '/includes/time-functions.php'; 137 269 138 // Parse This 139 require_once plugin_dir_path( __FILE__ ) . 'lib/parse-this/includes/autoload.php'; 140 if ( ! class_exists( 'REST_Parse_This' ) ) { 141 require_once plugin_dir_path( __FILE__ ) . 'lib/parse-this/includes/class-rest-parse-this.php'; 142 } 143 require_once plugin_dir_path( __FILE__ ) . 'lib/parse-this/includes/functions.php'; 270 /* 271 * Parse This. The standalone plugin, if active, has already loaded at 272 * plugins_loaded priority 9 and defined parse_this_loader(). Otherwise 273 * load the bundled copy. Its loader sets up the classes, the functions 274 * and the REST route. 275 */ 276 $parse_this = plugin_dir_path( __FILE__ ) . 'lib/parse-this/parse-this.php'; 277 if ( ! function_exists( 'parse_this_loader' ) && file_exists( $parse_this ) ) { 278 require_once $parse_this; 279 parse_this_loader(); 280 } 281 282 if ( ! function_exists( 'parse_this_loader' ) ) { 283 add_action( 'admin_notices', array( $cls, 'parse_this_error' ) ); 284 } elseif ( ! self::parse_this_is_current() ) { 285 /* 286 * Another plugin loaded a copy older than 2.0.0. Load the bundled 2.0 287 * API alongside it: everything in 2.0 is in the ParseThis namespace, 288 * so it does not clash with the old copy's global names, and both 289 * versions only load their bundled Mf2\Parser and Masterminds\HTML5 290 * if those classes do not already exist. 291 */ 292 $includes = plugin_dir_path( __FILE__ ) . 'lib/parse-this/includes/'; 293 if ( file_exists( $includes . 'autoload.php' ) ) { 294 require_once $includes . 'autoload.php'; 295 require_once $includes . 'functions.php'; 296 } 297 add_action( 'admin_notices', array( $cls, 'parse_this_outdated_notice' ) ); 298 } 144 299 $class_load = array( 145 'Plugins', // Plugin Specific Customization 146 'Media_Metadata', // Media Metadata Enhancements 147 'Config', // Configuration Menu 148 'Metabox', // Metabox for Classic Editor 149 'View', // Kind Display Functionality 300 'Plugins', // Plugin Specific Customization. 301 'Media_Metadata', // Media Metadata Enhancements. 302 'Config', // Configuration Menu. 303 'Metabox', // Metabox for Classic Editor. 304 'View', // Kind Display Functionality. 305 'Upgrade', // One-time upgrade of stored data. 150 306 ); 151 307 152 308 foreach ( $class_load as $load ) { 153 309 add_action( 'init', array( 'Kind_' . $load, 'init' ) ); 310 } 311 312 if ( defined( 'WP_CLI' ) && WP_CLI ) { 313 WP_CLI::add_command( 'post-kinds upgrade', array( 'Kind_Upgrade', 'cli' ) ); 154 314 } 155 315 … … 162 322 add_action( 'admin_enqueue_scripts', array( $cls, 'admin_style_load' ) ); 163 323 164 // Load Privacy Declaration 324 // Load Privacy Declaration. 165 325 add_action( 'admin_init', array( $cls, 'privacy_declaration' ) ); 166 remove_all_actions( 'do_feed_rss2' ); 167 remove_all_actions( 'do_feed_atom' ); 168 add_action( 'do_feed_rss2', array( $cls, 'do_feed_rss2' ), 10, 1 ); 169 add_action( 'do_feed_atom', array( $cls, 'do_feed_atom' ), 10, 1 ); 170 171 // Register Widgets 326 327 // Register Widgets. 172 328 add_action( 173 329 'widgets_init', … … 179 335 } 180 336 181 public static function do_feed_atom( $for_comments ) {182 if ( $for_comments ) {183 load_template( plugin_dir_path( __FILE__ ) . 'templates/feed-atom-comments.php' );184 } else {185 load_template( plugin_dir_path( __FILE__ ) . 'templates/feed-atom.php' );186 }187 }188 189 public static function do_feed_rss2( $for_comments ) {190 if ( $for_comments ) {191 load_template( plugin_dir_path( __FILE__ ) . 'templates/feed-rss2-comments.php' );192 } else {193 load_template( plugin_dir_path( __FILE__ ) . 'templates/feed-rss2.php' );194 }195 }196 197 337 /** 198 338 * Adds link to Plugin Page for Options Page. 339 * 340 * @since 2.4.0 199 341 * 200 342 * @access public … … 217 359 /** 218 360 * Loads the Stylesheet for the Plugin. 361 * 362 * @since 2.4.0 219 363 */ 220 364 public static function style_load() { … … 224 368 /** 225 369 * Loads the Admin Stylesheet for the Plugin. 370 * 371 * @since 2.4.0 226 372 */ 227 373 public static function admin_style_load() { … … 229 375 } 230 376 377 /** 378 * Adds suggested text to the privacy policy guide. 379 * 380 * @since 3.0.5 381 */ 231 382 public static function privacy_declaration() { 232 383 if ( function_exists( 'wp_add_privacy_policy_content' ) ) { … … 242 393 } 243 394 395 /** 396 * Whether the current screen uses the block editor. 397 * 398 * Screens other than the post editor count as using it. 399 * 400 * @since 3.3.2 401 * 402 * @return bool 403 */ 244 404 public static function post_uses_gutenberg() { 245 405 $screen = get_current_screen(); -
indieweb-post-kinds/trunk/js/clone-media-fragment.js
r2110745 r3738687 1 1 function cloneMediaFragment() { 2 // Check that the fragment is a Media Fragment (starts with t=)3 if(window.location.hash && window.location.hash.match(/^#t=/)) {4 // Find any video and audio tags on the page5 document.querySelectorAll("video,audio").forEach(function(el){6 // Create a virtual element to use as a URI parser7 var parser = document.createElement('a');8 parser.href = el.currentSrc;9 // Replace the hash 10 parser.hash = window.location.hash;11 // Set the src of the video/audio tag to the full URL12 el.src = parser.href;13 });14 }2 // Check that the fragment is a Media Fragment (starts with t=) 3 if ( window.location.hash && window.location.hash.match( /^#t=/ ) ) { 4 // Find any video and audio tags on the page 5 document.querySelectorAll( 'video,audio' ).forEach( function( el ) { 6 // Create a virtual element to use as a URI parser 7 var parser = document.createElement( 'a' ); 8 parser.href = el.currentSrc; 9 // Replace the hash 10 parser.hash = window.location.hash; 11 // Set the src of the video/audio tag to the full URL 12 el.src = parser.href; 13 } ); 14 } 15 15 } 16 16 17 document.addEventListener( "DOMContentLoaded", function() {18 cloneMediaFragment();19 // When the media is paused, update the fragment of the page20 document.querySelectorAll("video,audio").forEach(function(el) { 21 el.addEventListener("pause", function(event) {22 // Update the media fragment to the current time23 // Use replaceState to avoid triggering the "hashchange" listener above24 history.replaceState(null, null, "#t=" + Math.round(event.target.currentTime));25 });26 });27 } );17 document.addEventListener( 'DOMContentLoaded', function() { 18 cloneMediaFragment(); 19 // When the media is paused, update the fragment of the page 20 document.querySelectorAll( 'video,audio' ).forEach( function( el ) { 21 el.addEventListener( 'pause', function( event ) { 22 // Update the media fragment to the current time 23 // Use replaceState to avoid triggering the "hashchange" listener above 24 history.replaceState( null, null, '#t=' + Math.round( event.target.currentTime ) ); 25 } ); 26 } ); 27 } ); 28 28 29 29 // If the user changes the hash manually, clone the fragment to the media URLs 30 window.addEventListener( "hashchange", cloneMediaFragment);30 window.addEventListener( 'hashchange', cloneMediaFragment ); -
indieweb-post-kinds/trunk/js/kind.js
r3001401 r3738687 1 /* global PKAPI, moment, wp */ 2 /* eslint-disable no-alert -- The metabox uses alert() and confirm() for feedback by design. */ 1 3 jQuery( document ).ready( function( $ ) { 4 var KindWindow; 5 2 6 changeSettings(); 3 7 4 5 function clearPostProperties() { 6 var fieldIds = [ 7 'cite_url', 8 'cite_name', 9 'cite_summary', 10 'cite_tags', 11 'cite_media', 12 'cite_author_name', 13 'cite_author_url', 14 'cite_author_photo', 15 'cite_featured', 16 'cite_publication', 17 'mf2_rsvp', 18 'mf2_rating', 19 'mf2_start_time', 20 'mf2_start_date', 21 'mf2_end_time', 22 'mf2_end_date', 23 'cite_published_time', 24 'cite_published_date', 25 'cite_updated_time', 26 'cite_updated_date', 27 'duration_years', 28 'duration_months', 29 'duration_days', 30 'duration_hours', 31 'duration_minutes', 32 'duration_seconds' 33 ]; 8 function clearPostProperties() { 9 var fieldIds = [ 10 'cite_url', 11 'cite_name', 12 'cite_summary', 13 'cite_tags', 14 'cite_media', 15 'cite_author_name', 16 'cite_author_url', 17 'cite_author_photo', 18 'cite_featured', 19 'cite_publication', 20 'mf2_rsvp', 21 'mf2_rating', 22 'mf2_start_time', 23 'mf2_start_date', 24 'mf2_end_time', 25 'mf2_end_date', 26 'cite_published_time', 27 'cite_published_date', 28 'cite_updated_time', 29 'cite_updated_date', 30 'duration_years', 31 'duration_months', 32 'duration_days', 33 'duration_hours', 34 'duration_minutes', 35 'duration_seconds', 36 ]; 34 37 if ( ! confirm( PKAPI.clear_message ) ) { 35 38 return; … … 37 40 $.each( fieldIds, function( count, val ) { 38 41 document.getElementById( val ).value = ''; 39 } );42 } ); 40 43 $( '#kind-media-container' ).addClass( 'hidden' ); 41 44 $( '#kind-media-container' ).children( 'img' ).hide(); 42 45 $( '#add-kind-media' ).show(); 43 44 } 45 46 function addhttp( url ) { 47 if ( ! /^(?:f|ht)tps?\:\/\//.test( url ) ) { 48 url = 'http://' + url; 49 } 50 return url; 51 } 52 53 function showLoadingSpinner() { 54 $( '#replybox-meta' ).addClass( 'is-loading' ); 55 } 56 57 function hideLoadingSpinner() { 58 $( '#replybox-meta' ).removeClass( 'is-loading' ); 59 } 60 61 //function used to validate website URL 62 function checkUrl( url ) { 63 64 //regular expression for URL 65 var pattern = /^(http|https)?:\/\/[a-zA-Z0-9-\.]+\.[a-z]{2,4}/; 66 67 if ( pattern.test( url ) ) { 68 return true; 69 } else { 70 return false; 71 } 72 } 73 74 function getLinkPreview() { 75 if ( '' === $( '#cite_url' ).val() ) { 76 return; 77 } 78 $.ajax({ 79 type: 'GET', 80 81 // Here we supply the endpoint url, as opposed to the action in the data object with the admin-ajax method 82 url: PKAPI.api_url + 'parse/', 83 beforeSend: function( xhr ) { 84 85 // Here we set a header 'X-WP-Nonce' with the nonce as opposed to the nonce in the data object with admin-ajax 86 xhr.setRequestHeader( 'X-WP-Nonce', PKAPI.api_nonce ); 87 }, 88 data: { 89 url: $( '#cite_url' ).val(), 90 kind: $( 'input[name=\'tax_input[kind]\']:checked' ).val(), 91 follow: true 92 }, 93 success: function( response ) { 94 var published; 95 var updated; 96 if ( 'undefined' === typeof response ) { 97 alert( 'Error: Unable to Retrieve' ); 46 } 47 48 function showLoadingSpinner() { 49 $( '#replybox-meta' ).addClass( 'is-loading' ); 50 } 51 52 function hideLoadingSpinner() { 53 $( '#replybox-meta' ).removeClass( 'is-loading' ); 54 } 55 56 //function used to validate website URL 57 function checkUrl( url ) { 58 //regular expression for URL 59 var pattern = /^(http|https)?:\/\/[a-zA-Z0-9-\.]+\.[a-z]{2,4}/; 60 61 return pattern.test( url ); 62 } 63 64 // Fills the Author tab from one author or a list. Each field holds one value 65 // per author, separated by semicolons, so the values stay paired by position. 66 function fillAuthors( authors ) { 67 var fields = { name: [], url: [], photo: [] }; 68 if ( ! Array.isArray( authors ) ) { 69 authors = [ authors ]; 70 } 71 $.each( authors, function( index, author ) { 72 if ( 'string' === typeof author ) { 73 author = checkUrl( author ) ? { url: author } : { name: author }; 74 } 75 if ( ! author || 'object' !== typeof author ) { 98 76 return; 99 77 } 100 if ( 'message' in response ) { 101 alert( response.message ); 78 $.each( fields, function( key, values ) { 79 var value = author[ key ]; 80 if ( Array.isArray( value ) ) { 81 value = value.join( '; ' ); 82 } else if ( value && 'object' === typeof value ) { 83 value = value.value; 84 } 85 values.push( 'string' === typeof value ? value : '' ); 86 } ); 87 } ); 88 $.each( fields, function( key, values ) { 89 $( '#cite_author_' + key ).val( values.join( '' ) ? values.join( '; ' ) : '' ); 90 } ); 91 } 92 93 function getLinkPreview() { 94 if ( '' === $( '#cite_url' ).val() ) { 95 return; 96 } 97 $.ajax( { 98 type: 'GET', 99 100 // Here we supply the endpoint url, as opposed to the action in the data object with the admin-ajax method 101 url: PKAPI.api_url + 'parse/', 102 beforeSend: function( xhr ) { 103 // Here we set a header 'X-WP-Nonce' with the nonce as opposed to the nonce in the data object with admin-ajax 104 xhr.setRequestHeader( 'X-WP-Nonce', PKAPI.api_nonce ); 105 }, 106 data: { 107 url: $( '#cite_url' ).val(), 108 follow: true, 109 }, 110 success: function( response ) { 111 var published, updated, duration; 112 if ( 'undefined' === typeof response ) { 113 alert( PKAPI.error_message ); 114 return; 115 } 116 if ( 'message' in response ) { 117 alert( response.message ); 118 return; 119 } 120 if ( 'name' in response ) { 121 $( '#cite_name' ).val( response.name ); 122 } 123 if ( 'published' in response ) { 124 published = moment.parseZone( response.published ); 125 $( '#cite_published_date' ).val( published.format( 'YYYY-MM-DD' ) ); 126 $( '#cite_published_time' ).val( published.format( 'HH:mm:ss' ) ); 127 $( '#cite_published_offset' ).val( published.format( 'Z' ) ); 128 } 129 if ( 'updated' in response ) { 130 updated = moment.parseZone( response.updated ); 131 $( '#cite_updated_date' ).val( updated.format( 'YYYY-MM-DD' ) ); 132 $( '#cite_updated_time' ).val( updated.format( 'HH:mm:ss' ) ); 133 $( '#cite_updated_offset' ).val( updated.format( 'Z' ) ); 134 } 135 if ( 'duration' in response ) { 136 duration = moment.duration( response.duration ); 137 $( '#duration_years' ).val( duration.years() ); 138 $( '#duration_months' ).val( duration.months() ); 139 $( '#duration_days' ).val( duration.days() ); 140 $( '#duration_hours' ).val( duration.hours() ); 141 $( '#duration_minutes' ).val( duration.minutes() ); 142 $( '#duration_seconds' ).val( duration.seconds() ); 143 } 144 145 if ( 'summary' in response ) { 146 $( '#cite_summary' ).val( response.summary ); 147 } 148 if ( 'featured' in response ) { 149 $( '#cite_featured' ).val( response.featured ); 150 } 151 if ( 'author' in response ) { 152 fillAuthors( response.author ); 153 } 154 if ( 'publication' in response && ( 'string' !== typeof response.publication ) ) { 155 if ( 'name' in response.publication ) { 156 $( '#cite_publication' ).val( response.publication.name ); 157 } 158 } else { 159 $( '#cite_publication' ).val( response.publication ); 160 } 161 162 if ( 'category' in response ) { 163 if ( 'object' === typeof response.category ) { 164 $( '#cite_tags' ).val( response.category.join( ';' ) ); 165 } 166 } 167 alert( PKAPI.success_message ); 168 }, 169 error: function( jqXHR ) { 170 if ( jqXHR.responseJSON && jqXHR.responseJSON.message ) { 171 alert( jqXHR.responseJSON.message ); 172 } else { 173 alert( PKAPI.error_message ); 174 } 175 }, 176 complete: hideLoadingSpinner, 177 } ); 178 } 179 180 function changeSettings() { 181 var kind = $( 'input[name=\'tax_input[kind]\']:checked' ).val(); 182 switch ( kind ) { 183 case 'note': 184 hideTitle(); 185 hideReply(); 186 hideRSVP(); 187 hideRating(); 188 hideTime(); 189 hideMedia(); 190 break; 191 case 'article': 192 showTitle(); 193 hideReply(); 194 hideRSVP(); 195 hideRating(); 196 hideTime(); 197 hideMedia(); 198 break; 199 case 'issue': 200 showTitle(); 201 showReply(); 202 hideRSVP(); 203 hideRating(); 204 hideTime(); 205 hideMedia(); 206 break; 207 case 'listen': 208 case 'jam': 209 case 'watch': 210 case 'read': 211 case 'play': 212 hideTitle(); 213 showReply(); 214 hideRSVP(); 215 showRating(); 216 showTime(); 217 hideMedia(); 218 break; 219 case 'photo': 220 case 'video': 221 case 'audio': 222 hideTitle(); 223 showReply(); 224 hideRSVP(); 225 hideRating(); 226 showTime(); 227 showMedia(); 228 break; 229 case 'rsvp': 230 showReply(); 231 hideTitle(); 232 hideTime(); 233 showRSVP(); 234 hideRating(); 235 hideMedia(); 236 break; 237 case 'review': 238 case 'drink': 239 case 'eat': 240 showReply(); 241 hideTitle(); 242 hideTime(); 243 hideRSVP(); 244 showRating(); 245 hideMedia(); 246 break; 247 default: 248 showReply(); 249 hideTime(); 250 hideTitle(); 251 hideRSVP(); 252 hideRating(); 253 hideMedia(); 254 } 255 } 256 257 function showReply() { 258 $( '#replybox-meta' ).removeClass( 'hidden' ); 259 } 260 261 function hideReply() { 262 $( '#replybox-meta' ).addClass( 'hidden' ); 263 } 264 265 function showMedia() { 266 $( '#add-kind-media' ).removeClass( 'hidden' ); 267 } 268 269 function hideMedia() { 270 $( '#add-kind-media' ).addClass( 'hidden' ); 271 } 272 273 function showTitle() { 274 var titlediv = $( '#titlediv' ).detach(); 275 titlediv.prependTo( '#post-body-content' ); 276 } 277 278 function hideTitle() { 279 var titlediv = $( '#titlediv' ).detach(); 280 titlediv.insertAfter( '#postdivrich' ); 281 } 282 283 function showRSVP() { 284 $( '#rsvp-option' ).removeClass( 'hide-if-js' ); 285 } 286 287 function hideRSVP() { 288 $( '#rsvp-option' ).addClass( 'hide-if-js' ); 289 } 290 291 function showRating() { 292 $( '#rating-option' ).removeClass( 'hide-if-js' ); 293 } 294 295 function hideRating() { 296 $( '#rating-option' ).addClass( 'hide-if-js' ); 297 } 298 299 function showTime() { 300 $( '#kind-time' ).removeClass( 'hide-if-js' ); 301 } 302 303 function hideTime() { 304 $( '#kind-time' ).addClass( 'hide-if-js' ); 305 } 306 307 function handleKindMediaWindow() { 308 'use strict'; 309 310 var json; 311 312 /** 313 * If an instance of KindWindow already exists, then we can open it 314 * rather than creating a new instance. 315 */ 316 if ( undefined !== KindWindow ) { 317 KindWindow.open(); 318 return; 319 } 320 321 KindWindow = wp.media.frames.KindWindow = wp.media( { 322 title: PKAPI.media_title, 323 button: { 324 text: PKAPI.media_button, 325 }, 326 multiple: false, 327 } ); 328 329 KindWindow.on( 'select', function() { 330 json = KindWindow.state().get( 'selection' ).first().toJSON(); 331 if ( ! json.url ) { 102 332 return; 103 333 } 104 if ( 'name' in response ) { 105 $( '#cite_name' ).val( response.name ); 106 } 107 if ( 'published' in response ) { 108 var published = moment.parseZone( response.published ); 109 $( '#cite_published_date' ).val( published.format( 'YYYY-MM-DD' ) ) ; 110 $( '#cite_published_time' ).val( published.format( 'HH:mm:ss' ) ) ; 111 $( '#cite_published_offset' ).val( published.format( 'Z' ) ); 112 } 113 if ( 'updated' in response ) { 114 var updated = moment.parseZone( response.updated ); 115 $( '#cite_updated_date' ).val( updated.format( 'YYYY-MM-DD' ) ) ; 116 $( '#cite_updated_time' ).val( updated.format( 'HH:mm:ss' ) ) ; 117 $( '#cite_updated_offset' ).val( updated.format( 'Z' ) ); 118 } 119 if ( 'duration' in response ) { 120 var duration = moment.duration( response.duration); 121 $( '#duration_years' ).val( duration.years() ); 122 $( '#duration_months' ).val( duration.months() ); 123 $( '#duration_days' ).val( duration.days() ); 124 $( '#duration_hours' ).val( duration.hours() ); 125 $( '#duration_minutes' ).val( duration.minutes() ); 126 $( '#duration_seconds' ).val( duration.seconds() ); 127 } 128 129 if ( 'summary' in response ) { 130 $( '#cite_summary' ).val( response.summary ) ; 131 } 132 if ( 'featured' in response ) { 133 $( '#cite_featured' ).val( response.featured ) ; 134 } 135 if ( ( 'author' in response ) && ( 'string' != typeof response.author ) ) { 136 if ( 'name' in response.author ) { 137 if ( 'string' === typeof response.author.name ) { 138 $( '#cite_author_name' ).val( response.author.name ); 139 } else { 140 $( '#cite_author_name' ).val( response.author.name.join( ';' ) ) ; 141 } 142 } 143 if ( 'photo' in response.author ) { 144 if ( 'string' === typeof response.author.name ) { 145 $( '#cite_author_photo' ).val( response.author.photo ); 146 } else { 147 $( '#cite_author_photo' ).val( response.author.photo.join( ';' ) ) ; 148 } 149 } 150 if ( 'url' in response.author ) { 151 if ( 'string' === typeof response.author.url ) { 152 $( '#cite_author_url' ).val( response.author.url ); 153 } else { 154 $( '#cite_author_url' ).val( response.author.url.join( ';' ) ) ; 155 } 156 } 157 } 158 if ( 'publication' in response && ( 'string' != typeof response.publication ) ) { 159 if ( 'name' in response.publication ) { 160 $( '#cite_publication' ).val( response.publication.name ); 161 } 334 $( '#cite_name' ).val( json.title ); 335 $( '#cite_url' ).val( json.url ); 336 $( '#cite_media' ).val( json.id ); 337 $( '#cite_summary' ).val( json.description ); 338 $( '#kind-media-container' ) 339 .children( 'img' ) 340 .attr( 'src', json.url ) 341 .attr( 'alt', json.caption ) 342 .attr( 'title', json.title ) 343 .show() 344 .parent() 345 .removeClass( 'hidden' ); 346 347 $( '#add-kind-media' ).hide(); 348 } ); 349 KindWindow.open(); 350 } 351 352 jQuery( document ) 353 .on( 'change', '#taxonomy-kind', function( event ) { 354 changeSettings(); 355 event.preventDefault(); 356 } ) 357 .on( 'blur', '#cite_url', function( event ) { 358 if ( '' !== $( '#cite_url' ).val() ) { 359 if ( false === checkUrl( $( '#cite_url' ).val() ) ) { 360 alert( PKAPI.invalid_url ); 361 } else if ( '' === $( '#cite_name' ).val() ) { 362 showLoadingSpinner(); 363 getLinkPreview(); 364 } 365 event.preventDefault(); 366 } 367 } ) 368 .on( 'click', '.clear-kindmeta-button', function( event ) { 369 clearPostProperties(); 370 event.preventDefault(); 371 } ) 372 .on( 'click', 'a.show-kind-details', function( event ) { 373 if ( $( '#kind-details' ).is( ':hidden' ) ) { 374 $( '#kind-author' ).slideUp( 'fast' ).siblings( 'a.show-kind-author' ); 375 $( '#kind-details' ).slideDown( 'fast' ).siblings( 'a.hide-kind-details' ).show().focus(); 162 376 } else { 163 $( '#cite_publication' ).val( response.publication ) ; 164 } 165 166 if ( 'category' in response ) { 167 if ( 'object' === typeof response.category ) { 168 $( '#cite_tags' ).val( response.category.join( ';' ) ); 169 } 170 } 171 alert( PKAPI.success_message ); 172 console.log( response ); 173 }, 174 fail: function( response ) { 175 console.log( response ); 176 alert( response.message ); 177 }, 178 error: function( jqXHR, textStatus, errorThrown ) { 179 alert( jqXHR.responseJSON.message ); 180 console.log( jqXHR ); 181 }, 182 always: hideLoadingSpinner() 183 }); 184 } 185 186 function changeSettings() { 187 kind = $( 'input[name=\'tax_input[kind]\']:checked' ).val(); 188 switch ( kind ) { 189 case 'note': 190 hideTitle(); 191 hideReply(); 192 hideRSVP(); 193 hideRating(); 194 hideTime(); 195 hideMedia(); 196 break; 197 case 'article': 198 showTitle(); 199 hideReply(); 200 hideRSVP(); 201 hideRating(); 202 hideTime(); 203 hideMedia(); 204 break; 205 case 'issue': 206 showTitle(); 207 showReply(); 208 hideRSVP(); 209 hideRating(); 210 hideTime(); 211 hideMedia(); 212 break; 213 case 'listen': 214 case 'jam': 215 case 'watch': 216 case 'read': 217 case 'play': 218 hideTitle(); 219 showReply(); 220 hideRSVP(); 221 showRating(); 222 showTime(); 223 hideMedia(); 224 break; 225 case 'photo': 226 case 'video': 227 case 'audio': 228 hideTitle(); 229 showReply(); 230 hideRSVP(); 231 hideRating(); 232 showTime(); 233 showMedia(); 234 break; 235 case 'rsvp': 236 showReply(); 237 hideTitle(); 238 hideTime(); 239 showRSVP(); 240 hideRating(); 241 hideMedia(); 242 break; 243 case 'review': 244 case 'drink': 245 case 'eat': 246 showReply(); 247 hideTitle(); 248 hideTime(); 249 hideRSVP(); 250 showRating(); 251 hideMedia(); 252 break; 253 default: 254 showReply(); 255 hideTime(); 256 hideTitle(); 257 hideRSVP(); 258 hideRating(); 259 hideMedia(); 260 } 261 } 262 263 function showReply() { 264 $( '#replybox-meta' ).removeClass( 'hidden' ); 265 } 266 267 function hideReply() { 268 $( '#replybox-meta' ).addClass( 'hidden' ); 269 } 270 271 function showMedia() { 272 $( '#add-kind-media' ).removeClass( 'hidden' ); 273 } 274 275 function hideMedia() { 276 $( '#add-kind-media' ).addClass( 'hidden' ); 277 } 278 279 280 function showTitle() { 281 var titlediv = $( '#titlediv' ).detach(); 282 titlediv.prependTo( '#post-body-content' ); 283 } 284 285 function hideTitle() { 286 var titlediv = $( '#titlediv' ).detach(); 287 titlediv.insertAfter( '#postdivrich' ); 288 } 289 290 function showRSVP() { 291 $( '#rsvp-option' ).removeClass( 'hide-if-js' ); 292 } 293 294 function hideRSVP() { 295 $( '#rsvp-option' ).addClass( 'hide-if-js' ); 296 } 297 298 function showRating() { 299 $( '#rating-option' ).removeClass( 'hide-if-js' ); 300 } 301 302 function hideRating() { 303 $( '#rating-option' ).addClass( 'hide-if-js' ); 304 } 305 306 function showTime() { 307 $( '#kind-time' ).removeClass( 'hide-if-js' ); 308 } 309 310 function hideTime() { 311 $( '#kind-time' ).addClass( 'hide-if-js' ); 312 } 313 314 function handleKindMediaWindow() { 315 'use strict'; 316 317 var KindWindow, ImageData, json; 318 319 /** 320 * If an instance of KindWindow already exists, then we can open it 321 * rather than creating a new instance. 322 */ 323 if ( undefined !== KindWindow ) { 324 KindWindow.open(); 325 return; 326 } 327 328 KindWindow = wp.media.frames.KindWindow = wp.media({ 329 title: 'Attach', 330 button: { 331 text: 'Use this media' 332 }, 333 multiple: false 334 }); 335 336 KindWindow.on( 'select', function() { 337 json = KindWindow.state().get( 'selection' ).first().toJSON(); 338 console.log( json ); 339 if ( 0 > $.trim( json.url.length ) ) { 340 return; 341 } 342 $( '#cite_name' ).val( json.title ); 343 $( '#cite_url' ).val( json.url ); 344 $( '#cite_media' ).val( json.id ); 345 $( '#cite_summary' ).val( json.description ); 346 $( '#kind-media-container' ) 347 .children( 'img' ) 348 .attr( 'src', json.url ) 349 .attr( 'alt', json.caption ) 350 .attr( 'title', json.title ) 351 .show() 352 .parent() 353 .removeClass( 'hidden' ); 354 355 $( '#add-kind-media' ).hide(); 356 }); 357 KindWindow.open(); 358 359 } 360 361 jQuery( document ) 362 .on( 'change', '#taxonomy-kind', function( event ) { 363 changeSettings(); 364 event.preventDefault(); 365 }) 366 .on( 'blur', '#cite_url', function( event ) { 367 if ( '' !== $( '#cite_url' ).val() ) { 368 if ( false == checkUrl( $( '#cite_url' ).val() ) ) { 369 alert( 'Invalid URL' ); 370 } else if ( '' === $( '#cite_name' ).val() ) { 371 showLoadingSpinner(); 372 getLinkPreview(); 373 } 374 event.preventDefault(); 375 } 376 }) 377 .on( 'click', '.clear-kindmeta-button', function( event ) { 378 clearPostProperties(); 379 event.preventDefault(); 380 }) 381 .on( 'click', 'a.show-kind-details', function( event ) { 382 if ( $( '#kind-details' ).is( ':hidden' ) ) { 383 $( '#kind-author' ).slideUp( 'fast' ).siblings( 'a.show-kind-author' ); 384 $( '#kind-details' ).slideDown( 'fast' ).siblings( 'a.hide-kind-details' ).show().focus(); 385 } else { 386 $( '#kind-details' ).slideUp( 'fast' ).siblings( 'a.show-kind-details' ).focus(); 387 } 388 event.preventDefault(); 389 }) 390 .on( 'click', 'a.show-kind-author-details', function( event ) { 391 if ( $( '#kind-author' ).is( ':hidden' ) ) { 392 $( '#kind-details' ).slideUp( 'fast' ).siblings( 'a.show-kind-details' ); 393 $( '#kind-author' ).slideDown( 'fast' ).siblings( 'a.hide-kind-author' ).show().focus(); 394 } else { 395 $( '#kind-author' ).slideUp( 'fast' ).siblings( 'a.show-kind-author' ).focus(); 396 } 397 event.preventDefault(); 398 }) 399 .on( 'click', '#add-kind-media', function( event ) { 400 event.preventDefault(); 401 handleKindMediaWindow(); 402 }); 403 }); 377 $( '#kind-details' ).slideUp( 'fast' ).siblings( 'a.show-kind-details' ).focus(); 378 } 379 event.preventDefault(); 380 } ) 381 .on( 'click', 'a.show-kind-author-details', function( event ) { 382 if ( $( '#kind-author' ).is( ':hidden' ) ) { 383 $( '#kind-details' ).slideUp( 'fast' ).siblings( 'a.show-kind-details' ); 384 $( '#kind-author' ).slideDown( 'fast' ).siblings( 'a.hide-kind-author' ).show().focus(); 385 } else { 386 $( '#kind-author' ).slideUp( 'fast' ).siblings( 'a.show-kind-author' ).focus(); 387 } 388 event.preventDefault(); 389 } ) 390 .on( 'click', '#add-kind-media', function( event ) { 391 event.preventDefault(); 392 handleKindMediaWindow(); 393 } ); 394 } ); -
indieweb-post-kinds/trunk/languages/indieweb-post-kinds.pot
r3067251 r3738687 1 # Copyright (C) 202 4David Shanske2 # This file is distributed under the same license as the Post Kinds plugin.1 # Copyright (C) 2026 David Shanske 2 # This file is distributed under the GPLv2 or later. 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: Post Kinds 3.7.3\n"5 "Project-Id-Version: Post Kinds 4.0.0\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/indieweb-post-kinds\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 202 4-04-09T03:10:32+00:00\n"12 "POT-Creation-Date: 2026-10-10T20:26:16+00:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 "X-Generator: WP-CLI 2.1 0.0\n"14 "X-Generator: WP-CLI 2.12.0\n" 15 15 "X-Domain: indieweb-post-kinds\n" 16 16 17 17 #. Plugin Name of the plugin 18 #: indieweb-post-kinds.php 19 #: includes/class-kind-config.php: 25120 #: includes/class-kind-config.php: 25221 #: includes/class-kind-config.php: 25818 #: indieweb-post-kinds.php:7 19 #: includes/class-kind-config.php:433 20 #: includes/class-kind-config.php:434 21 #: includes/class-kind-config.php:440 22 22 msgid "Post Kinds" 23 23 msgstr "" 24 24 25 25 #. Plugin URI of the plugin 26 #: indieweb-post-kinds.php 26 #: indieweb-post-kinds.php:8 27 27 msgid "https://wordpress.org/plugins/indieweb-post-kinds/" 28 28 msgstr "" 29 29 30 30 #. Description of the plugin 31 #: indieweb-post-kinds.php 31 #: indieweb-post-kinds.php:9 32 32 msgid "Ever want to reply to someone else's post with a post on your own site? Or to \"like\" someone else's post, but with your own site?" 33 33 msgstr "" 34 34 35 35 #. Author of the plugin 36 #: indieweb-post-kinds.php 36 #: indieweb-post-kinds.php:15 37 37 msgid "David Shanske" 38 38 msgstr "" 39 39 40 40 #. Author URI of the plugin 41 #: indieweb-post-kinds.php 41 #: indieweb-post-kinds.php:16 42 42 msgid "https://david.shanske.com" 43 43 msgstr "" 44 44 45 #: includes/class-kind-config.php:96 45 #: includes/class-kind-config.php:224 46 msgid "The allowed HTML must be a valid JSON object. The previous value has been kept." 47 msgstr "" 48 49 #: includes/class-kind-config.php:261 50 msgid "Kind base" 51 msgstr "" 52 53 #: includes/class-kind-config.php:269 46 54 msgid "Content Options" 47 55 msgstr "" 48 56 49 #: includes/class-kind-config.php: 10557 #: includes/class-kind-config.php:278 50 58 msgid "Select All Kinds You Wish to Use" 51 59 msgstr "" 52 60 53 #: includes/class-kind-config.php: 11261 #: includes/class-kind-config.php:285 54 62 msgid "Default Kind for New Posts" 55 63 msgstr "" 56 64 57 #: includes/class-kind-config.php: 12065 #: includes/class-kind-config.php:293 58 66 msgid "Embed Sites into your Response" 59 67 msgstr "" 60 68 61 #: includes/class-kind-config.php: 13169 #: includes/class-kind-config.php:304 62 70 msgid "Kinds to Show in Main Feed. Selecting None will Disable this Filter" 63 71 msgstr "" 64 72 65 #: includes/class-kind-config.php: 14273 #: includes/class-kind-config.php:315 66 74 msgid "Move Response to Bottom of Content" 67 75 msgstr "" 68 76 69 #: includes/class-kind-config.php: 15377 #: includes/class-kind-config.php:326 70 78 msgid "Display Before Kind" 71 79 msgstr "" 72 80 73 #: includes/class-kind-config.php: 16181 #: includes/class-kind-config.php:334 74 82 msgid "Show Icon" 75 83 msgstr "" 76 84 77 #: includes/class-kind-config.php: 16285 #: includes/class-kind-config.php:335 78 86 msgid "Show Text" 79 87 msgstr "" 80 88 81 #: includes/class-kind-config.php: 16389 #: includes/class-kind-config.php:336 82 90 msgid "Show Icon and Text" 83 91 msgstr "" 84 92 85 #: includes/class-kind-config.php: 16493 #: includes/class-kind-config.php:337 86 94 msgid "Display Nothing" 87 95 msgstr "" 88 96 89 #: includes/class-kind-config.php: 17297 #: includes/class-kind-config.php:345 90 98 msgid "Response Content Allowed Html Elements" 91 99 msgstr "" 92 100 93 #: includes/class-kind-config.php: 183101 #: includes/class-kind-config.php:356 94 102 msgid "Automatically add the Kind to the Title" 95 103 msgstr "" 96 104 97 #: includes/class-kind-config.php: 391105 #: includes/class-kind-config.php:593 98 106 msgid "Indieweb Post Kinds" 99 107 msgstr "" 100 108 101 #: includes/class-kind-config.php: 392109 #: includes/class-kind-config.php:594 102 110 msgid "Adds support for responding and interacting with other sites." 103 111 msgstr "" 104 112 105 #: includes/class-kind-config.php: 415113 #: includes/class-kind-config.php:619 106 114 msgid "Post Properties" 107 115 msgstr "" 108 116 109 #: includes/class-kind-config.php: 416117 #: includes/class-kind-config.php:620 110 118 msgid "" 111 119 "\n" … … 139 147 msgstr "" 140 148 141 #: includes/class-kind-fields.php: 27149 #: includes/class-kind-fields.php:39 142 150 msgid "(GMT-11:00) Niue" 143 151 msgstr "" 144 152 145 #: includes/class-kind-fields.php: 28153 #: includes/class-kind-fields.php:40 146 154 msgid "(GMT-11:00) Pago Pago" 147 155 msgstr "" 148 156 149 #: includes/class-kind-fields.php: 29157 #: includes/class-kind-fields.php:41 150 158 msgid "(GMT-10:00) Hawaii Time" 151 159 msgstr "" 152 160 153 #: includes/class-kind-fields.php: 30161 #: includes/class-kind-fields.php:42 154 162 msgid "(GMT-10:00) Rarotonga" 155 163 msgstr "" 156 164 157 #: includes/class-kind-fields.php: 31165 #: includes/class-kind-fields.php:43 158 166 msgid "(GMT-10:00) Tahiti" 159 167 msgstr "" 160 168 161 #: includes/class-kind-fields.php: 32169 #: includes/class-kind-fields.php:44 162 170 msgid "(GMT-09:30) Marquesas" 163 171 msgstr "" 164 172 165 #: includes/class-kind-fields.php: 33173 #: includes/class-kind-fields.php:45 166 174 msgid "(GMT-09:00) Alaska Time" 167 175 msgstr "" 168 176 169 #: includes/class-kind-fields.php: 34177 #: includes/class-kind-fields.php:46 170 178 msgid "(GMT-09:00) Gambier" 171 179 msgstr "" 172 180 173 #: includes/class-kind-fields.php: 35181 #: includes/class-kind-fields.php:47 174 182 msgid "(GMT-08:00) Pacific Time" 175 183 msgstr "" 176 184 177 #: includes/class-kind-fields.php: 36185 #: includes/class-kind-fields.php:48 178 186 msgid "(GMT-08:00) Pacific Time - Tijuana" 179 187 msgstr "" 180 188 181 #: includes/class-kind-fields.php: 37189 #: includes/class-kind-fields.php:49 182 190 msgid "(GMT-08:00) Pacific Time - Vancouver" 183 191 msgstr "" 184 192 185 #: includes/class-kind-fields.php: 38193 #: includes/class-kind-fields.php:50 186 194 msgid "(GMT-08:00) Pacific Time - Whitehorse" 187 195 msgstr "" 188 196 189 #: includes/class-kind-fields.php: 39197 #: includes/class-kind-fields.php:51 190 198 msgid "(GMT-08:00) Pitcairn" 191 199 msgstr "" 192 200 193 #: includes/class-kind-fields.php: 40201 #: includes/class-kind-fields.php:52 194 202 msgid "(GMT-07:00) Mountain Time - Dawson Creek" 195 203 msgstr "" 196 204 197 #: includes/class-kind-fields.php: 41205 #: includes/class-kind-fields.php:53 198 206 msgid "(GMT-07:00) Mountain Time" 199 207 msgstr "" 200 208 201 #: includes/class-kind-fields.php: 42209 #: includes/class-kind-fields.php:54 202 210 msgid "(GMT-07:00) Mountain Time - Edmonton" 203 211 msgstr "" 204 212 205 #: includes/class-kind-fields.php: 43213 #: includes/class-kind-fields.php:55 206 214 msgid "(GMT-07:00) Mountain Time - Hermosillo" 207 215 msgstr "" 208 216 209 #: includes/class-kind-fields.php: 44217 #: includes/class-kind-fields.php:56 210 218 msgid "(GMT-07:00) Mountain Time - Chihuahua, Mazatlan" 211 219 msgstr "" 212 220 213 #: includes/class-kind-fields.php: 45221 #: includes/class-kind-fields.php:57 214 222 msgid "(GMT-07:00) Mountain Time - Arizona" 215 223 msgstr "" 216 224 217 #: includes/class-kind-fields.php: 46225 #: includes/class-kind-fields.php:58 218 226 msgid "(GMT-07:00) Mountain Time - Yellowknife" 219 227 msgstr "" 220 228 221 #: includes/class-kind-fields.php: 47229 #: includes/class-kind-fields.php:59 222 230 msgid "(GMT-06:00) Belize" 223 231 msgstr "" 224 232 225 #: includes/class-kind-fields.php: 48233 #: includes/class-kind-fields.php:60 226 234 msgid "(GMT-06:00) Central Time" 227 235 msgstr "" 228 236 229 #: includes/class-kind-fields.php: 49237 #: includes/class-kind-fields.php:61 230 238 msgid "(GMT-06:00) Costa Rica" 231 239 msgstr "" 232 240 233 #: includes/class-kind-fields.php: 50241 #: includes/class-kind-fields.php:62 234 242 msgid "(GMT-06:00) El Salvador" 235 243 msgstr "" 236 244 237 #: includes/class-kind-fields.php: 51245 #: includes/class-kind-fields.php:63 238 246 msgid "(GMT-06:00) Guatemala" 239 247 msgstr "" 240 248 241 #: includes/class-kind-fields.php: 52249 #: includes/class-kind-fields.php:64 242 250 msgid "(GMT-06:00) Managua" 243 251 msgstr "" 244 252 245 #: includes/class-kind-fields.php: 53253 #: includes/class-kind-fields.php:65 246 254 msgid "(GMT-06:00) Central Time - Mexico City" 247 255 msgstr "" 248 256 249 #: includes/class-kind-fields.php: 54257 #: includes/class-kind-fields.php:66 250 258 msgid "(GMT-06:00) Central Time - Regina" 251 259 msgstr "" 252 260 253 #: includes/class-kind-fields.php: 55261 #: includes/class-kind-fields.php:67 254 262 msgid "(GMT-06:00) Central Time - Tegucigalpa" 255 263 msgstr "" 256 264 257 #: includes/class-kind-fields.php: 56265 #: includes/class-kind-fields.php:68 258 266 msgid "(GMT-06:00) Central Time - Winnipeg" 259 267 msgstr "" 260 268 261 #: includes/class-kind-fields.php: 57269 #: includes/class-kind-fields.php:69 262 270 msgid "(GMT-06:00) Galapagos" 263 271 msgstr "" 264 272 265 #: includes/class-kind-fields.php: 58273 #: includes/class-kind-fields.php:70 266 274 msgid "(GMT-05:00) Bogota" 267 275 msgstr "" 268 276 269 #: includes/class-kind-fields.php: 59277 #: includes/class-kind-fields.php:71 270 278 msgid "(GMT-05:00) America Cancun" 271 279 msgstr "" 272 280 273 #: includes/class-kind-fields.php: 60281 #: includes/class-kind-fields.php:72 274 282 msgid "(GMT-05:00) Cayman" 275 283 msgstr "" 276 284 277 #: includes/class-kind-fields.php: 61285 #: includes/class-kind-fields.php:73 278 286 msgid "(GMT-05:00) Guayaquil" 279 287 msgstr "" 280 288 281 #: includes/class-kind-fields.php: 62289 #: includes/class-kind-fields.php:74 282 290 msgid "(GMT-05:00) Havana" 283 291 msgstr "" 284 292 285 #: includes/class-kind-fields.php: 63293 #: includes/class-kind-fields.php:75 286 294 msgid "(GMT-05:00) Eastern Time - Iqaluit" 287 295 msgstr "" 288 296 289 #: includes/class-kind-fields.php: 64297 #: includes/class-kind-fields.php:76 290 298 msgid "(GMT-05:00) Jamaica" 291 299 msgstr "" 292 300 293 #: includes/class-kind-fields.php: 65301 #: includes/class-kind-fields.php:77 294 302 msgid "(GMT-05:00) Lima" 295 303 msgstr "" 296 304 297 #: includes/class-kind-fields.php: 66305 #: includes/class-kind-fields.php:78 298 306 msgid "(GMT-05:00) Nassau" 299 307 msgstr "" 300 308 301 #: includes/class-kind-fields.php: 67309 #: includes/class-kind-fields.php:79 302 310 msgid "(GMT-05:00) Eastern Time" 303 311 msgstr "" 304 312 305 #: includes/class-kind-fields.php: 68313 #: includes/class-kind-fields.php:80 306 314 msgid "(GMT-05:00) Panama" 307 315 msgstr "" 308 316 309 #: includes/class-kind-fields.php: 69317 #: includes/class-kind-fields.php:81 310 318 msgid "(GMT-05:00) Port-au-Prince" 311 319 msgstr "" 312 320 313 #: includes/class-kind-fields.php: 70321 #: includes/class-kind-fields.php:82 314 322 msgid "(GMT-05:00) Rio Branco" 315 323 msgstr "" 316 324 317 #: includes/class-kind-fields.php: 71325 #: includes/class-kind-fields.php:83 318 326 msgid "(GMT-05:00) Eastern Time - Toronto" 319 327 msgstr "" 320 328 321 #: includes/class-kind-fields.php: 72329 #: includes/class-kind-fields.php:84 322 330 msgid "(GMT-05:00) Easter Island" 323 331 msgstr "" 324 332 325 #: includes/class-kind-fields.php: 73333 #: includes/class-kind-fields.php:85 326 334 msgid "(GMT-04:30) Caracas" 327 335 msgstr "" 328 336 329 #: includes/class-kind-fields.php: 74337 #: includes/class-kind-fields.php:86 330 338 msgid "(GMT-03:00) Asuncion" 331 339 msgstr "" 332 340 333 #: includes/class-kind-fields.php: 75341 #: includes/class-kind-fields.php:87 334 342 msgid "(GMT-04:00) Barbados" 335 343 msgstr "" 336 344 337 #: includes/class-kind-fields.php: 76345 #: includes/class-kind-fields.php:88 338 346 msgid "(GMT-04:00) Boa Vista" 339 347 msgstr "" 340 348 341 #: includes/class-kind-fields.php: 77349 #: includes/class-kind-fields.php:89 342 350 msgid "(GMT-03:00) Campo Grande" 343 351 msgstr "" 344 352 345 #: includes/class-kind-fields.php: 78353 #: includes/class-kind-fields.php:90 346 354 msgid "(GMT-03:00) Cuiaba" 347 355 msgstr "" 348 356 349 #: includes/class-kind-fields.php: 79357 #: includes/class-kind-fields.php:91 350 358 msgid "(GMT-04:00) Curacao" 351 359 msgstr "" 352 360 353 #: includes/class-kind-fields.php: 80361 #: includes/class-kind-fields.php:92 354 362 msgid "(GMT-04:00) Grand Turk" 355 363 msgstr "" 356 364 357 #: includes/class-kind-fields.php: 81365 #: includes/class-kind-fields.php:93 358 366 msgid "(GMT-04:00) Guyana" 359 367 msgstr "" 360 368 361 #: includes/class-kind-fields.php: 82369 #: includes/class-kind-fields.php:94 362 370 msgid "(GMT-04:00) Atlantic Time - Halifax" 363 371 msgstr "" 364 372 365 #: includes/class-kind-fields.php: 83373 #: includes/class-kind-fields.php:95 366 374 msgid "(GMT-04:00) La Paz" 367 375 msgstr "" 368 376 369 #: includes/class-kind-fields.php: 84377 #: includes/class-kind-fields.php:96 370 378 msgid "(GMT-04:00) Manaus" 371 379 msgstr "" 372 380 373 #: includes/class-kind-fields.php: 85381 #: includes/class-kind-fields.php:97 374 382 msgid "(GMT-04:00) Martinique" 375 383 msgstr "" 376 384 377 #: includes/class-kind-fields.php: 86385 #: includes/class-kind-fields.php:98 378 386 msgid "(GMT-04:00) Port of Spain" 379 387 msgstr "" 380 388 381 #: includes/class-kind-fields.php: 87389 #: includes/class-kind-fields.php:99 382 390 msgid "(GMT-04:00) Porto Velho" 383 391 msgstr "" 384 392 385 #: includes/class-kind-fields.php: 88393 #: includes/class-kind-fields.php:100 386 394 msgid "(GMT-04:00) Puerto Rico" 387 395 msgstr "" 388 396 389 #: includes/class-kind-fields.php: 89397 #: includes/class-kind-fields.php:101 390 398 msgid "(GMT-04:00) Santo Domingo" 391 399 msgstr "" 392 400 393 #: includes/class-kind-fields.php: 90401 #: includes/class-kind-fields.php:102 394 402 msgid "(GMT-04:00) Thule" 395 403 msgstr "" 396 404 397 #: includes/class-kind-fields.php: 91405 #: includes/class-kind-fields.php:103 398 406 msgid "(GMT-04:00) Bermuda" 399 407 msgstr "" 400 408 401 #: includes/class-kind-fields.php: 92409 #: includes/class-kind-fields.php:104 402 410 msgid "(GMT-03:30) Newfoundland Time - St. Johns" 403 411 msgstr "" 404 412 405 #: includes/class-kind-fields.php: 93413 #: includes/class-kind-fields.php:105 406 414 msgid "(GMT-03:00) Araguaina" 407 415 msgstr "" 408 416 409 #: includes/class-kind-fields.php: 94417 #: includes/class-kind-fields.php:106 410 418 msgid "(GMT-03:00) Buenos Aires" 411 419 msgstr "" 412 420 413 #: includes/class-kind-fields.php: 95421 #: includes/class-kind-fields.php:107 414 422 msgid "(GMT-03:00) Salvador" 415 423 msgstr "" 416 424 417 #: includes/class-kind-fields.php: 96425 #: includes/class-kind-fields.php:108 418 426 msgid "(GMT-03:00) Belem" 419 427 msgstr "" 420 428 421 #: includes/class-kind-fields.php: 97429 #: includes/class-kind-fields.php:109 422 430 msgid "(GMT-03:00) Cayenne" 423 431 msgstr "" 424 432 425 #: includes/class-kind-fields.php: 98433 #: includes/class-kind-fields.php:110 426 434 msgid "(GMT-03:00) Fortaleza" 427 435 msgstr "" 428 436 429 #: includes/class-kind-fields.php: 99437 #: includes/class-kind-fields.php:111 430 438 msgid "(GMT-03:00) Godthab" 431 439 msgstr "" 432 440 433 #: includes/class-kind-fields.php:1 00441 #: includes/class-kind-fields.php:112 434 442 msgid "(GMT-03:00) Maceio" 435 443 msgstr "" 436 444 437 #: includes/class-kind-fields.php:1 01445 #: includes/class-kind-fields.php:113 438 446 msgid "(GMT-03:00) Miquelon" 439 447 msgstr "" 440 448 441 #: includes/class-kind-fields.php:1 02449 #: includes/class-kind-fields.php:114 442 450 msgid "(GMT-03:00) Montevideo" 443 451 msgstr "" 444 452 445 #: includes/class-kind-fields.php:1 03453 #: includes/class-kind-fields.php:115 446 454 msgid "(GMT-03:00) Paramaribo" 447 455 msgstr "" 448 456 449 #: includes/class-kind-fields.php:1 04457 #: includes/class-kind-fields.php:116 450 458 msgid "(GMT-03:00) Recife" 451 459 msgstr "" 452 460 453 #: includes/class-kind-fields.php:1 05461 #: includes/class-kind-fields.php:117 454 462 msgid "(GMT-03:00) Santiago" 455 463 msgstr "" 456 464 457 #: includes/class-kind-fields.php:1 06465 #: includes/class-kind-fields.php:118 458 466 msgid "(GMT-02:00) Sao Paulo" 459 467 msgstr "" 460 468 461 #: includes/class-kind-fields.php:1 07469 #: includes/class-kind-fields.php:119 462 470 msgid "(GMT-03:00) Palmer" 463 471 msgstr "" 464 472 465 #: includes/class-kind-fields.php:1 08473 #: includes/class-kind-fields.php:120 466 474 msgid "(GMT-03:00) Rothera" 467 475 msgstr "" 468 476 469 #: includes/class-kind-fields.php:1 09477 #: includes/class-kind-fields.php:121 470 478 msgid "(GMT-03:00) Stanley" 471 479 msgstr "" 472 480 473 #: includes/class-kind-fields.php:1 10481 #: includes/class-kind-fields.php:122 474 482 msgid "(GMT-02:00) Noronha" 475 483 msgstr "" 476 484 477 #: includes/class-kind-fields.php:1 11485 #: includes/class-kind-fields.php:123 478 486 msgid "(GMT-02:00) South Georgia" 479 487 msgstr "" 480 488 481 #: includes/class-kind-fields.php:1 12489 #: includes/class-kind-fields.php:124 482 490 msgid "(GMT-01:00) Scoresbysund" 483 491 msgstr "" 484 492 485 #: includes/class-kind-fields.php:1 13493 #: includes/class-kind-fields.php:125 486 494 msgid "(GMT-01:00) Azores" 487 495 msgstr "" 488 496 489 #: includes/class-kind-fields.php:1 14497 #: includes/class-kind-fields.php:126 490 498 msgid "(GMT-01:00) Cape Verde" 491 499 msgstr "" 492 500 493 #: includes/class-kind-fields.php:1 15501 #: includes/class-kind-fields.php:127 494 502 msgid "(GMT+00:00) Abidjan" 495 503 msgstr "" 496 504 497 #: includes/class-kind-fields.php:1 16505 #: includes/class-kind-fields.php:128 498 506 msgid "(GMT+00:00) Accra" 499 507 msgstr "" 500 508 501 #: includes/class-kind-fields.php:1 17509 #: includes/class-kind-fields.php:129 502 510 msgid "(GMT+00:00) Bissau" 503 511 msgstr "" 504 512 505 #: includes/class-kind-fields.php:1 18513 #: includes/class-kind-fields.php:130 506 514 msgid "(GMT+00:00) Casablanca" 507 515 msgstr "" 508 516 509 #: includes/class-kind-fields.php:1 19517 #: includes/class-kind-fields.php:131 510 518 msgid "(GMT+00:00) El Aaiun" 511 519 msgstr "" 512 520 513 #: includes/class-kind-fields.php:1 20521 #: includes/class-kind-fields.php:132 514 522 msgid "(GMT+00:00) Monrovia" 515 523 msgstr "" 516 524 517 #: includes/class-kind-fields.php:1 21525 #: includes/class-kind-fields.php:133 518 526 msgid "(GMT+00:00) Danmarkshavn" 519 527 msgstr "" 520 528 521 #: includes/class-kind-fields.php:1 22529 #: includes/class-kind-fields.php:134 522 530 msgid "(GMT+00:00) Canary Islands" 523 531 msgstr "" 524 532 525 #: includes/class-kind-fields.php:1 23533 #: includes/class-kind-fields.php:135 526 534 msgid "(GMT+00:00) Faeroe" 527 535 msgstr "" 528 536 529 #: includes/class-kind-fields.php:1 24537 #: includes/class-kind-fields.php:136 530 538 msgid "(GMT+00:00) Reykjavik" 531 539 msgstr "" 532 540 533 #: includes/class-kind-fields.php:1 25541 #: includes/class-kind-fields.php:137 534 542 msgid "(GMT+00:00) GMT (no daylight saving)" 535 543 msgstr "" 536 544 537 #: includes/class-kind-fields.php:1 26545 #: includes/class-kind-fields.php:138 538 546 msgid "(GMT+00:00) Dublin" 539 547 msgstr "" 540 548 541 #: includes/class-kind-fields.php:1 27549 #: includes/class-kind-fields.php:139 542 550 msgid "(GMT+00:00) Lisbon" 543 551 msgstr "" 544 552 545 #: includes/class-kind-fields.php:1 28553 #: includes/class-kind-fields.php:140 546 554 msgid "(GMT+00:00) London" 547 555 msgstr "" 548 556 549 #: includes/class-kind-fields.php:1 29557 #: includes/class-kind-fields.php:141 550 558 msgid "(GMT+01:00) Algiers" 551 559 msgstr "" 552 560 553 #: includes/class-kind-fields.php:1 30561 #: includes/class-kind-fields.php:142 554 562 msgid "(GMT+01:00) Ceuta" 555 563 msgstr "" 556 564 557 #: includes/class-kind-fields.php:1 31565 #: includes/class-kind-fields.php:143 558 566 msgid "(GMT+01:00) Lagos" 559 567 msgstr "" 560 568 561 #: includes/class-kind-fields.php:1 32569 #: includes/class-kind-fields.php:144 562 570 msgid "(GMT+01:00) Ndjamena" 563 571 msgstr "" 564 572 565 #: includes/class-kind-fields.php:1 33573 #: includes/class-kind-fields.php:145 566 574 msgid "(GMT+01:00) Tunis" 567 575 msgstr "" 568 576 569 #: includes/class-kind-fields.php:1 34577 #: includes/class-kind-fields.php:146 570 578 msgid "(GMT+02:00) Windhoek" 571 579 msgstr "" 572 580 573 #: includes/class-kind-fields.php:1 35581 #: includes/class-kind-fields.php:147 574 582 msgid "(GMT+01:00) Amsterdam" 575 583 msgstr "" 576 584 577 #: includes/class-kind-fields.php:1 36585 #: includes/class-kind-fields.php:148 578 586 msgid "(GMT+01:00) Andorra" 579 587 msgstr "" 580 588 581 #: includes/class-kind-fields.php:1 37589 #: includes/class-kind-fields.php:149 582 590 msgid "(GMT+01:00) Central European Time - Belgrade" 583 591 msgstr "" 584 592 585 #: includes/class-kind-fields.php:1 38593 #: includes/class-kind-fields.php:150 586 594 msgid "(GMT+01:00) Berlin" 587 595 msgstr "" 588 596 589 #: includes/class-kind-fields.php:1 39597 #: includes/class-kind-fields.php:151 590 598 msgid "(GMT+01:00) Brussels" 591 599 msgstr "" 592 600 593 #: includes/class-kind-fields.php:1 40601 #: includes/class-kind-fields.php:152 594 602 msgid "(GMT+01:00) Budapest" 595 603 msgstr "" 596 604 597 #: includes/class-kind-fields.php:1 41605 #: includes/class-kind-fields.php:153 598 606 msgid "(GMT+01:00) Copenhagen" 599 607 msgstr "" 600 608 601 #: includes/class-kind-fields.php:1 42609 #: includes/class-kind-fields.php:154 602 610 msgid "(GMT+01:00) Gibraltar" 603 611 msgstr "" 604 612 605 #: includes/class-kind-fields.php:1 43613 #: includes/class-kind-fields.php:155 606 614 msgid "(GMT+01:00) Luxembourg" 607 615 msgstr "" 608 616 609 #: includes/class-kind-fields.php:1 44617 #: includes/class-kind-fields.php:156 610 618 msgid "(GMT+01:00) Madrid" 611 619 msgstr "" 612 620 613 #: includes/class-kind-fields.php:1 45621 #: includes/class-kind-fields.php:157 614 622 msgid "(GMT+01:00) Malta" 615 623 msgstr "" 616 624 617 #: includes/class-kind-fields.php:1 46625 #: includes/class-kind-fields.php:158 618 626 msgid "(GMT+01:00) Monaco" 619 627 msgstr "" 620 628 621 #: includes/class-kind-fields.php:1 47629 #: includes/class-kind-fields.php:159 622 630 msgid "(GMT+01:00) Oslo" 623 631 msgstr "" 624 632 625 #: includes/class-kind-fields.php:1 48633 #: includes/class-kind-fields.php:160 626 634 msgid "(GMT+01:00) Paris" 627 635 msgstr "" 628 636 629 #: includes/class-kind-fields.php:1 49637 #: includes/class-kind-fields.php:161 630 638 msgid "(GMT+01:00) Central European Time - Prague" 631 639 msgstr "" 632 640 633 #: includes/class-kind-fields.php:1 50641 #: includes/class-kind-fields.php:162 634 642 msgid "(GMT+01:00) Rome" 635 643 msgstr "" 636 644 637 #: includes/class-kind-fields.php:1 51645 #: includes/class-kind-fields.php:163 638 646 msgid "(GMT+01:00) Stockholm" 639 647 msgstr "" 640 648 641 #: includes/class-kind-fields.php:1 52649 #: includes/class-kind-fields.php:164 642 650 msgid "(GMT+01:00) Tirane" 643 651 msgstr "" 644 652 645 #: includes/class-kind-fields.php:1 53653 #: includes/class-kind-fields.php:165 646 654 msgid "(GMT+01:00) Vienna" 647 655 msgstr "" 648 656 649 #: includes/class-kind-fields.php:1 54657 #: includes/class-kind-fields.php:166 650 658 msgid "(GMT+01:00) Warsaw" 651 659 msgstr "" 652 660 653 #: includes/class-kind-fields.php:1 55661 #: includes/class-kind-fields.php:167 654 662 msgid "(GMT+01:00) Zurich" 655 663 msgstr "" 656 664 657 #: includes/class-kind-fields.php:1 56665 #: includes/class-kind-fields.php:168 658 666 msgid "(GMT+02:00) Cairo" 659 667 msgstr "" 660 668 661 #: includes/class-kind-fields.php:1 57669 #: includes/class-kind-fields.php:169 662 670 msgid "(GMT+02:00) Johannesburg" 663 671 msgstr "" 664 672 665 #: includes/class-kind-fields.php:1 58673 #: includes/class-kind-fields.php:170 666 674 msgid "(GMT+02:00) Maputo" 667 675 msgstr "" 668 676 669 #: includes/class-kind-fields.php:1 59677 #: includes/class-kind-fields.php:171 670 678 msgid "(GMT+02:00) Tripoli" 671 679 msgstr "" 672 680 673 #: includes/class-kind-fields.php:1 60681 #: includes/class-kind-fields.php:172 674 682 msgid "(GMT+02:00) Amman" 675 683 msgstr "" 676 684 677 #: includes/class-kind-fields.php:1 61685 #: includes/class-kind-fields.php:173 678 686 msgid "(GMT+02:00) Beirut" 679 687 msgstr "" 680 688 681 #: includes/class-kind-fields.php:1 62689 #: includes/class-kind-fields.php:174 682 690 msgid "(GMT+02:00) Damascus" 683 691 msgstr "" 684 692 685 #: includes/class-kind-fields.php:1 63693 #: includes/class-kind-fields.php:175 686 694 msgid "(GMT+02:00) Gaza" 687 695 msgstr "" 688 696 689 #: includes/class-kind-fields.php:1 64697 #: includes/class-kind-fields.php:176 690 698 msgid "(GMT+02:00) Jerusalem" 691 699 msgstr "" 692 700 693 #: includes/class-kind-fields.php:1 65701 #: includes/class-kind-fields.php:177 694 702 msgid "(GMT+02:00) Nicosia" 695 703 msgstr "" 696 704 697 #: includes/class-kind-fields.php:1 66705 #: includes/class-kind-fields.php:178 698 706 msgid "(GMT+02:00) Athens" 699 707 msgstr "" 700 708 701 #: includes/class-kind-fields.php:1 67709 #: includes/class-kind-fields.php:179 702 710 msgid "(GMT+02:00) Bucharest" 703 711 msgstr "" 704 712 705 #: includes/class-kind-fields.php:1 68713 #: includes/class-kind-fields.php:180 706 714 msgid "(GMT+02:00) Chisinau" 707 715 msgstr "" 708 716 709 #: includes/class-kind-fields.php:1 69717 #: includes/class-kind-fields.php:181 710 718 msgid "(GMT+02:00) Helsinki" 711 719 msgstr "" 712 720 713 #: includes/class-kind-fields.php:1 70721 #: includes/class-kind-fields.php:182 714 722 msgid "(GMT+02:00) Istanbul" 715 723 msgstr "" 716 724 717 #: includes/class-kind-fields.php:1 71725 #: includes/class-kind-fields.php:183 718 726 msgid "(GMT+02:00) Moscow-01 - Kaliningrad" 719 727 msgstr "" 720 728 721 #: includes/class-kind-fields.php:1 72729 #: includes/class-kind-fields.php:184 722 730 msgid "(GMT+02:00) Kiev" 723 731 msgstr "" 724 732 725 #: includes/class-kind-fields.php:1 73733 #: includes/class-kind-fields.php:185 726 734 msgid "(GMT+02:00) Riga" 727 735 msgstr "" 728 736 729 #: includes/class-kind-fields.php:1 74737 #: includes/class-kind-fields.php:186 730 738 msgid "(GMT+02:00) Sofia" 731 739 msgstr "" 732 740 733 #: includes/class-kind-fields.php:1 75741 #: includes/class-kind-fields.php:187 734 742 msgid "(GMT+02:00) Tallinn" 735 743 msgstr "" 736 744 737 #: includes/class-kind-fields.php:1 76745 #: includes/class-kind-fields.php:188 738 746 msgid "(GMT+02:00) Vilnius" 739 747 msgstr "" 740 748 741 #: includes/class-kind-fields.php:1 77749 #: includes/class-kind-fields.php:189 742 750 msgid "(GMT+03:00) Khartoum" 743 751 msgstr "" 744 752 745 #: includes/class-kind-fields.php:1 78753 #: includes/class-kind-fields.php:190 746 754 msgid "(GMT+03:00) Nairobi" 747 755 msgstr "" 748 756 749 #: includes/class-kind-fields.php:1 79757 #: includes/class-kind-fields.php:191 750 758 msgid "(GMT+03:00) Syowa" 751 759 msgstr "" 752 760 753 #: includes/class-kind-fields.php:1 80761 #: includes/class-kind-fields.php:192 754 762 msgid "(GMT+03:00) Baghdad" 755 763 msgstr "" 756 764 757 #: includes/class-kind-fields.php:1 81765 #: includes/class-kind-fields.php:193 758 766 msgid "(GMT+03:00) Qatar" 759 767 msgstr "" 760 768 761 #: includes/class-kind-fields.php:1 82769 #: includes/class-kind-fields.php:194 762 770 msgid "(GMT+03:00) Riyadh" 763 771 msgstr "" 764 772 765 #: includes/class-kind-fields.php:1 83773 #: includes/class-kind-fields.php:195 766 774 msgid "(GMT+03:00) Minsk" 767 775 msgstr "" 768 776 769 #: includes/class-kind-fields.php:1 84777 #: includes/class-kind-fields.php:196 770 778 msgid "(GMT+03:00) Moscow+00 - Moscow" 771 779 msgstr "" 772 780 773 #: includes/class-kind-fields.php:1 85781 #: includes/class-kind-fields.php:197 774 782 msgid "(GMT+03:30) Tehran" 775 783 msgstr "" 776 784 777 #: includes/class-kind-fields.php:1 86785 #: includes/class-kind-fields.php:198 778 786 msgid "(GMT+04:00) Baku" 779 787 msgstr "" 780 788 781 #: includes/class-kind-fields.php:1 87789 #: includes/class-kind-fields.php:199 782 790 msgid "(GMT+04:00) Dubai" 783 791 msgstr "" 784 792 785 #: includes/class-kind-fields.php: 188793 #: includes/class-kind-fields.php:200 786 794 msgid "(GMT+04:00) Tbilisi" 787 795 msgstr "" 788 796 789 #: includes/class-kind-fields.php: 189797 #: includes/class-kind-fields.php:201 790 798 msgid "(GMT+04:00) Yerevan" 791 799 msgstr "" 792 800 793 #: includes/class-kind-fields.php: 190801 #: includes/class-kind-fields.php:202 794 802 msgid "(GMT+04:00) Moscow+01 - Samara" 795 803 msgstr "" 796 804 797 #: includes/class-kind-fields.php: 191805 #: includes/class-kind-fields.php:203 798 806 msgid "(GMT+04:00) Mahe" 799 807 msgstr "" 800 808 801 #: includes/class-kind-fields.php: 192809 #: includes/class-kind-fields.php:204 802 810 msgid "(GMT+04:00) Mauritius" 803 811 msgstr "" 804 812 805 #: includes/class-kind-fields.php: 193813 #: includes/class-kind-fields.php:205 806 814 msgid "(GMT+04:00) Reunion" 807 815 msgstr "" 808 816 809 #: includes/class-kind-fields.php: 194817 #: includes/class-kind-fields.php:206 810 818 msgid "(GMT+04:30) Kabul" 811 819 msgstr "" 812 820 813 #: includes/class-kind-fields.php: 195821 #: includes/class-kind-fields.php:207 814 822 msgid "(GMT+05:00) Mawson" 815 823 msgstr "" 816 824 817 #: includes/class-kind-fields.php: 196825 #: includes/class-kind-fields.php:208 818 826 msgid "(GMT+05:00) Aqtau" 819 827 msgstr "" 820 828 821 #: includes/class-kind-fields.php: 197829 #: includes/class-kind-fields.php:209 822 830 msgid "(GMT+05:00) Aqtobe" 823 831 msgstr "" 824 832 825 #: includes/class-kind-fields.php: 198833 #: includes/class-kind-fields.php:210 826 834 msgid "(GMT+05:00) Ashgabat" 827 835 msgstr "" 828 836 829 #: includes/class-kind-fields.php: 199837 #: includes/class-kind-fields.php:211 830 838 msgid "(GMT+05:00) Dushanbe" 831 839 msgstr "" 832 840 833 #: includes/class-kind-fields.php:2 00841 #: includes/class-kind-fields.php:212 834 842 msgid "(GMT+05:00) Karachi" 835 843 msgstr "" 836 844 837 #: includes/class-kind-fields.php:2 01845 #: includes/class-kind-fields.php:213 838 846 msgid "(GMT+05:00) Tashkent" 839 847 msgstr "" 840 848 841 #: includes/class-kind-fields.php:2 02849 #: includes/class-kind-fields.php:214 842 850 msgid "(GMT+05:00) Moscow+02 - Yekaterinburg" 843 851 msgstr "" 844 852 845 #: includes/class-kind-fields.php:2 03853 #: includes/class-kind-fields.php:215 846 854 msgid "(GMT+05:00) Kerguelen" 847 855 msgstr "" 848 856 849 #: includes/class-kind-fields.php:2 04857 #: includes/class-kind-fields.php:216 850 858 msgid "(GMT+05:00) Maldives" 851 859 msgstr "" 852 860 853 #: includes/class-kind-fields.php:2 05861 #: includes/class-kind-fields.php:217 854 862 msgid "(GMT+05:30) India Standard Time" 855 863 msgstr "" 856 864 857 #: includes/class-kind-fields.php:2 06865 #: includes/class-kind-fields.php:218 858 866 msgid "(GMT+05:30) Colombo" 859 867 msgstr "" 860 868 861 #: includes/class-kind-fields.php:2 07869 #: includes/class-kind-fields.php:219 862 870 msgid "(GMT+05:45) Katmandu" 863 871 msgstr "" 864 872 865 #: includes/class-kind-fields.php:2 08873 #: includes/class-kind-fields.php:220 866 874 msgid "(GMT+06:00) Vostok" 867 875 msgstr "" 868 876 869 #: includes/class-kind-fields.php:2 09877 #: includes/class-kind-fields.php:221 870 878 msgid "(GMT+06:00) Almaty" 871 879 msgstr "" 872 880 873 #: includes/class-kind-fields.php:2 10881 #: includes/class-kind-fields.php:222 874 882 msgid "(GMT+06:00) Bishkek" 875 883 msgstr "" 876 884 877 #: includes/class-kind-fields.php:2 11885 #: includes/class-kind-fields.php:223 878 886 msgid "(GMT+06:00) Dhaka" 879 887 msgstr "" 880 888 881 #: includes/class-kind-fields.php:2 12889 #: includes/class-kind-fields.php:224 882 890 msgid "(GMT+06:00) Moscow+03 - Omsk, Novosibirsk" 883 891 msgstr "" 884 892 885 #: includes/class-kind-fields.php:2 13893 #: includes/class-kind-fields.php:225 886 894 msgid "(GMT+06:00) Thimphu" 887 895 msgstr "" 888 896 889 #: includes/class-kind-fields.php:2 14897 #: includes/class-kind-fields.php:226 890 898 msgid "(GMT+06:00) Chagos" 891 899 msgstr "" 892 900 893 #: includes/class-kind-fields.php:2 15901 #: includes/class-kind-fields.php:227 894 902 msgid "(GMT+06:30) Rangoon" 895 903 msgstr "" 896 904 897 #: includes/class-kind-fields.php:2 16905 #: includes/class-kind-fields.php:228 898 906 msgid "(GMT+06:30) Cocos" 899 907 msgstr "" 900 908 901 #: includes/class-kind-fields.php:2 17909 #: includes/class-kind-fields.php:229 902 910 msgid "(GMT+07:00) Davis" 903 911 msgstr "" 904 912 905 #: includes/class-kind-fields.php:2 18913 #: includes/class-kind-fields.php:230 906 914 msgid "(GMT+07:00) Bangkok" 907 915 msgstr "" 908 916 909 #: includes/class-kind-fields.php:2 19917 #: includes/class-kind-fields.php:231 910 918 msgid "(GMT+07:00) Hovd" 911 919 msgstr "" 912 920 913 #: includes/class-kind-fields.php:2 20921 #: includes/class-kind-fields.php:232 914 922 msgid "(GMT+07:00) Jakarta" 915 923 msgstr "" 916 924 917 #: includes/class-kind-fields.php:2 21925 #: includes/class-kind-fields.php:233 918 926 msgid "(GMT+07:00) Moscow+04 - Krasnoyarsk" 919 927 msgstr "" 920 928 921 #: includes/class-kind-fields.php:2 22929 #: includes/class-kind-fields.php:234 922 930 msgid "(GMT+07:00) Hanoi" 923 931 msgstr "" 924 932 925 #: includes/class-kind-fields.php:2 23933 #: includes/class-kind-fields.php:235 926 934 msgid "(GMT+07:00) Ho Chi Minh" 927 935 msgstr "" 928 936 929 #: includes/class-kind-fields.php:2 24937 #: includes/class-kind-fields.php:236 930 938 msgid "(GMT+07:00) Christmas" 931 939 msgstr "" 932 940 933 #: includes/class-kind-fields.php:2 25941 #: includes/class-kind-fields.php:237 934 942 msgid "(GMT+08:00) Casey" 935 943 msgstr "" 936 944 937 #: includes/class-kind-fields.php:2 26945 #: includes/class-kind-fields.php:238 938 946 msgid "(GMT+08:00) Brunei" 939 947 msgstr "" 940 948 941 #: includes/class-kind-fields.php:2 27949 #: includes/class-kind-fields.php:239 942 950 msgid "(GMT+08:00) Choibalsan" 943 951 msgstr "" 944 952 945 #: includes/class-kind-fields.php:2 28953 #: includes/class-kind-fields.php:240 946 954 msgid "(GMT+08:00) Hong Kong" 947 955 msgstr "" 948 956 949 #: includes/class-kind-fields.php:2 29957 #: includes/class-kind-fields.php:241 950 958 msgid "(GMT+08:00) Moscow+05 - Irkutsk" 951 959 msgstr "" 952 960 953 #: includes/class-kind-fields.php:2 30961 #: includes/class-kind-fields.php:242 954 962 msgid "(GMT+08:00) Kuala Lumpur" 955 963 msgstr "" 956 964 957 #: includes/class-kind-fields.php:2 31965 #: includes/class-kind-fields.php:243 958 966 msgid "(GMT+08:00) Macau" 959 967 msgstr "" 960 968 961 #: includes/class-kind-fields.php:2 32969 #: includes/class-kind-fields.php:244 962 970 msgid "(GMT+08:00) Makassar" 963 971 msgstr "" 964 972 965 #: includes/class-kind-fields.php:2 33973 #: includes/class-kind-fields.php:245 966 974 msgid "(GMT+08:00) Manila" 967 975 msgstr "" 968 976 969 #: includes/class-kind-fields.php:2 34977 #: includes/class-kind-fields.php:246 970 978 msgid "(GMT+08:00) China Time - Beijing" 971 979 msgstr "" 972 980 973 #: includes/class-kind-fields.php:2 35981 #: includes/class-kind-fields.php:247 974 982 msgid "(GMT+08:00) Singapore" 975 983 msgstr "" 976 984 977 #: includes/class-kind-fields.php:2 36985 #: includes/class-kind-fields.php:248 978 986 msgid "(GMT+08:00) Taipei" 979 987 msgstr "" 980 988 981 #: includes/class-kind-fields.php:2 37989 #: includes/class-kind-fields.php:249 982 990 msgid "(GMT+08:00) Ulaanbaatar" 983 991 msgstr "" 984 992 985 #: includes/class-kind-fields.php:2 38993 #: includes/class-kind-fields.php:250 986 994 msgid "(GMT+08:00) Western Time - Perth" 987 995 msgstr "" 988 996 989 #: includes/class-kind-fields.php:2 39997 #: includes/class-kind-fields.php:251 990 998 msgid "(GMT+08:30) Pyongyang" 991 999 msgstr "" 992 1000 993 #: includes/class-kind-fields.php:2 401001 #: includes/class-kind-fields.php:252 994 1002 msgid "(GMT+09:00) Dili" 995 1003 msgstr "" 996 1004 997 #: includes/class-kind-fields.php:2 411005 #: includes/class-kind-fields.php:253 998 1006 msgid "(GMT+09:00) Jayapura" 999 1007 msgstr "" 1000 1008 1001 #: includes/class-kind-fields.php:2 421009 #: includes/class-kind-fields.php:254 1002 1010 msgid "(GMT+09:00) Seoul" 1003 1011 msgstr "" 1004 1012 1005 #: includes/class-kind-fields.php:2 431013 #: includes/class-kind-fields.php:255 1006 1014 msgid "(GMT+09:00) Tokyo" 1007 1015 msgstr "" 1008 1016 1009 #: includes/class-kind-fields.php:2 441017 #: includes/class-kind-fields.php:256 1010 1018 msgid "(GMT+09:00) Moscow+06 - Yakutsk" 1011 1019 msgstr "" 1012 1020 1013 #: includes/class-kind-fields.php:2 451021 #: includes/class-kind-fields.php:257 1014 1022 msgid "(GMT+09:00) Palau" 1015 1023 msgstr "" 1016 1024 1017 #: includes/class-kind-fields.php:2 461025 #: includes/class-kind-fields.php:258 1018 1026 msgid "(GMT+10:30) Central Time - Adelaide" 1019 1027 msgstr "" 1020 1028 1021 #: includes/class-kind-fields.php:2 471029 #: includes/class-kind-fields.php:259 1022 1030 msgid "(GMT+09:30) Central Time - Darwin" 1023 1031 msgstr "" 1024 1032 1025 #: includes/class-kind-fields.php:2 491033 #: includes/class-kind-fields.php:261 1026 1034 msgid "(GMT+10:00) Moscow+07 - Magadan" 1027 1035 msgstr "" 1028 1036 1029 #: includes/class-kind-fields.php:2 501037 #: includes/class-kind-fields.php:262 1030 1038 msgid "(GMT+10:00) Moscow+07 - Yuzhno-Sakhalinsk" 1031 1039 msgstr "" 1032 1040 1033 #: includes/class-kind-fields.php:2 511041 #: includes/class-kind-fields.php:263 1034 1042 msgid "(GMT+10:00) Eastern Time - Brisbane" 1035 1043 msgstr "" 1036 1044 1037 #: includes/class-kind-fields.php:2 521045 #: includes/class-kind-fields.php:264 1038 1046 msgid "(GMT+11:00) Eastern Time - Hobart" 1039 1047 msgstr "" 1040 1048 1041 #: includes/class-kind-fields.php:2 531049 #: includes/class-kind-fields.php:265 1042 1050 msgid "(GMT+11:00) Eastern Time - Melbourne, Sydney" 1043 1051 msgstr "" 1044 1052 1045 #: includes/class-kind-fields.php:2 541053 #: includes/class-kind-fields.php:266 1046 1054 msgid "(GMT+10:00) Truk" 1047 1055 msgstr "" 1048 1056 1049 #: includes/class-kind-fields.php:2 551057 #: includes/class-kind-fields.php:267 1050 1058 msgid "(GMT+10:00) Guam" 1051 1059 msgstr "" 1052 1060 1053 #: includes/class-kind-fields.php:2 561061 #: includes/class-kind-fields.php:268 1054 1062 msgid "(GMT+10:00) Port Moresby" 1055 1063 msgstr "" 1056 1064 1057 #: includes/class-kind-fields.php:2 571065 #: includes/class-kind-fields.php:269 1058 1066 msgid "(GMT+11:00) Efate" 1059 1067 msgstr "" 1060 1068 1061 #: includes/class-kind-fields.php:2 581069 #: includes/class-kind-fields.php:270 1062 1070 msgid "(GMT+11:00) Guadalcanal" 1063 1071 msgstr "" 1064 1072 1065 #: includes/class-kind-fields.php:2 591073 #: includes/class-kind-fields.php:271 1066 1074 msgid "(GMT+11:00) Kosrae" 1067 1075 msgstr "" 1068 1076 1069 #: includes/class-kind-fields.php:2 601077 #: includes/class-kind-fields.php:272 1070 1078 msgid "(GMT+11:00) Norfolk" 1071 1079 msgstr "" 1072 1080 1073 #: includes/class-kind-fields.php:2 611081 #: includes/class-kind-fields.php:273 1074 1082 msgid "(GMT+11:00) Noumea" 1075 1083 msgstr "" 1076 1084 1077 #: includes/class-kind-fields.php:2 621085 #: includes/class-kind-fields.php:274 1078 1086 msgid "(GMT+11:00) Ponape" 1079 1087 msgstr "" 1080 1088 1081 #: includes/class-kind-fields.php:2 631089 #: includes/class-kind-fields.php:275 1082 1090 msgid "(GMT+12:00) Moscow+09 - Petropavlovsk-Kamchatskiy" 1083 1091 msgstr "" 1084 1092 1085 #: includes/class-kind-fields.php:2 641093 #: includes/class-kind-fields.php:276 1086 1094 msgid "(GMT+13:00) Auckland" 1087 1095 msgstr "" 1088 1096 1089 #: includes/class-kind-fields.php:2 651097 #: includes/class-kind-fields.php:277 1090 1098 msgid "(GMT+13:00) Fiji" 1091 1099 msgstr "" 1092 1100 1093 #: includes/class-kind-fields.php:2 661101 #: includes/class-kind-fields.php:278 1094 1102 msgid "(GMT+12:00) Funafuti" 1095 1103 msgstr "" 1096 1104 1097 #: includes/class-kind-fields.php:2 671105 #: includes/class-kind-fields.php:279 1098 1106 msgid "(GMT+12:00) Kwajalein" 1099 1107 msgstr "" 1100 1108 1101 #: includes/class-kind-fields.php:2 681109 #: includes/class-kind-fields.php:280 1102 1110 msgid "(GMT+12:00) Majuro" 1103 1111 msgstr "" 1104 1112 1105 #: includes/class-kind-fields.php:2 691113 #: includes/class-kind-fields.php:281 1106 1114 msgid "(GMT+12:00) Nauru" 1107 1115 msgstr "" 1108 1116 1109 #: includes/class-kind-fields.php:2 701117 #: includes/class-kind-fields.php:282 1110 1118 msgid "(GMT+12:00) Tarawa" 1111 1119 msgstr "" 1112 1120 1113 #: includes/class-kind-fields.php:2 711121 #: includes/class-kind-fields.php:283 1114 1122 msgid "(GMT+12:00) Wake" 1115 1123 msgstr "" 1116 1124 1117 #: includes/class-kind-fields.php:2 721125 #: includes/class-kind-fields.php:284 1118 1126 msgid "(GMT+12:00) Wallis" 1119 1127 msgstr "" 1120 1128 1121 #: includes/class-kind-fields.php:2 731129 #: includes/class-kind-fields.php:285 1122 1130 msgid "(GMT+14:00) Apia" 1123 1131 msgstr "" 1124 1132 1125 #: includes/class-kind-fields.php:2 741133 #: includes/class-kind-fields.php:286 1126 1134 msgid "(GMT+13:00) Enderbury" 1127 1135 msgstr "" 1128 1136 1129 #: includes/class-kind-fields.php:2 751137 #: includes/class-kind-fields.php:287 1130 1138 msgid "(GMT+13:00) Fakaofo" 1131 1139 msgstr "" 1132 1140 1133 #: includes/class-kind-fields.php:2 761141 #: includes/class-kind-fields.php:288 1134 1142 msgid "(GMT+13:00) Tongatapu" 1135 1143 msgstr "" 1136 1144 1137 #: includes/class-kind-fields.php:2 771145 #: includes/class-kind-fields.php:289 1138 1146 msgid "(GMT+14:00) Kiritimati" 1139 1147 msgstr "" 1140 1148 1141 #: includes/class-kind-fields.php:3 851149 #: includes/class-kind-fields.php:396 1142 1150 msgid "Author Name" 1143 1151 msgstr "" 1144 1152 1145 #: includes/class-kind-fields.php: 3891146 #: templates/reply-author.php: 121153 #: includes/class-kind-fields.php:400 1154 #: templates/reply-author.php:26 1147 1155 msgid "Author URL" 1148 1156 msgstr "" 1149 1157 1150 #: includes/class-kind-fields.php: 3931158 #: includes/class-kind-fields.php:404 1151 1159 msgid "Author Photo" 1152 1160 msgstr "" 1153 1161 1154 #: includes/class-kind-media-metadata.php: 661155 #: includes/register-kinds.php:5 421156 #: includes/register-kinds.php:6 071162 #: includes/class-kind-media-metadata.php:125 1163 #: includes/register-kinds.php:551 1164 #: includes/register-kinds.php:616 1157 1165 msgid "Artist" 1158 1166 msgstr "" 1159 1167 1160 #: includes/class-kind-media-metadata.php: 671168 #: includes/class-kind-media-metadata.php:126 1161 1169 msgid "The creator of the image" 1162 1170 msgstr "" 1163 1171 1164 #: includes/class-kind-media-metadata.php: 721172 #: includes/class-kind-media-metadata.php:131 1165 1173 msgid "Artist URL" 1166 1174 msgstr "" 1167 1175 1168 #: includes/class-kind-media-metadata.php: 731176 #: includes/class-kind-media-metadata.php:132 1169 1177 msgid "The Artists URL or webpage" 1170 1178 msgstr "" 1171 1179 1172 #: includes/class-kind-menu-widget.php: 101180 #: includes/class-kind-menu-widget.php:25 1173 1181 msgid "Kind Menu Widget" 1174 1182 msgstr "" 1175 1183 1176 #: includes/class-kind-menu-widget.php: 131184 #: includes/class-kind-menu-widget.php:28 1177 1185 msgid "A widget that allows you to display a menu of kind archives" 1178 1186 msgstr "" 1179 1187 1180 1188 #. translators: 1. Blog Title 2. Kind Singular Name 1181 #: includes/class-kind-menu-widget.php:62 1182 msgid "title=\"%1$s %2$s Feed\"" 1183 msgstr "" 1184 1185 #: includes/class-kind-menu-widget.php:78 1189 #: includes/class-kind-menu-widget.php:80 1190 #, php-format 1191 msgid "%1$s %2$s Feed" 1192 msgstr "" 1193 1194 #: includes/class-kind-menu-widget.php:96 1186 1195 msgid "All Posts" 1187 1196 msgstr "" 1188 1197 1189 #: includes/class-kind-menu-widget.php:1 221190 #: includes/class-kind-post-widget.php: 951198 #: includes/class-kind-menu-widget.php:149 1199 #: includes/class-kind-post-widget.php:118 1191 1200 msgid "Title: " 1192 1201 msgstr "" 1193 1202 1194 #: includes/class-kind-menu-widget.php:1 431203 #: includes/class-kind-menu-widget.php:169 1195 1204 msgid "Show Link to All:" 1196 1205 msgstr "" 1197 1206 1198 #: includes/class-kind-menu-widget.php:1 481207 #: includes/class-kind-menu-widget.php:174 1199 1208 msgid "Show Count:" 1200 1209 msgstr "" 1201 1210 1202 #: includes/class-kind-metabox.php: 1541211 #: includes/class-kind-metabox.php:211 1203 1212 msgid "Your URL has been successfully retrieved and parsed" 1204 1213 msgstr "" 1205 1214 1206 #: includes/class-kind-metabox.php: 1551215 #: includes/class-kind-metabox.php:212 1207 1216 msgid "Are you sure you want to clear post properties?" 1208 1217 msgstr "" 1209 1218 1210 #: includes/class-kind-metabox.php:272 1219 #: includes/class-kind-metabox.php:213 1220 msgid "Error: Unable to Retrieve" 1221 msgstr "" 1222 1223 #: includes/class-kind-metabox.php:214 1224 msgid "Invalid URL" 1225 msgstr "" 1226 1227 #: includes/class-kind-metabox.php:215 1228 msgid "Attach" 1229 msgstr "" 1230 1231 #: includes/class-kind-metabox.php:216 1232 msgid "Use this media" 1233 msgstr "" 1234 1235 #: includes/class-kind-metabox.php:346 1211 1236 msgid "Very Dissatisfied" 1212 1237 msgstr "" 1213 1238 1214 #: includes/class-kind-metabox.php: 2731239 #: includes/class-kind-metabox.php:347 1215 1240 msgid "Dissatisfied" 1216 1241 msgstr "" 1217 1242 1218 #: includes/class-kind-metabox.php: 2741243 #: includes/class-kind-metabox.php:348 1219 1244 msgid "Neutral" 1220 1245 msgstr "" 1221 1246 1222 #: includes/class-kind-metabox.php: 2751247 #: includes/class-kind-metabox.php:349 1223 1248 msgid "Satisfied" 1224 1249 msgstr "" 1225 1250 1226 #: includes/class-kind-metabox.php: 2761251 #: includes/class-kind-metabox.php:350 1227 1252 msgid "Very Satisfied" 1228 1253 msgstr "" 1229 1254 1230 #: includes/class-kind-metabox.php:3 001231 #: includes/register-kinds.php:4 591255 #: includes/class-kind-metabox.php:376 1256 #: includes/register-kinds.php:468 1232 1257 msgid "Yes" 1233 1258 msgstr "" 1234 1259 1235 #: includes/class-kind-metabox.php:301 1260 #: includes/class-kind-metabox.php:377 1261 #: includes/register-kinds.php:469 1262 msgid "No" 1263 msgstr "" 1264 1265 #: includes/class-kind-metabox.php:378 1266 #: includes/register-kinds.php:470 1267 msgid "Maybe" 1268 msgstr "" 1269 1270 #: includes/class-kind-metabox.php:379 1271 #: includes/register-kinds.php:471 1272 msgid "Interested" 1273 msgstr "" 1274 1275 #: includes/class-kind-metabox.php:380 1276 #: includes/register-kinds.php:472 1277 msgid "Remote" 1278 msgstr "" 1279 1280 #: includes/class-kind-metabox.php:404 1236 1281 #: includes/register-kinds.php:460 1237 msgid "No" 1238 msgstr "" 1239 1240 #: includes/class-kind-metabox.php:302 1241 #: includes/register-kinds.php:461 1242 msgid "Maybe" 1243 msgstr "" 1244 1245 #: includes/class-kind-metabox.php:303 1246 #: includes/register-kinds.php:462 1247 msgid "Interested" 1248 msgstr "" 1249 1250 #: includes/class-kind-metabox.php:304 1251 #: includes/register-kinds.php:463 1252 msgid "Remote" 1253 msgstr "" 1254 1255 #: includes/class-kind-metabox.php:326 1256 #: includes/register-kinds.php:451 1282 #: views/kind.php:81 1257 1283 msgid "RSVP" 1258 1284 msgstr "" 1259 1285 1260 #: includes/class-kind-metabox.php: 3421286 #: includes/class-kind-metabox.php:422 1261 1287 msgid "Rating" 1262 1288 msgstr "" 1263 1289 1264 #: includes/class-kind-metabox.php: 3571290 #: includes/class-kind-metabox.php:461 1265 1291 msgid "Response Properties" 1266 1292 msgstr "" 1267 1293 1268 #: includes/class-kind-plugins.php: 1331294 #: includes/class-kind-plugins.php:209 1269 1295 msgctxt "direct article" 1270 1296 msgid "this" 1271 1297 msgstr "" 1272 1298 1273 #: includes/class-kind-post-widget.php:10 1299 #: includes/class-kind-plugins.php:535 1300 msgid "Nothing could be parsed from the URL." 1301 msgstr "" 1302 1303 #: includes/class-kind-post-widget.php:25 1274 1304 msgid "Kind Post Widget" 1275 1305 msgstr "" 1276 1306 1277 #: includes/class-kind-post-widget.php: 131307 #: includes/class-kind-post-widget.php:28 1278 1308 msgid "A widget that allows you to display a list of posts by type" 1279 1309 msgstr "" 1280 1310 1281 #: includes/class-kind-post-widget.php:63 1282 msgid "No Posts Found" 1283 msgstr "" 1284 1285 #: includes/class-kind-post-widget.php:115 1311 #: includes/class-kind-post-widget.php:137 1286 1312 msgid "Number of Posts:" 1287 1313 msgstr "" 1288 1314 1289 #: includes/class-kind-taxonomy.php:305 1290 msgid "Post Kind" 1291 msgstr "" 1292 1293 #: includes/class-kind-taxonomy.php:526 1315 #: includes/class-kind-taxonomy.php:161 1316 msgid "Unknown post kind." 1317 msgstr "" 1318 1319 #: includes/class-kind-taxonomy.php:363 1320 msgid "The post kind, or false if the post has none." 1321 msgstr "" 1322 1323 #: includes/class-kind-taxonomy.php:609 1324 msgid "Post Kinds replaces the IndieWeb Taxonomy plugin. Deactivate IndieWeb Taxonomy, then activate Post Kinds." 1325 msgstr "" 1326 1327 #: includes/class-kind-taxonomy.php:697 1294 1328 msgctxt "taxonomy general name" 1295 1329 msgid "Kinds" 1296 1330 msgstr "" 1297 1331 1298 #: includes/class-kind-taxonomy.php: 5271332 #: includes/class-kind-taxonomy.php:698 1299 1333 msgctxt "taxonomy singular name" 1300 1334 msgid "Kind" 1301 1335 msgstr "" 1302 1336 1303 #: includes/class-kind-taxonomy.php: 5281337 #: includes/class-kind-taxonomy.php:699 1304 1338 msgctxt "search locations" 1305 1339 msgid "Search Kinds" 1306 1340 msgstr "" 1307 1341 1308 #: includes/class-kind-taxonomy.php: 5291342 #: includes/class-kind-taxonomy.php:700 1309 1343 msgctxt "popular kinds" 1310 1344 msgid "Popular Kinds" 1311 1345 msgstr "" 1312 1346 1313 #: includes/class-kind-taxonomy.php: 5301347 #: includes/class-kind-taxonomy.php:701 1314 1348 msgctxt "all taxonomy items" 1315 1349 msgid "All Kinds" 1316 1350 msgstr "" 1317 1351 1318 #: includes/class-kind-taxonomy.php: 5311352 #: includes/class-kind-taxonomy.php:702 1319 1353 msgctxt "taxonomy parent item" 1320 1354 msgid "Parent Kind" 1321 1355 msgstr "" 1322 1356 1323 #: includes/class-kind-taxonomy.php: 5321357 #: includes/class-kind-taxonomy.php:703 1324 1358 msgctxt "taxonomy parent item with colon" 1325 1359 msgid "Parent Kind:" 1326 1360 msgstr "" 1327 1361 1328 #: includes/class-kind-taxonomy.php: 5331362 #: includes/class-kind-taxonomy.php:704 1329 1363 msgid "This will appear on archives as the description if your theme supports" 1330 1364 msgstr "" 1331 1365 1332 #: includes/class-kind-taxonomy.php: 5341366 #: includes/class-kind-taxonomy.php:705 1333 1367 msgctxt "edit taxonomy item" 1334 1368 msgid "Edit Kind" 1335 1369 msgstr "" 1336 1370 1337 #: includes/class-kind-taxonomy.php: 5351371 #: includes/class-kind-taxonomy.php:706 1338 1372 msgctxt "view taxonomy item" 1339 1373 msgid "View Kind" 1340 1374 msgstr "" 1341 1375 1342 #: includes/class-kind-taxonomy.php: 5361376 #: includes/class-kind-taxonomy.php:707 1343 1377 msgctxt "update taxonomy item" 1344 1378 msgid "Update Kind" 1345 1379 msgstr "" 1346 1380 1347 #: includes/class-kind-taxonomy.php: 5371381 #: includes/class-kind-taxonomy.php:708 1348 1382 msgctxt "add taxonomy item" 1349 1383 msgid "Add New Kind" 1350 1384 msgstr "" 1351 1385 1352 #: includes/class-kind-taxonomy.php: 5381386 #: includes/class-kind-taxonomy.php:709 1353 1387 msgctxt "new taxonomy item" 1354 1388 msgid "New Kind" 1355 1389 msgstr "" 1356 1390 1357 #: includes/class-kind-taxonomy.php: 5391391 #: includes/class-kind-taxonomy.php:710 1358 1392 msgctxt "separate kinds with commas" 1359 1393 msgid "Separate kinds with commas" 1360 1394 msgstr "" 1361 1395 1362 #: includes/class-kind-taxonomy.php: 5401396 #: includes/class-kind-taxonomy.php:711 1363 1397 msgctxt "add or remove items" 1364 1398 msgid "Add or remove kinds" 1365 1399 msgstr "" 1366 1400 1367 #: includes/class-kind-taxonomy.php: 5411401 #: includes/class-kind-taxonomy.php:712 1368 1402 msgctxt "choose most used" 1369 1403 msgid "Choose from the most used kinds" 1370 1404 msgstr "" 1371 1405 1372 #: includes/class-kind-taxonomy.php: 5421406 #: includes/class-kind-taxonomy.php:713 1373 1407 msgctxt "no kinds found" 1374 1408 msgid "No kinds found" 1375 1409 msgstr "" 1376 1410 1377 #: includes/class-kind-taxonomy.php: 5431411 #: includes/class-kind-taxonomy.php:714 1378 1412 msgctxt "no kinds" 1379 1413 msgid "No kinds" 1380 1414 msgstr "" 1381 1415 1382 #: includes/class-kind-taxonomy.php: 5441416 #: includes/class-kind-taxonomy.php:715 1383 1417 msgid "Back to Kinds" 1384 1418 msgstr "" 1385 1419 1386 #: includes/class-kind-taxonomy.php: 5451420 #: includes/class-kind-taxonomy.php:716 1387 1421 msgid "Kind Link" 1388 1422 msgstr "" 1389 1423 1390 #: includes/class-kind-taxonomy.php: 5461424 #: includes/class-kind-taxonomy.php:717 1391 1425 msgid "A link to a kind" 1392 msgstr ""1393 1394 #: includes/class-kind-taxonomy.php:5651395 #: includes/register-kinds.php:81396 msgid "Article"1397 1426 msgstr "" 1398 1427 … … 1400 1429 #. translators: 1: Taxonomy. 2: Month name and year 1401 1430 #. translators: 1. Taxonomy . 1: Date 1402 #: includes/class-kind-taxonomy.php:860 1403 #: includes/class-kind-taxonomy.php:863 1404 #: includes/class-kind-taxonomy.php:866 1431 #: includes/class-kind-taxonomy.php:1114 1432 #: includes/class-kind-taxonomy.php:1117 1433 #: includes/class-kind-taxonomy.php:1120 1434 #, php-format 1405 1435 msgid "%1$1s - %2$2s" 1406 1436 msgstr "" 1407 1437 1408 1438 #. translators: 1: Taxonomy. Yearly archive title. 2: Year 1409 #: includes/class-kind-taxonomy.php: 8601439 #: includes/class-kind-taxonomy.php:1114 1410 1440 msgctxt "yearly archives date format" 1411 1441 msgid "Y" … … 1413 1443 1414 1444 #. translators: 1: Taxonomy. 2: Month name and year 1415 #: includes/class-kind-taxonomy.php: 8631445 #: includes/class-kind-taxonomy.php:1117 1416 1446 msgctxt "monthly archives date format" 1417 1447 msgid "F Y" … … 1419 1449 1420 1450 #. translators: 1. Taxonomy . 1: Date 1421 #: includes/class-kind-taxonomy.php: 8661451 #: includes/class-kind-taxonomy.php:1120 1422 1452 msgctxt "daily archives date format" 1423 1453 msgid "F j, Y" … … 1425 1455 1426 1456 #. translators: 1. Title 2. Translated term for Firehose Feed 1427 #: includes/class-kind-taxonomy.php:870 1457 #: includes/class-kind-taxonomy.php:1124 1458 #, php-format 1428 1459 msgid "%1$s - %2$2s" 1429 1460 msgstr "" 1430 1461 1431 1462 #. translators: 1. Title 2. Translated term for Firehose Feed 1432 #: includes/class-kind-taxonomy.php: 8701463 #: includes/class-kind-taxonomy.php:1124 1433 1464 msgid "Firehose" 1434 1465 msgstr "" 1435 1466 1436 1467 #. translators: 1: Title prefix. 2: Title. 1437 #: includes/class-kind-taxonomy.php:876 1468 #: includes/class-kind-taxonomy.php:1130 1469 #, php-format 1438 1470 msgctxt "archive title" 1439 1471 msgid "%1$s %2$s" … … 1441 1473 1442 1474 #. translators: All 1443 #: includes/class-kind-taxonomy.php:1164 1475 #: includes/class-kind-taxonomy.php:1455 1476 #, php-format 1444 1477 msgid "All %1s" 1445 1478 msgstr "" 1446 1479 1447 #: includes/class-kind-taxonomy.php:1 3341480 #: includes/class-kind-taxonomy.php:1647 1448 1481 msgid "Invalid post" 1449 1482 msgstr "" 1450 1483 1451 #: includes/class-kind-taxonomy.php:1 3371484 #: includes/class-kind-taxonomy.php:1650 1452 1485 msgid "Invalid Kind" 1453 1486 msgstr "" 1454 1487 1455 #: includes/class-kind-view.php:418 1488 #: includes/class-kind-taxonomy.php:1670 1489 msgid "Invalid post." 1490 msgstr "" 1491 1492 #: includes/class-kind-taxonomy.php:1673 1493 msgid "Sorry, you are not allowed to set the kind of this post." 1494 msgstr "" 1495 1496 #: includes/class-kind-view.php:495 1456 1497 msgctxt "singular Twitter" 1457 1498 msgid "a tweet" 1458 1499 msgstr "" 1459 1500 1460 #: includes/class-kind-view.php:4 191501 #: includes/class-kind-view.php:496 1461 1502 msgctxt "singular Vimeo" 1462 1503 msgid "a video" 1463 1504 msgstr "" 1464 1505 1465 #: includes/class-kind-view.php:4 201506 #: includes/class-kind-view.php:497 1466 1507 msgctxt "singular Youtube" 1467 1508 msgid "a video" 1468 1509 msgstr "" 1469 1510 1470 #: includes/class-kind-view.php:4 211511 #: includes/class-kind-view.php:498 1471 1512 msgctxt "singular Intagram" 1472 1513 msgid "an image" 1473 1514 msgstr "" 1474 1515 1475 #: includes/class-kind-view.php: 4271516 #: includes/class-kind-view.php:513 1476 1517 msgctxt "singular post" 1477 1518 msgid "a post" 1478 1519 msgstr "" 1479 1520 1480 #: includes/class-kind-view.php: 4941521 #: includes/class-kind-view.php:589 1481 1522 msgid "an author" 1482 1523 msgstr "" 1483 1524 1484 #. translators: URL for link to event and name of event 1485 #: includes/class-kind-view.php:614 1486 msgid "Attending <a href=\"%1$s\" class=\"u-in-reply-to\">%2$s</a>" 1487 msgstr "" 1488 1489 #. translators: URL for link to event and name of event 1490 #: includes/class-kind-view.php:616 1491 msgid "Might be attending <a href=\"%1$s\" class=\"u-in-reply-to\">%2$s</a>" 1492 msgstr "" 1493 1494 #. translators: URL for link to event and name of event 1495 #: includes/class-kind-view.php:618 1496 msgid "Unable to Attend <a href=\"%1$s\" class=\"u-in-reply-to\">%2$s</a>" 1497 msgstr "" 1498 1499 #. translators: URL for link to event and name of event 1500 #: includes/class-kind-view.php:620 1501 msgid "Interested in Attending <a href=\"%1$s\" class=u-in-reply-to\">%2$s</a>" 1502 msgstr "" 1503 1504 #. translators: URL for link to event and name of event 1505 #: includes/class-kind-view.php:622 1506 msgid "Attending <a href=\"%1$s\" class=\"u-in-reply-to\">%2$s</a> remotely" 1507 msgstr "" 1508 1509 #: includes/class-kind-view.php:640 1525 #. translators: %s: Link to the event 1526 #: includes/class-kind-view.php:752 1527 #, php-format 1528 msgid "Attending %s" 1529 msgstr "" 1530 1531 #. translators: %s: Link to the event 1532 #: includes/class-kind-view.php:754 1533 #, php-format 1534 msgid "Might be attending %s" 1535 msgstr "" 1536 1537 #. translators: %s: Link to the event 1538 #: includes/class-kind-view.php:756 1539 #, php-format 1540 msgid "Unable to Attend %s" 1541 msgstr "" 1542 1543 #. translators: %s: Link to the event 1544 #: includes/class-kind-view.php:758 1545 #, php-format 1546 msgid "Interested in Attending %s" 1547 msgstr "" 1548 1549 #. translators: %s: Link to the event 1550 #: includes/class-kind-view.php:760 1551 #, php-format 1552 msgid "Attending %s remotely" 1553 msgstr "" 1554 1555 #: includes/class-kind-view.php:794 1510 1556 msgid "Want to Read: " 1511 1557 msgstr "" 1512 1558 1513 #: includes/class-kind-view.php: 6411559 #: includes/class-kind-view.php:795 1514 1560 msgid "Reading: " 1515 1561 msgstr "" 1516 1562 1517 #: includes/class-kind-view.php: 6421563 #: includes/class-kind-view.php:796 1518 1564 msgid "Finished Reading: " 1519 1565 msgstr "" 1520 1566 1521 1567 #. translators: singular and plural 1522 #: includes/class-kind-view.php:674 1568 #: includes/class-kind-view.php:828 1569 #, php-format 1523 1570 msgid "%d year" 1524 1571 msgid_plural "%d years" … … 1527 1574 1528 1575 #. translators: singular and plural 1529 #: includes/class-kind-view.php:678 1576 #: includes/class-kind-view.php:832 1577 #, php-format 1530 1578 msgid "%d month" 1531 1579 msgid_plural "%d months" … … 1534 1582 1535 1583 #. translators: singular and plural 1536 #: includes/class-kind-view.php:682 1584 #: includes/class-kind-view.php:836 1585 #, php-format 1537 1586 msgid "%d day" 1538 1587 msgid_plural "%d days" … … 1541 1590 1542 1591 #. translators: singular and plural 1543 #: includes/class-kind-view.php:686 1592 #: includes/class-kind-view.php:840 1593 #, php-format 1544 1594 msgid "%d hour" 1545 1595 msgid_plural "%d hours" … … 1548 1598 1549 1599 #. translators: singular and plural 1550 #: includes/class-kind-view.php:690 1600 #: includes/class-kind-view.php:844 1601 #, php-format 1551 1602 msgid "%d minute" 1552 1603 msgid_plural "%d minutes" … … 1555 1606 1556 1607 #. translators: singular and plural 1557 #: includes/class-kind-view.php:694 1608 #: includes/class-kind-view.php:848 1609 #, php-format 1558 1610 msgid "%d second" 1559 1611 msgid_plural "%d seconds" … … 1561 1613 msgstr[1] "" 1562 1614 1563 #: includes/register-kinds.php:9 1615 #: includes/register-kinds.php:17 1616 msgid "Article" 1617 msgstr "" 1618 1619 #: includes/register-kinds.php:18 1564 1620 msgid "Articles" 1565 1621 msgstr "" 1566 1622 1567 #: includes/register-kinds.php: 141623 #: includes/register-kinds.php:23 1568 1624 msgid "traditional long form content: a post with an explicit title and body" 1569 1625 msgstr "" 1570 1626 1571 #: includes/register-kinds.php: 251627 #: includes/register-kinds.php:34 1572 1628 msgid "Note" 1573 1629 msgstr "" 1574 1630 1575 #: includes/register-kinds.php: 261631 #: includes/register-kinds.php:35 1576 1632 msgid "Notes" 1577 1633 msgstr "" 1578 1634 1579 #: includes/register-kinds.php: 311635 #: includes/register-kinds.php:40 1580 1636 msgid "short content: a post or status update with just plain content and typically without a title" 1581 1637 msgstr "" 1582 1638 1583 #: includes/register-kinds.php: 421639 #: includes/register-kinds.php:51 1584 1640 msgid "Reply" 1585 1641 msgstr "" 1586 1642 1587 #: includes/register-kinds.php: 431643 #: includes/register-kinds.php:52 1588 1644 msgid "Replies" 1589 1645 msgstr "" 1590 1646 1591 #: includes/register-kinds.php: 441647 #: includes/register-kinds.php:53 1592 1648 msgid "Replied to" 1593 1649 msgstr "" 1594 1650 1595 #: includes/register-kinds.php: 491651 #: includes/register-kinds.php:58 1596 1652 msgid "In Reply To" 1597 1653 msgstr "" 1598 1654 1599 #: includes/register-kinds.php:53 1655 #: includes/register-kinds.php:62 1656 #: includes/register-kinds.php:131 1657 #: includes/register-kinds.php:200 1658 #: includes/register-kinds.php:269 1659 #: includes/register-kinds.php:338 1660 #: includes/register-kinds.php:481 1661 #: includes/register-kinds.php:543 1662 #: includes/register-kinds.php:608 1663 #: includes/register-kinds.php:673 1664 #: templates/reply-metabox.php:56 1665 msgid "Name" 1666 msgstr "" 1667 1668 #: includes/register-kinds.php:66 1669 #: includes/register-kinds.php:135 1670 #: includes/register-kinds.php:204 1671 #: includes/register-kinds.php:273 1672 #: includes/register-kinds.php:342 1673 #: includes/register-kinds.php:485 1674 #: includes/register-kinds.php:547 1675 #: includes/register-kinds.php:612 1676 #: includes/register-kinds.php:677 1677 #: templates/reply-metabox.php:50 1678 msgid "URL" 1679 msgstr "" 1680 1681 #: includes/register-kinds.php:70 1682 #: includes/register-kinds.php:139 1683 #: includes/register-kinds.php:208 1684 #: includes/register-kinds.php:277 1685 #: includes/register-kinds.php:346 1686 #: templates/reply-author.php:20 1687 #: templates/reply-metabox.php:45 1688 msgid "Author" 1689 msgstr "" 1690 1691 #: includes/register-kinds.php:77 1692 #: includes/register-kinds.php:146 1693 #: includes/register-kinds.php:215 1694 #: includes/register-kinds.php:284 1695 #: includes/register-kinds.php:353 1696 #: includes/register-kinds.php:489 1697 #: includes/register-kinds.php:558 1698 #: includes/register-kinds.php:623 1699 msgid "Summary" 1700 msgstr "" 1701 1702 #: includes/register-kinds.php:81 1703 #: includes/register-kinds.php:150 1704 #: includes/register-kinds.php:219 1705 #: includes/register-kinds.php:288 1706 #: includes/register-kinds.php:357 1707 #: includes/register-kinds.php:493 1708 msgid "Publication/Website" 1709 msgstr "" 1710 1711 #: includes/register-kinds.php:85 1712 #: includes/register-kinds.php:154 1713 #: includes/register-kinds.php:223 1714 #: includes/register-kinds.php:292 1715 #: includes/register-kinds.php:361 1716 msgid "Publish Date" 1717 msgstr "" 1718 1719 #: includes/register-kinds.php:89 1720 #: includes/register-kinds.php:158 1721 #: includes/register-kinds.php:227 1722 #: includes/register-kinds.php:296 1723 #: includes/register-kinds.php:365 1724 msgid "Updated Time" 1725 msgstr "" 1726 1727 #: includes/register-kinds.php:93 1728 #: includes/register-kinds.php:162 1729 #: includes/register-kinds.php:231 1730 #: includes/register-kinds.php:300 1731 #: includes/register-kinds.php:369 1732 #: includes/register-kinds.php:445 1733 #: includes/register-kinds.php:505 1734 #: includes/register-kinds.php:570 1735 #: includes/register-kinds.php:635 1736 msgid "Tags" 1737 msgstr "" 1738 1739 #: includes/register-kinds.php:100 1740 #: includes/register-kinds.php:169 1741 #: includes/register-kinds.php:238 1742 #: includes/register-kinds.php:307 1743 #: includes/register-kinds.php:376 1744 #: includes/register-kinds.php:512 1745 msgid "Featured Media" 1746 msgstr "" 1747 1748 #: includes/register-kinds.php:109 1749 msgid "a reply to content typically on another site" 1750 msgstr "" 1751 1752 #: includes/register-kinds.php:120 1753 #: includes/register-kinds.php:127 1754 msgid "Repost" 1755 msgstr "" 1756 1757 #: includes/register-kinds.php:121 1758 msgid "Reposts" 1759 msgstr "" 1760 1600 1761 #: includes/register-kinds.php:122 1762 msgid "Reposted" 1763 msgstr "" 1764 1765 #: includes/register-kinds.php:178 1766 msgid "a complete reposting of content from another site" 1767 msgstr "" 1768 1769 #: includes/register-kinds.php:189 1770 #: includes/register-kinds.php:196 1771 msgid "Like" 1772 msgstr "" 1773 1774 #: includes/register-kinds.php:190 1775 msgid "Likes" 1776 msgstr "" 1777 1601 1778 #: includes/register-kinds.php:191 1779 msgid "Liked" 1780 msgstr "" 1781 1782 #: includes/register-kinds.php:247 1783 msgid "a way to pay compliments to the original post/poster of external content" 1784 msgstr "" 1785 1786 #: includes/register-kinds.php:258 1787 msgid "Favorite" 1788 msgstr "" 1789 1790 #: includes/register-kinds.php:259 1791 msgid "Favorites" 1792 msgstr "" 1793 1602 1794 #: includes/register-kinds.php:260 1795 #: includes/register-kinds.php:265 1796 msgid "Favorited" 1797 msgstr "" 1798 1799 #: includes/register-kinds.php:316 1800 msgid "special to the author" 1801 msgstr "" 1802 1803 #: includes/register-kinds.php:327 1804 #: includes/register-kinds.php:334 1805 msgid "Bookmark" 1806 msgstr "" 1807 1808 #: includes/register-kinds.php:328 1809 msgid "Bookmarks" 1810 msgstr "" 1811 1603 1812 #: includes/register-kinds.php:329 1604 #: includes/register-kinds.php:472 1813 msgid "Bookmarked" 1814 msgstr "" 1815 1816 #: includes/register-kinds.php:385 1817 msgid "storing a link/bookmark for personal use or sharing with others" 1818 msgstr "" 1819 1820 #: includes/register-kinds.php:396 1821 msgid "Photo" 1822 msgstr "" 1823 1824 #: includes/register-kinds.php:397 1825 msgid "Photos" 1826 msgstr "" 1827 1828 #: includes/register-kinds.php:401 1829 msgid "a post with an embedded image/photo as its primary focus" 1830 msgstr "" 1831 1832 #: includes/register-kinds.php:412 1833 msgid "Video" 1834 msgstr "" 1835 1836 #: includes/register-kinds.php:413 1837 msgid "Videos" 1838 msgstr "" 1839 1840 #: includes/register-kinds.php:417 1841 msgid "a post with an embedded video as its primary focus" 1842 msgstr "" 1843 1844 #: includes/register-kinds.php:428 1845 msgid "Audio" 1846 msgstr "" 1847 1848 #: includes/register-kinds.php:429 1849 msgid "Audios" 1850 msgstr "" 1851 1852 #: includes/register-kinds.php:433 1853 msgid "a post with an embedded audio file as its primary focus" 1854 msgstr "" 1855 1856 #: includes/register-kinds.php:444 1857 msgid "Tag" 1858 msgstr "" 1859 1860 #: includes/register-kinds.php:446 1861 msgid "Tagged" 1862 msgstr "" 1863 1864 #: includes/register-kinds.php:449 1865 msgid "allows you to tag a post as being of a specific category or tag, or for person tagging" 1866 msgstr "" 1867 1868 #: includes/register-kinds.php:461 1869 msgid "RSVPs" 1870 msgstr "" 1871 1872 #: includes/register-kinds.php:462 1873 msgid "RSVPed" 1874 msgstr "" 1875 1876 #: includes/register-kinds.php:477 1877 msgid "Event Details" 1878 msgstr "" 1879 1880 #: includes/register-kinds.php:497 1881 msgid "Start Date" 1882 msgstr "" 1883 1884 #: includes/register-kinds.php:501 1885 msgid "End Date" 1886 msgstr "" 1887 1888 #: includes/register-kinds.php:521 1889 msgid "a specific type of reply regarding attendance of an event" 1890 msgstr "" 1891 1892 #: includes/register-kinds.php:532 1893 msgid "Listen" 1894 msgstr "" 1895 1896 #: includes/register-kinds.php:533 1897 msgid "Listens" 1898 msgstr "" 1899 1605 1900 #: includes/register-kinds.php:534 1901 msgid "Listened" 1902 msgstr "" 1903 1904 #: includes/register-kinds.php:539 1905 msgid "Listen To" 1906 msgstr "" 1907 1908 #: includes/register-kinds.php:562 1909 msgid "Album" 1910 msgstr "" 1911 1912 #: includes/register-kinds.php:566 1913 #: includes/register-kinds.php:631 1914 msgid "Release Date" 1915 msgstr "" 1916 1917 #: includes/register-kinds.php:577 1918 msgid "Album Art" 1919 msgstr "" 1920 1921 #: includes/register-kinds.php:586 1922 msgid "listening to audio; sometimes called a scrobble" 1923 msgstr "" 1924 1925 #: includes/register-kinds.php:597 1926 msgid "Watch" 1927 msgstr "" 1928 1929 #: includes/register-kinds.php:598 1930 msgid "Watches" 1931 msgstr "" 1932 1606 1933 #: includes/register-kinds.php:599 1934 msgid "Watched" 1935 msgstr "" 1936 1937 #: includes/register-kinds.php:604 1938 msgid "Watching To" 1939 msgstr "" 1940 1941 #: includes/register-kinds.php:627 1942 msgid "Series" 1943 msgstr "" 1944 1945 #: includes/register-kinds.php:642 1946 msgid "Poster" 1947 msgstr "" 1948 1949 #: includes/register-kinds.php:651 1950 msgid "watching a movie, television show, online video, play or other visual-based event" 1951 msgstr "" 1952 1953 #: includes/register-kinds.php:662 1954 msgid "Checkin" 1955 msgstr "" 1956 1957 #: includes/register-kinds.php:663 1958 msgid "Checkins" 1959 msgstr "" 1960 1607 1961 #: includes/register-kinds.php:664 1608 #: templates/reply-metabox.php:421609 msgid "Name"1610 msgstr ""1611 1612 #: includes/register-kinds.php:571613 #: includes/register-kinds.php:1261614 #: includes/register-kinds.php:1951615 #: includes/register-kinds.php:2641616 #: includes/register-kinds.php:3331617 #: includes/register-kinds.php:4761618 #: includes/register-kinds.php:5381619 #: includes/register-kinds.php:6031620 #: includes/register-kinds.php:6681621 #: lib/parse-this/includes/class-rest-parse-this.php:491622 #: templates/reply-metabox.php:361623 msgid "URL"1624 msgstr ""1625 1626 #: includes/register-kinds.php:611627 #: includes/register-kinds.php:1301628 #: includes/register-kinds.php:1991629 #: includes/register-kinds.php:2681630 #: includes/register-kinds.php:3371631 #: templates/reply-author.php:61632 #: templates/reply-metabox.php:311633 msgid "Author"1634 msgstr ""1635 1636 #: includes/register-kinds.php:681637 #: includes/register-kinds.php:1371638 #: includes/register-kinds.php:2061639 #: includes/register-kinds.php:2751640 #: includes/register-kinds.php:3441641 #: includes/register-kinds.php:4801642 #: includes/register-kinds.php:5491643 #: includes/register-kinds.php:6141644 msgid "Summary"1645 msgstr ""1646 1647 #: includes/register-kinds.php:721648 #: includes/register-kinds.php:1411649 #: includes/register-kinds.php:2101650 #: includes/register-kinds.php:2791651 #: includes/register-kinds.php:3481652 #: includes/register-kinds.php:4841653 msgid "Publication/Website"1654 msgstr ""1655 1656 #: includes/register-kinds.php:761657 #: includes/register-kinds.php:1451658 #: includes/register-kinds.php:2141659 #: includes/register-kinds.php:2831660 #: includes/register-kinds.php:3521661 msgid "Publish Date"1662 msgstr ""1663 1664 #: includes/register-kinds.php:801665 #: includes/register-kinds.php:1491666 #: includes/register-kinds.php:2181667 #: includes/register-kinds.php:2871668 #: includes/register-kinds.php:3561669 msgid "Updated Time"1670 msgstr ""1671 1672 #: includes/register-kinds.php:841673 #: includes/register-kinds.php:1531674 #: includes/register-kinds.php:2221675 #: includes/register-kinds.php:2911676 #: includes/register-kinds.php:3601677 #: includes/register-kinds.php:4361678 #: includes/register-kinds.php:4961679 #: includes/register-kinds.php:5611680 #: includes/register-kinds.php:6261681 msgid "Tags"1682 msgstr ""1683 1684 #: includes/register-kinds.php:911685 #: includes/register-kinds.php:1601686 #: includes/register-kinds.php:2291687 #: includes/register-kinds.php:2981688 #: includes/register-kinds.php:3671689 #: includes/register-kinds.php:5031690 msgid "Featured Media"1691 msgstr ""1692 1693 #: includes/register-kinds.php:1001694 msgid "a reply to content typically on another site"1695 msgstr ""1696 1697 #: includes/register-kinds.php:1111698 #: includes/register-kinds.php:1181699 msgid "Repost"1700 msgstr ""1701 1702 #: includes/register-kinds.php:1121703 msgid "Reposts"1704 msgstr ""1705 1706 #: includes/register-kinds.php:1131707 msgid "Reposted"1708 msgstr ""1709 1710 #: includes/register-kinds.php:1691711 msgid "a complete reposting of content from another site"1712 msgstr ""1713 1714 #: includes/register-kinds.php:1801715 #: includes/register-kinds.php:1871716 msgid "Like"1717 msgstr ""1718 1719 #: includes/register-kinds.php:1811720 msgid "Likes"1721 msgstr ""1722 1723 #: includes/register-kinds.php:1821724 msgid "Liked"1725 msgstr ""1726 1727 #: includes/register-kinds.php:2381728 msgid "a way to pay compliments to the original post/poster of external content"1729 msgstr ""1730 1731 #: includes/register-kinds.php:2491732 msgid "Favorite"1733 msgstr ""1734 1735 #: includes/register-kinds.php:2501736 msgid "Favorites"1737 msgstr ""1738 1739 #: includes/register-kinds.php:2511740 #: includes/register-kinds.php:2561741 msgid "Favorited"1742 msgstr ""1743 1744 #: includes/register-kinds.php:3071745 msgid "special to the author"1746 msgstr ""1747 1748 #: includes/register-kinds.php:3181749 #: includes/register-kinds.php:3251750 msgid "Bookmark"1751 msgstr ""1752 1753 #: includes/register-kinds.php:3191754 msgid "Bookmarks"1755 msgstr ""1756 1757 #: includes/register-kinds.php:3201758 msgid "Bookmarked"1759 msgstr ""1760 1761 #: includes/register-kinds.php:3761762 msgid "storing a link/bookmark for personal use or sharing with others"1763 msgstr ""1764 1765 #: includes/register-kinds.php:3871766 msgid "Photo"1767 msgstr ""1768 1769 #: includes/register-kinds.php:3881770 msgid "Photos"1771 msgstr ""1772 1773 #: includes/register-kinds.php:3921774 msgid "a post with an embedded image/photo as its primary focus"1775 msgstr ""1776 1777 #: includes/register-kinds.php:4031778 msgid "Video"1779 msgstr ""1780 1781 #: includes/register-kinds.php:4041782 msgid "Videos"1783 msgstr ""1784 1785 #: includes/register-kinds.php:4081786 msgid "a post with an embedded video as its primary focus"1787 msgstr ""1788 1789 #: includes/register-kinds.php:4191790 msgid "Audio"1791 msgstr ""1792 1793 #: includes/register-kinds.php:4201794 msgid "Audios"1795 msgstr ""1796 1797 #: includes/register-kinds.php:4241798 msgid "a post with an embedded audio file as its primary focus"1799 msgstr ""1800 1801 #: includes/register-kinds.php:4351802 msgid "Tag"1803 msgstr ""1804 1805 #: includes/register-kinds.php:4371806 msgid "Tagged"1807 msgstr ""1808 1809 #: includes/register-kinds.php:4401810 msgid "allows you to tag a post as being of a specific category or tag, or for person tagging"1811 msgstr ""1812 1813 #: includes/register-kinds.php:4521814 msgid "RSVPs"1815 msgstr ""1816 1817 #: includes/register-kinds.php:4531818 msgid "RSVPed"1819 msgstr ""1820 1821 #: includes/register-kinds.php:4681822 msgid "Event Details"1823 msgstr ""1824 1825 #: includes/register-kinds.php:4881826 msgid "Start Date"1827 msgstr ""1828 1829 #: includes/register-kinds.php:4921830 msgid "End Date"1831 msgstr ""1832 1833 #: includes/register-kinds.php:5121834 msgid "a specific type of reply regarding attendance of an event"1835 msgstr ""1836 1837 #: includes/register-kinds.php:5231838 msgid "Listen"1839 msgstr ""1840 1841 #: includes/register-kinds.php:5241842 msgid "Listens"1843 msgstr ""1844 1845 #: includes/register-kinds.php:5251846 msgid "Listened"1847 msgstr ""1848 1849 #: includes/register-kinds.php:5301850 msgid "Listen To"1851 msgstr ""1852 1853 #: includes/register-kinds.php:5531854 msgid "Album"1855 msgstr ""1856 1857 #: includes/register-kinds.php:5571858 #: includes/register-kinds.php:6221859 msgid "Release Date"1860 msgstr ""1861 1862 #: includes/register-kinds.php:5681863 msgid "Album Art"1864 msgstr ""1865 1866 #: includes/register-kinds.php:5771867 msgid "listening to audio; sometimes called a scrobble"1868 msgstr ""1869 1870 #: includes/register-kinds.php:5881871 msgid "Watch"1872 msgstr ""1873 1874 #: includes/register-kinds.php:5891875 msgid "Watches"1876 msgstr ""1877 1878 #: includes/register-kinds.php:5901879 msgid "Watched"1880 msgstr ""1881 1882 #: includes/register-kinds.php:5951883 msgid "Watching To"1884 msgstr ""1885 1886 #: includes/register-kinds.php:6181887 msgid "Series"1888 msgstr ""1889 1890 #: includes/register-kinds.php:6331891 msgid "Poster"1892 msgstr ""1893 1894 #: includes/register-kinds.php:6421895 msgid "watching a movie, television show, online video, play or other visual-based event"1896 msgstr ""1897 1898 #: includes/register-kinds.php:6531899 msgid "Checkin"1900 msgstr ""1901 1902 #: includes/register-kinds.php:6541903 msgid "Checkins"1904 msgstr ""1905 1906 #: includes/register-kinds.php:6551907 1962 msgid "Checked into" 1908 1963 msgstr "" 1909 1964 1910 #: includes/register-kinds.php:66 01965 #: includes/register-kinds.php:669 1911 1966 msgid "Checked In" 1912 1967 msgstr "" 1913 1968 1914 #: includes/register-kinds.php:6 721969 #: includes/register-kinds.php:681 1915 1970 msgid "Latitude" 1916 1971 msgstr "" 1917 1972 1918 #: includes/register-kinds.php:68 01973 #: includes/register-kinds.php:689 1919 1974 msgid "Longitude" 1920 1975 msgstr "" 1921 1976 1922 #: includes/register-kinds.php:6 881977 #: includes/register-kinds.php:697 1923 1978 msgid "Street Address" 1924 1979 msgstr "" 1925 1980 1926 #: includes/register-kinds.php: 6921981 #: includes/register-kinds.php:701 1927 1982 msgid "Locality" 1928 1983 msgstr "" 1929 1984 1930 #: includes/register-kinds.php: 6961985 #: includes/register-kinds.php:705 1931 1986 msgid "Region" 1932 1987 msgstr "" 1933 1988 1934 #: includes/register-kinds.php:70 01989 #: includes/register-kinds.php:709 1935 1990 msgid "Country Name" 1936 1991 msgstr "" 1937 1992 1938 #: includes/register-kinds.php:7 041993 #: includes/register-kinds.php:713 1939 1994 msgid "Postal Code" 1940 1995 msgstr "" 1941 1996 1942 #: includes/register-kinds.php:71 01997 #: includes/register-kinds.php:719 1943 1998 msgid "identifying you are at a particular geographic location" 1944 1999 msgstr "" 1945 2000 1946 #: includes/register-kinds.php:7 212001 #: includes/register-kinds.php:730 1947 2002 msgid "Wish" 1948 2003 msgstr "" 1949 2004 1950 #: includes/register-kinds.php:7 222005 #: includes/register-kinds.php:731 1951 2006 msgid "Wishes" 1952 2007 msgstr "" 1953 2008 1954 #: includes/register-kinds.php:7 232009 #: includes/register-kinds.php:732 1955 2010 msgid "Wished" 1956 2011 msgstr "" 1957 2012 1958 #: includes/register-kinds.php:7 262013 #: includes/register-kinds.php:735 1959 2014 msgid "a post indicating a desire/wish. The archive of which would be a wishlist, such as a gift registry or similar" 1960 2015 msgstr "" 1961 2016 1962 #: includes/register-kinds.php:7 372017 #: includes/register-kinds.php:746 1963 2018 msgid "Play" 1964 2019 msgstr "" 1965 2020 1966 #: includes/register-kinds.php:7 382021 #: includes/register-kinds.php:747 1967 2022 msgid "Playing" 1968 2023 msgstr "" 1969 2024 1970 #: includes/register-kinds.php:7 392025 #: includes/register-kinds.php:748 1971 2026 msgid "Played" 1972 2027 msgstr "" 1973 2028 1974 #: includes/register-kinds.php:7 422029 #: includes/register-kinds.php:751 1975 2030 msgid "playing a game" 1976 2031 msgstr "" 1977 2032 1978 #: includes/register-kinds.php:7 531979 #: includes/register-kinds.php:7 542033 #: includes/register-kinds.php:762 2034 #: includes/register-kinds.php:763 1980 2035 msgid "Weather" 1981 2036 msgstr "" 1982 2037 1983 #: includes/register-kinds.php:7 582038 #: includes/register-kinds.php:767 1984 2039 msgid "current weather conditions" 1985 2040 msgstr "" 1986 2041 1987 #: includes/register-kinds.php:7 691988 #: includes/register-kinds.php:77 02042 #: includes/register-kinds.php:778 2043 #: includes/register-kinds.php:779 1989 2044 msgid "Exercise" 1990 2045 msgstr "" 1991 2046 1992 #: includes/register-kinds.php:7 712047 #: includes/register-kinds.php:780 1993 2048 msgid "Exercised" 1994 2049 msgstr "" 1995 2050 1996 #: includes/register-kinds.php:7 742051 #: includes/register-kinds.php:783 1997 2052 msgid "some form of physical activity or workout (examples: walk, run, cycle, hike, yoga, etc.)" 1998 2053 msgstr "" 1999 2054 2000 #: includes/register-kinds.php:7 852055 #: includes/register-kinds.php:794 2001 2056 msgid "Trip" 2002 2057 msgstr "" 2003 2058 2004 #: includes/register-kinds.php:7 862059 #: includes/register-kinds.php:795 2005 2060 msgid "Trips" 2006 2061 msgstr "" 2007 2062 2008 #: includes/register-kinds.php:7 872009 #: includes/register-kinds.php:8 032063 #: includes/register-kinds.php:796 2064 #: includes/register-kinds.php:812 2010 2065 msgid "Travelled" 2011 2066 msgstr "" 2012 2067 2013 #: includes/register-kinds.php:79 02068 #: includes/register-kinds.php:799 2014 2069 msgid "represents a geographic journey" 2015 2070 msgstr "" 2016 2071 2017 #: includes/register-kinds.php:8 012072 #: includes/register-kinds.php:810 2018 2073 msgid "Itinerary" 2019 2074 msgstr "" 2020 2075 2021 #: includes/register-kinds.php:8 022076 #: includes/register-kinds.php:811 2022 2077 msgid "Itineraries" 2023 2078 msgstr "" 2024 2079 2025 #: includes/register-kinds.php:8 062080 #: includes/register-kinds.php:815 2026 2081 msgid "parts of a scheduled trip including transit by car, plane, train, etc." 2027 2082 msgstr "" 2028 2083 2029 #: includes/register-kinds.php:8 182030 #: includes/register-kinds.php:8 192084 #: includes/register-kinds.php:827 2085 #: includes/register-kinds.php:828 2031 2086 msgid "Eat" 2032 2087 msgstr "" 2033 2088 2034 #: includes/register-kinds.php:82 02089 #: includes/register-kinds.php:829 2035 2090 msgid "Ate" 2036 2091 msgstr "" 2037 2092 2038 #: includes/register-kinds.php:8 232093 #: includes/register-kinds.php:832 2039 2094 msgid "what you are eating, perhaps for a food diary" 2040 2095 msgstr "" 2041 2096 2042 #: includes/register-kinds.php:8 352097 #: includes/register-kinds.php:844 2043 2098 msgid "Drink" 2044 2099 msgstr "" 2045 2100 2046 #: includes/register-kinds.php:8 362101 #: includes/register-kinds.php:845 2047 2102 msgid "Drinks" 2048 2103 msgstr "" 2049 2104 2050 #: includes/register-kinds.php:8 372105 #: includes/register-kinds.php:846 2051 2106 msgid "Drank" 2052 2107 msgstr "" 2053 2108 2054 #: includes/register-kinds.php:84 02109 #: includes/register-kinds.php:849 2055 2110 msgid "what you are drinking, perhaps for a food dairy" 2056 2111 msgstr "" 2057 2112 2058 #: includes/register-kinds.php:8 522113 #: includes/register-kinds.php:861 2059 2114 msgid "Follow" 2060 2115 msgstr "" 2061 2116 2062 #: includes/register-kinds.php:8 532117 #: includes/register-kinds.php:862 2063 2118 msgid "Follows" 2064 2119 msgstr "" 2065 2120 2066 #: includes/register-kinds.php:8 542121 #: includes/register-kinds.php:863 2067 2122 msgid "Followed" 2068 2123 msgstr "" 2069 2124 2070 #: includes/register-kinds.php:8 572125 #: includes/register-kinds.php:866 2071 2126 msgid "indicating you are now following or subscribing to another person`s activities online" 2072 2127 msgstr "" 2073 2128 2074 #: includes/register-kinds.php:8 692129 #: includes/register-kinds.php:878 2075 2130 msgid "Jam" 2076 2131 msgstr "" 2077 2132 2078 #: includes/register-kinds.php:87 02133 #: includes/register-kinds.php:879 2079 2134 msgid "Jams" 2080 2135 msgstr "" 2081 2136 2082 #: includes/register-kinds.php:8 712137 #: includes/register-kinds.php:880 2083 2138 msgid "Listened to" 2084 2139 msgstr "" 2085 2140 2086 #: includes/register-kinds.php:8 742141 #: includes/register-kinds.php:883 2087 2142 msgid "a particularly personally meaningful song (a listen with added emphasis)" 2088 2143 msgstr "" 2089 2144 2090 #: includes/register-kinds.php:8 862091 #: includes/register-kinds.php:8 882145 #: includes/register-kinds.php:895 2146 #: includes/register-kinds.php:897 2092 2147 msgid "Read" 2093 2148 msgstr "" 2094 2149 2095 #: includes/register-kinds.php:8 872150 #: includes/register-kinds.php:896 2096 2151 msgid "Reads" 2097 2152 msgstr "" 2098 2153 2099 #: includes/register-kinds.php: 8912154 #: includes/register-kinds.php:900 2100 2155 msgid "reading a book, magazine, newspaper, other physical document, or online post" 2101 2156 msgstr "" 2102 2157 2103 #: includes/register-kinds.php:9 032158 #: includes/register-kinds.php:912 2104 2159 msgid "Quote" 2105 2160 msgstr "" 2106 2161 2107 #: includes/register-kinds.php:9 042162 #: includes/register-kinds.php:913 2108 2163 msgid "Quotes" 2109 2164 msgstr "" 2110 2165 2111 #: includes/register-kinds.php:9 052166 #: includes/register-kinds.php:914 2112 2167 msgid "Quoted" 2113 2168 msgstr "" 2114 2169 2115 #: includes/register-kinds.php:9 082170 #: includes/register-kinds.php:917 2116 2171 msgid "quoted content" 2117 2172 msgstr "" 2118 2173 2119 #: includes/register-kinds.php:92 02174 #: includes/register-kinds.php:929 2120 2175 msgid "Mood" 2121 2176 msgstr "" 2122 2177 2123 #: includes/register-kinds.php:9 212178 #: includes/register-kinds.php:930 2124 2179 msgid "Moods" 2125 2180 msgstr "" 2126 2181 2127 #: includes/register-kinds.php:9 222182 #: includes/register-kinds.php:931 2128 2183 msgid "Felt" 2129 2184 msgstr "" 2130 2185 2131 #: includes/register-kinds.php:9 252186 #: includes/register-kinds.php:934 2132 2187 msgid "how you are feeling (example: happy, sad, indifferent, etc.)" 2133 2188 msgstr "" 2134 2189 2135 #: includes/register-kinds.php:9 372190 #: includes/register-kinds.php:946 2136 2191 msgid "Recipe" 2137 2192 msgstr "" 2138 2193 2139 #: includes/register-kinds.php:9 382194 #: includes/register-kinds.php:947 2140 2195 msgid "Recipes" 2141 2196 msgstr "" 2142 2197 2143 #: includes/register-kinds.php:9 392198 #: includes/register-kinds.php:948 2144 2199 msgid "Cooked" 2145 2200 msgstr "" 2146 2201 2147 #: includes/register-kinds.php:9 422202 #: includes/register-kinds.php:951 2148 2203 msgid "list of ingredients and directions for making food or drink" 2149 2204 msgstr "" 2150 2205 2151 #: includes/register-kinds.php:9 542206 #: includes/register-kinds.php:963 2152 2207 msgid "Issue" 2153 2208 msgstr "" 2154 2209 2155 #: includes/register-kinds.php:9 552210 #: includes/register-kinds.php:964 2156 2211 msgid "Issues" 2157 2212 msgstr "" 2158 2213 2159 #: includes/register-kinds.php:9 562214 #: includes/register-kinds.php:965 2160 2215 msgid "Filed an Issue" 2161 2216 msgstr "" 2162 2217 2163 #: includes/register-kinds.php:9 592218 #: includes/register-kinds.php:968 2164 2219 msgid "Issue is a special kind of article post that is a reply to typically some source code, though potentially anything at a source control repository." 2165 2220 msgstr "" 2166 2221 2167 #: includes/register-kinds.php:9 712222 #: includes/register-kinds.php:980 2168 2223 msgid "Question" 2169 2224 msgstr "" 2170 2225 2171 #: includes/register-kinds.php:9 722226 #: includes/register-kinds.php:981 2172 2227 msgid "Questions" 2173 2228 msgstr "" 2174 2229 2175 #: includes/register-kinds.php:9 732230 #: includes/register-kinds.php:982 2176 2231 msgid "Asked a question" 2177 2232 msgstr "" 2178 2233 2179 #: includes/register-kinds.php:9 762234 #: includes/register-kinds.php:985 2180 2235 msgid "Question is a post type for soliciting answer replies, which are then typically up/down voted by others and then displayed underneath the question post ordered by highest positive vote count rather than time ordered." 2181 2236 msgstr "" 2182 2237 2183 #: includes/register-kinds.php:9 882238 #: includes/register-kinds.php:997 2184 2239 msgid "Sleep" 2185 2240 msgstr "" 2186 2241 2187 #: includes/register-kinds.php:9 892242 #: includes/register-kinds.php:998 2188 2243 msgid "Sleeps" 2189 2244 msgstr "" 2190 2245 2191 #: includes/register-kinds.php:99 02246 #: includes/register-kinds.php:999 2192 2247 msgid "Slept" 2193 2248 msgstr "" 2194 2249 2195 #: includes/register-kinds.php: 9932250 #: includes/register-kinds.php:1002 2196 2251 msgid "Sleep is a passive metrics post type that indicates how much time (and often a graph of how deeply) a person has slept." 2197 2252 msgstr "" 2198 2253 2199 #: includes/register-kinds.php:10 052254 #: includes/register-kinds.php:1014 2200 2255 msgid "Event" 2201 2256 msgstr "" 2202 2257 2203 #: includes/register-kinds.php:10 062258 #: includes/register-kinds.php:1015 2204 2259 msgid "Events" 2205 2260 msgstr "" 2206 2261 2207 #: includes/register-kinds.php:10 072262 #: includes/register-kinds.php:1016 2208 2263 msgid "Planned" 2209 2264 msgstr "" 2210 2265 2211 #: includes/register-kinds.php:101 02266 #: includes/register-kinds.php:1019 2212 2267 msgid "An event is a type of post that in addition to a post name (event title) has a start datetime (likely end datetime), and a location." 2213 2268 msgstr "" 2214 2269 2215 #: includes/register-kinds.php:10 212270 #: includes/register-kinds.php:1030 2216 2271 msgid "Review" 2217 2272 msgstr "" 2218 2273 2219 #: includes/register-kinds.php:10 222274 #: includes/register-kinds.php:1031 2220 2275 msgid "Reviews" 2221 2276 msgstr "" 2222 2277 2223 #: includes/register-kinds.php:10 232278 #: includes/register-kinds.php:1032 2224 2279 msgid "Reviewed" 2225 2280 msgstr "" 2226 2281 2227 #: includes/register-kinds.php:10 262282 #: includes/register-kinds.php:1035 2228 2283 msgid "A review is a post evaluating a product or service, usually involving a written description, sometimes with summary numerical evaluations, also known as just a rating." 2229 2284 msgstr "" 2230 2285 2231 #: includes/register-kinds.php:10 382286 #: includes/register-kinds.php:1047 2232 2287 msgid "Acquisition" 2233 2288 msgstr "" 2234 2289 2235 #: includes/register-kinds.php:10 392290 #: includes/register-kinds.php:1048 2236 2291 msgid "Acquisitions" 2237 2292 msgstr "" 2238 2293 2239 #: includes/register-kinds.php:104 02294 #: includes/register-kinds.php:1049 2240 2295 msgid "Acquired" 2241 2296 msgstr "" 2242 2297 2243 #: includes/register-kinds.php:10 432298 #: includes/register-kinds.php:1052 2244 2299 msgid "Purchases, gifts, found things, or objects donated" 2245 2300 msgstr "" 2246 2301 2247 #: includes/register-kinds.php:10 542302 #: includes/register-kinds.php:1063 2248 2303 msgid "Craft" 2249 2304 msgstr "" 2250 2305 2251 #: includes/register-kinds.php:10 552306 #: includes/register-kinds.php:1064 2252 2307 msgid "Crafts" 2253 2308 msgstr "" 2254 2309 2255 #: includes/register-kinds.php:10 562310 #: includes/register-kinds.php:1065 2256 2311 msgid "Crafted" 2257 2312 msgstr "" 2258 2313 2259 #: includes/register-kinds.php:10 592314 #: includes/register-kinds.php:1068 2260 2315 msgid "Activities like knitting, crocheting, cross stitch, wood working, restoration, 3d printing...the activity of building something." 2261 2316 msgstr "" 2262 2317 2263 #: indieweb-post-kinds.php: 652318 #: indieweb-post-kinds.php:87 2264 2319 msgid "Parse This is not installed. Please advise the developer" 2265 2320 msgstr "" 2266 2321 2267 #: indieweb-post-kinds.php:98 2322 #. translators: %s: Name of the plugin, or path of the file, that loaded the older copy of Parse This. 2323 #: indieweb-post-kinds.php:109 2324 #, php-format 2325 msgid "Post Kinds: %s is loading a copy of Parse This older than version 2.0. Post Kinds will keep working, but URL lookups in the post editor use that older copy. Updating that plugin, or installing the Parse This plugin (version 2.0 or later), resolves this." 2326 msgstr "" 2327 2328 #: indieweb-post-kinds.php:209 2268 2329 msgid "Classic Editor or Disable Gutenberg Plugin is not active. The Post Kinds plugin will not function correctly at this time without using the Classic Editor." 2269 2330 msgstr "" 2270 2331 2271 #: indieweb-post-kinds.php: 2112332 #: indieweb-post-kinds.php:353 2272 2333 msgid "Settings" 2273 2334 msgstr "" 2274 2335 2275 #: indieweb-post-kinds.php: 2332336 #: indieweb-post-kinds.php:384 2276 2337 msgid "For responses to URLs, such as responding to a post or article, this site allows the storage of data around the post/article in order to generate a rich citation. Items such as author name and image, summary of the text, embed provided by third-party site, etc may be stored and are solely to provide this context. We will remove any of this on request." 2277 2338 msgstr "" 2278 2339 2279 #: lib/parse-this/includes/class-parse-this-discovery.php:57 2280 #: lib/parse-this/includes/class-parse-this-opml.php:17 2281 #: lib/parse-this/includes/class-parse-this.php:231 2282 #: lib/parse-this/includes/class-parse-this.php:265 2283 msgid "A valid URL was not provided." 2284 msgstr "" 2285 2286 #: lib/parse-this/includes/class-rest-parse-this.php:19 2287 #: lib/parse-this/includes/class-rest-parse-this.php:20 2288 msgid "Parse This" 2289 msgstr "" 2290 2291 #: lib/parse-this/includes/class-rest-parse-this.php:35 2292 msgid "Parse This Debugger" 2293 msgstr "" 2294 2295 #: lib/parse-this/includes/class-rest-parse-this.php:55 2296 msgid "MF2" 2297 msgstr "" 2298 2299 #: lib/parse-this/includes/class-rest-parse-this.php:63 2300 msgid "Feed Discovery" 2301 msgstr "" 2302 2303 #: lib/parse-this/includes/class-rest-parse-this.php:71 2304 msgid "References" 2305 msgstr "" 2306 2307 #: lib/parse-this/includes/class-rest-parse-this.php:79 2308 msgid "Clean up Location" 2309 msgstr "" 2310 2311 #: lib/parse-this/includes/class-rest-parse-this.php:87 2312 msgid "Return Type" 2313 msgstr "" 2314 2315 #: lib/parse-this/includes/class-rest-parse-this.php:91 2316 msgid "Single" 2317 msgstr "" 2318 2319 #: lib/parse-this/includes/class-rest-parse-this.php:92 2320 msgid "Feed" 2321 msgstr "" 2322 2323 #: lib/parse-this/includes/class-rest-parse-this.php:98 2324 msgid "Follow Author Links" 2325 msgstr "" 2326 2327 #: lib/parse-this/includes/class-rest-parse-this.php:107 2328 msgid "Parse" 2329 msgstr "" 2330 2331 #: templates/reply-author.php:2 2340 #: templates/reply-author.php:16 2332 2341 msgid "Information on the Author or Artist of the Piece" 2333 2342 msgstr "" 2334 2343 2335 #: templates/reply-author.php: 32344 #: templates/reply-author.php:17 2336 2345 msgid "(Multiple Entries separated by semicolon)" 2337 2346 msgstr "" 2338 2347 2339 #: templates/reply-author.php: 182348 #: templates/reply-author.php:32 2340 2349 msgid "Author Photo URL" 2341 2350 msgstr "" 2342 2351 2343 #: templates/reply-details.php: 22352 #: templates/reply-details.php:17 2344 2353 msgid "Information on what you are responding to" 2345 2354 msgstr "" 2346 2355 2347 #: templates/reply-details.php: 42356 #: templates/reply-details.php:19 2348 2357 msgid "Summary/Quote/Caption" 2349 2358 msgstr "" 2350 2359 2351 #: templates/reply-details.php: 92360 #: templates/reply-details.php:24 2352 2361 msgid "Site Name/Publication/Album" 2353 2362 msgstr "" 2354 2363 2355 #: templates/reply-details.php: 142364 #: templates/reply-details.php:29 2356 2365 msgid "Published/Released" 2357 2366 msgstr "" 2358 2367 2359 #: templates/reply-details.php: 152368 #: templates/reply-details.php:30 2360 2369 msgid "Updated" 2361 2370 msgstr "" 2362 2371 2363 #: templates/reply-details.php: 182372 #: templates/reply-details.php:33 2364 2373 msgid "Tags (semicolon separated)" 2365 2374 msgstr "" 2366 2375 2367 #: templates/reply-details.php: 232376 #: templates/reply-details.php:38 2368 2377 msgid "Featured Image" 2369 2378 msgstr "" 2370 2379 2371 #: templates/reply-metabox.php: 302380 #: templates/reply-metabox.php:44 2372 2381 msgid "Details" 2373 2382 msgstr "" 2374 2383 2375 #: templates/reply-metabox.php:33 2384 #: templates/reply-metabox.php:46 2385 msgid "Upload or Attach Media" 2386 msgstr "" 2387 2388 #: templates/reply-metabox.php:47 2376 2389 msgid "Clear" 2377 2390 msgstr "" 2378 2391 2379 #: templates/reply-time.php: 42392 #: templates/reply-time.php:18 2380 2393 msgid "Duration" 2381 2394 msgstr "" 2382 2395 2383 #: templates/reply-time.php: 72396 #: templates/reply-time.php:21 2384 2397 msgid "Years" 2385 2398 msgstr "" 2386 2399 2387 #: templates/reply-time.php: 112400 #: templates/reply-time.php:25 2388 2401 msgid "Months" 2389 2402 msgstr "" 2390 2403 2391 #: templates/reply-time.php: 152404 #: templates/reply-time.php:29 2392 2405 msgid "Days" 2393 2406 msgstr "" 2394 2407 2395 #: templates/reply-time.php: 212408 #: templates/reply-time.php:35 2396 2409 msgid "Hours" 2397 2410 msgstr "" 2398 2411 2399 #: templates/reply-time.php: 252412 #: templates/reply-time.php:39 2400 2413 msgid "Minutes" 2401 2414 msgstr "" 2402 2415 2403 #: templates/reply-time.php: 292416 #: templates/reply-time.php:43 2404 2417 msgid "Seconds" 2405 2418 msgstr "" 2406 2419 2407 #: templates/reply-time.php: 332420 #: templates/reply-time.php:47 2408 2421 msgid "Start Time and End Time will be Used to Calculate Duration if Duration Not Set." 2409 2422 msgstr "" 2410 2423 2411 #: templates/reply-time.php: 352424 #: templates/reply-time.php:49 2412 2425 msgid "Start Time" 2413 2426 msgstr "" 2414 2427 2415 #: templates/reply-time.php: 362428 #: templates/reply-time.php:50 2416 2429 msgid "End Time" 2417 2430 msgstr "" 2418 2431 2419 #: views/kind-bookmark.php: 252420 #: views/kind-favorite.php: 242421 #: views/kind-issue.php: 242422 #: views/kind-jam.php: 282423 #: views/kind-like.php: 252424 #: views/kind-listen.php: 302425 #: views/kind-read.php: 282426 #: views/kind-video.php: 452427 #: views/kind-watch.php: 292428 #: views/kind.php: 352432 #: views/kind-bookmark.php:39 2433 #: views/kind-favorite.php:38 2434 #: views/kind-issue.php:38 2435 #: views/kind-jam.php:46 2436 #: views/kind-like.php:39 2437 #: views/kind-listen.php:48 2438 #: views/kind-read.php:42 2439 #: views/kind-video.php:63 2440 #: views/kind-watch.php:47 2441 #: views/kind.php:53 2429 2442 msgid "by" 2430 2443 msgstr "" 2431 2444 2432 #: views/kind-event.php: 222445 #: views/kind-event.php:39 2433 2446 msgid "Start" 2434 2447 msgstr "" 2435 2448 2436 #: views/kind-event.php: 252449 #: views/kind-event.php:42 2437 2450 msgid "End" 2438 2451 msgstr "" 2439 2452 2440 #: views/kind-itinerary.php: 332453 #: views/kind-itinerary.php:56 2441 2454 msgid "Departs: " 2442 2455 msgstr "" 2443 2456 2444 #: views/kind-itinerary.php: 382457 #: views/kind-itinerary.php:61 2445 2458 msgid "Arrives: " 2446 2459 msgstr "" 2447 2460 2448 #: views/kind-jam.php: 312449 #: views/kind-listen.php: 332450 #: views/kind-watch.php: 322461 #: views/kind-jam.php:49 2462 #: views/kind-listen.php:51 2463 #: views/kind-watch.php:50 2451 2464 msgid " from " 2452 2465 msgstr "" -
indieweb-post-kinds/trunk/lib/parse-this/includes/autoload.php
r2332001 r3738687 1 1 <?php 2 /** 3 * Class autoloader for Parse This. 4 * 5 * @package Parse_This 6 */ 2 7 3 // autoloader for Parse This 8 defined( 'ABSPATH' ) || exit; 9 10 /* 11 * Maps classes in the ParseThis namespace to files in this directory, for 12 * example ParseThis\MF2_Utils to class-mf2-utils.php. 13 */ 4 14 spl_autoload_register( 5 15 function ( $class ) { 6 $base_dir = trailingslashit( __DIR__ ); 7 $bases = array( 'Parse_This' ); 8 foreach ( $bases as $base ) { 9 if ( strncmp( $class, $base, strlen( $base ) ) === 0 ) { 10 $filename = 'class-' . strtolower( str_replace( '_', '-', $class ) ); 11 $file = $base_dir . $filename . '.php'; 12 if ( file_exists( $file ) ) { 13 require $file; 14 } 15 } 16 $prefix = 'ParseThis\\'; 17 if ( 0 !== strncmp( $class, $prefix, strlen( $prefix ) ) ) { 18 return; 19 } 20 $name = substr( $class, strlen( $prefix ) ); 21 $file = __DIR__ . '/class-' . strtolower( str_replace( '_', '-', $name ) ) . '.php'; 22 if ( file_exists( $file ) ) { 23 require $file; 16 24 } 17 25 } 18 26 ); 27 28 // Deprecated global names still used by other plugins. 29 require_once __DIR__ . '/aliases.php'; -
indieweb-post-kinds/trunk/lib/parse-this/includes/functions.php
r2757240 r3738687 1 1 <?php 2 // Parse This Global Functions 3 4 5 if ( ! function_exists( 'jf2_to_mf2' ) ) { 2 /** 3 * Helper functions. 4 * 5 * All functions are in the ParseThis namespace. Each is wrapped in 6 * function_exists() because several plugins bundle their own copy of Parse 7 * This; the first copy loaded wins. Deprecated global names for some of them 8 * are defined in aliases.php. 9 * 10 * @package Parse_This 11 */ 12 13 namespace ParseThis; 14 15 defined( 'ABSPATH' ) || exit; 16 17 if ( ! function_exists( __NAMESPACE__ . '\\jf2_to_mf2' ) ) { 18 /** 19 * Converts jf2 into microformats2 JSON. 20 * 21 * The inverse of mf2_to_jf2(). Empty values and the _raw key are dropped. 22 * 23 * @since 1.0.0 24 * 25 * @link https://jf2.spec.indieweb.org/ 26 * 27 * @param array|mixed $jf2 jf2 object, or array( 'items' => ... ) for a list. 28 * @return array|mixed mf2 object with type and properties. Values that are not 29 * jf2 objects are returned unchanged. 30 */ 6 31 function jf2_to_mf2( $jf2 ) { 7 32 if ( ! $jf2 || ! is_array( $jf2 ) ) { … … 10 35 if ( 1 === count( $jf2 ) && array_key_exists( 'items', $jf2 ) ) { 11 36 return array( 12 'items' => array_map( 'jf2_to_mf2', $jf2['items'] ),37 'items' => array_map( __NAMESPACE__ . '\\jf2_to_mf2', $jf2['items'] ), 13 38 ); 14 39 } … … 24 49 } 25 50 if ( array_key_exists( 'children', $jf2 ) ) { 26 $mf2['children'] = array_map( 'jf2_to_mf2', $jf2['children'] );51 $mf2['children'] = array_map( __NAMESPACE__ . '\\jf2_to_mf2', $jf2['children'] ); 27 52 unset( $jf2['children'] ); 28 53 } … … 31 56 32 57 foreach ( $jf2 as $key => $value ) { 33 // Exclude values 58 // Exclude values. 34 59 if ( empty( $value ) || ( '_raw' === $key ) ) { 35 60 continue; … … 38 63 $value = array( jf2_to_mf2( $value ) ); 39 64 } elseif ( wp_is_numeric_array( $value ) ) { 40 $value = array_map( 'jf2_to_mf2', $value );65 $value = array_map( __NAMESPACE__ . '\\jf2_to_mf2', $value ); 41 66 } elseif ( ! wp_is_numeric_array( $value ) ) { 42 67 $value = array( $value ); … … 48 73 } 49 74 50 if ( ! function_exists( 'mf2_to_jf2' ) ) { 51 75 if ( ! function_exists( __NAMESPACE__ . '\\mf2_to_jf2' ) ) { 76 77 /** 78 * Converts microformats2 JSON into jf2. 79 * 80 * Single-value property arrays are collapsed to the value, the h- prefix is 81 * removed from types, and nested microformats are converted recursively. 82 * 83 * @since 1.0.0 84 * 85 * @link https://jf2.spec.indieweb.org/ 86 * 87 * @param array|mixed $mf2 mf2 object, a parsed mf2 document with items, or a 88 * list of mf2 objects. 89 * @return array|mixed jf2. A list of one is returned as the single item. 90 * Strings and objects are returned unchanged. 91 */ 52 92 function mf2_to_jf2( $mf2 ) { 53 93 if ( empty( $mf2 ) || is_string( $mf2 ) || is_object( $mf2 ) ) { … … 57 97 $jf2 = array(); 58 98 59 // If it is a numeric array, run this function through each item 99 // If it is a numeric array, run this function through each item. 60 100 if ( wp_is_numeric_array( $mf2 ) ) { 61 $jf2 = array_map( 'mf2_to_jf2', $mf2 );101 $jf2 = array_map( __NAMESPACE__ . '\\mf2_to_jf2', $mf2 ); 62 102 if ( 1 === count( $jf2 ) ) { 63 103 return array_pop( $jf2 ); … … 67 107 68 108 if ( isset( $mf2['items'] ) ) { 69 $jf2['items'] = array_map( 'mf2_to_jf2', $mf2['items'] );109 $jf2['items'] = array_map( __NAMESPACE__ . '\\mf2_to_jf2', $mf2['items'] ); 70 110 } 71 111 72 112 if ( isset( $mf2['children'] ) ) { 73 $jf2['children'] = array_map( 'mf2_to_jf2', $mf2['children'] );113 $jf2['children'] = array_map( __NAMESPACE__ . '\\mf2_to_jf2', $mf2['children'] ); 74 114 } 75 115 … … 82 122 if ( is_array( $value ) ) { 83 123 if ( wp_is_numeric_array( $value ) ) { 84 $value = array_map( 'mf2_to_jf2', $value );124 $value = array_map( __NAMESPACE__ . '\\mf2_to_jf2', $value ); 85 125 if ( is_countable( $value ) && 1 === count( $value ) ) { 86 126 $value = array_pop( $value ); … … 98 138 99 139 100 if ( ! function_exists( 'jf2_location' ) ) { 101 /* 102 Flatten nested location properties. 103 */ 140 if ( ! function_exists( __NAMESPACE__ . '\\jf2_location' ) ) { 141 /** 142 * Flattens a nested jf2 location. 143 * 144 * The latitude, longitude and altitude are copied onto the object, and location 145 * becomes the place's label or name (or is removed). Any check-in on the 146 * object also receives the location's properties. 147 * 148 * @since 1.0.1 149 * 150 * @param array $data jf2 object. 151 * @return array The updated object. 152 */ 104 153 function jf2_location( $data ) { 105 154 if ( ! array_key_exists( 'location', $data ) ) { 106 155 return $data; 107 156 } 108 $location = $data['location']; 109 if ( is_string( $location ) ) { 157 // jf2_references() may have moved the location or check-in to refs, 158 // leaving its URL (as a one-item list) in its place. 159 $from_refs = function ( $value ) use ( $data ) { 160 if ( is_array( $value ) && wp_is_numeric_array( $value ) && 1 === count( $value ) ) { 161 $value = $value[0]; 162 } 163 if ( is_string( $value ) && isset( $data['refs'][ $value ] ) && is_array( $data['refs'][ $value ] ) ) { 164 return $data['refs'][ $value ]; 165 } 166 return $value; 167 }; 168 $location = $from_refs( $data['location'] ); 169 if ( ! is_array( $location ) || wp_is_numeric_array( $location ) ) { 110 170 return $data; 111 171 } … … 122 182 unset( $data['location'] ); 123 183 } 184 if ( isset( $data['checkin'] ) ) { 185 $data['checkin'] = $from_refs( $data['checkin'] ); 186 } 124 187 if ( array_key_exists( 'checkin', $data ) && is_array( $data['checkin'] ) ) { 125 188 foreach ( $location as $key => $value ) { … … 134 197 135 198 136 if ( ! function_exists( 'jf2_references' ) ) { 137 /* 138 Turns nested properties into references per the jf2 spec 139 */ 199 if ( ! function_exists( __NAMESPACE__ . '\\jf2_author_to_card' ) ) { 200 /** 201 * Converts an author value into a jf2 card. 202 * 203 * Microsub requires author to always be a card, and jf2 consumers generally 204 * expect one. A URL string becomes a card with that url, any other string a 205 * card with that name, and an object without a type gets type card. A list of 206 * authors is converted item by item; a list of one becomes that card. 207 * 208 * @since 2.0.0 209 * 210 * @param mixed $author Author: a string, an object or a list of either. 211 * @return array|null The card (or list of cards), or null if there is no author. 212 */ 213 function jf2_author_to_card( $author ) { 214 if ( is_string( $author ) ) { 215 $author = trim( $author ); 216 if ( '' === $author ) { 217 return null; 218 } 219 $key = ( preg_match( '#^https?://#i', $author ) && filter_var( $author, FILTER_VALIDATE_URL ) ) ? 'url' : 'name'; 220 return array( 221 'type' => 'card', 222 $key => $author, 223 ); 224 } 225 if ( ! is_array( $author ) || empty( $author ) ) { 226 return null; 227 } 228 if ( wp_is_numeric_array( $author ) ) { 229 $cards = array_values( array_filter( array_map( __NAMESPACE__ . '\\jf2_author_to_card', $author ) ) ); 230 if ( empty( $cards ) ) { 231 return null; 232 } 233 return ( 1 === count( $cards ) ) ? $cards[0] : $cards; 234 } 235 if ( ! isset( $author['type'] ) ) { 236 $author = array( 'type' => 'card' ) + $author; 237 } 238 return $author; 239 } 240 } 241 242 if ( ! function_exists( __NAMESPACE__ . '\\jf2_references' ) ) { 243 /** 244 * Moves nested objects into refs, per the jf2 spec. 245 * 246 * Any nested typed object with an http or https URL (an h-cite, h-card, 247 * h-event, h-product or any other type) is replaced by its URL, and stored 248 * in refs keyed by that URL, so the result is one level deep. The property 249 * becomes a list of values, as before. Objects without such a URL stay in place, authors stay 250 * cards, and a feed's items are left alone. References that a nested object 251 * carries are merged into the same refs. 252 * 253 * @since 1.0.0 254 * @since 2.0.0 Moves any typed object with a URL, not only citations, and 255 * merges nested refs. 256 * 257 * @link https://jf2.spec.indieweb.org/#references 258 * 259 * @param array $data jf2 object. 260 * @return array The updated object. 261 */ 140 262 function jf2_references( $data ) { 263 if ( ! is_array( $data ) ) { 264 return $data; 265 } 266 // Authors stay cards, and a feed's entries are not references. 267 $skip = array( 'refs', 'author', 'items', 'children' ); 141 268 foreach ( $data as $key => $val ) { 142 if ( ! is_array( $val ) ) {269 if ( ! is_array( $val ) || in_array( $key, $skip, true ) || ( is_string( $key ) && '_' === substr( $key, 0, 1 ) ) ) { 143 270 continue; 144 271 } 145 if ( ! wp_is_numeric_array( $val ) ) { 146 $val = array( $val ); 147 } 148 if ( wp_is_numeric_array( $val ) ) { 149 foreach ( $val as $value ) { 150 // Indicates nested type 151 if ( is_array( $value ) && array_key_exists( 'type', $value ) && 'cite' === $value['type'] ) { 152 if ( ! isset( $data['refs'] ) ) { 153 $data['refs'] = array(); 154 } 155 if ( isset( $value['url'] ) ) { 156 $data['refs'][ $value['url'] ] = $value; 157 $data[ $key ] = array( $value['url'] ); 158 } 272 $values = wp_is_numeric_array( $val ) ? $val : array( $val ); 273 $changed = false; 274 foreach ( $values as $i => $value ) { 275 if ( ! is_array( $value ) ) { 276 continue; 277 } 278 // A nested object may carry references of its own: keep one flat refs. 279 if ( isset( $value['refs'] ) && is_array( $value['refs'] ) ) { 280 foreach ( $value['refs'] as $ref_url => $ref ) { 281 $data['refs'][ $ref_url ] = isset( $data['refs'][ $ref_url ] ) ? $data['refs'][ $ref_url ] + $ref : $ref; 159 282 } 160 if ( 'category' === $key ) { 161 foreach ( $value as $k => $v ) { 162 if ( is_array( $v ) && array_key_exists( 'type', $v ) ) { 163 if ( ! isset( $data['refs'] ) ) { 164 $data['refs'] = array(); 165 } 166 if ( isset( $v['url'] ) ) { 167 $data['refs'][ $v['url'] ] = $v; 168 $data['category'][ $k ] = $v['url']; 169 } 170 } 171 } 172 } 173 } 283 unset( $value['refs'] ); 284 $values[ $i ] = $value; 285 $changed = true; 286 } 287 $url = $value['url'] ?? null; 288 if ( is_array( $url ) ) { 289 $url = reset( $url ); 290 } 291 // Any typed object with an http(s) URL is a reference. One without 292 // stays in place, where sanitizing can drop a bad URL and keep the rest. 293 if ( empty( $value['type'] ) || ! is_string( $value['type'] ) || ! is_string( $url ) || ! preg_match( '#^https?://#i', trim( $url ) ) ) { 294 continue; 295 } 296 $data['refs'][ $url ] = isset( $data['refs'][ $url ] ) ? $data['refs'][ $url ] + $value : $value; 297 $values[ $i ] = $url; 298 $changed = true; 299 } 300 if ( $changed ) { 301 $data[ $key ] = $values; 174 302 } 175 303 } … … 178 306 } 179 307 180 if ( ! function_exists( 'url_to_author' ) ) { 181 /** 182 * Examine a url and try to determine the author ID it represents. 183 * 184 * @param string $url Permalink to check. 185 * 186 * @return WP_User, or null on failure. 308 if ( ! function_exists( __NAMESPACE__ . '\\url_to_author' ) ) { 309 /** 310 * Returns the local user that an author archive URL belongs to. 311 * 312 * Handles ?author=N URLs and pretty author permalinks. 313 * 314 * @since 1.0.0 315 * 316 * @global WP_Rewrite $wp_rewrite WordPress rewrite component. 317 * 318 * @param string $url URL to check. 319 * @return WP_User|false|null The user, false if no user has that ID or slug, or 320 * null if the URL is not on this site or not an 321 * author URL. 187 322 */ 188 323 function url_to_author( $url ) { 189 324 global $wp_rewrite; 190 // check if url hase the same host 325 // check if url hase the same host. 191 326 if ( wp_parse_url( site_url(), PHP_URL_HOST ) !== wp_parse_url( $url, PHP_URL_HOST ) ) { 192 327 return null; 193 328 } 194 // first, check to see if there is a 'author=N' to match against 329 // first, check to see if there is a 'author=N' to match against. 195 330 if ( preg_match( '/[?&]author=(\d+)/i', $url, $values ) ) { 196 331 $id = absint( $values[1] ); … … 199 334 } 200 335 } 201 // check to see if we are using rewrite rules 336 // check to see if we are using rewrite rules. 202 337 $rewrite = $wp_rewrite->wp_rewrite_rules(); 203 // not using rewrite rules, and 'author=N' method failed, so we're out of options 338 // not using rewrite rules, and 'author=N' method failed, so we're out of options. 204 339 if ( empty( $rewrite ) ) { 205 340 return null; 206 341 } 207 // generate rewrite rule for the author url 342 // generate rewrite rule for the author url. 208 343 $author_rewrite = $wp_rewrite->get_author_permastruct(); 209 344 $author_regexp = str_replace( '%author%', '', $author_rewrite ); 210 // match the rewrite rule with the passed url 345 // match the rewrite rule with the passed url. 211 346 if ( preg_match( '/https?:\/\/(.+)' . preg_quote( $author_regexp, '/' ) . '([^\/]+)/i', $url, $match ) ) { 212 347 $user = get_user_by( 'slug', $match[2] ); … … 219 354 } 220 355 221 if ( ! function_exists( 'url_to_user' ) ) { 222 /** 223 * Get the user associated with a URL. 224 * 225 * @param string $url url to match 226 * @return WP_User $user Associated user, or null if no associated user 356 if ( ! function_exists( __NAMESPACE__ . '\\url_to_user' ) ) { 357 /** 358 * Returns the local user associated with a URL. 359 * 360 * The site's home URL maps to the IndieWeb plugin's default author when the 361 * IndieWeb plugin is active, otherwise to the only author if there is just 362 * one. Author archive URLs map to their author, and other URLs are matched 363 * against users' website field. 364 * 365 * @since 1.0.0 366 * 367 * @param string $url URL to check. 368 * @return WP_User|false|null The user, or null (or false) if none matches. 227 369 */ 228 370 function url_to_user( $url ) { … … 230 372 return null; 231 373 } 232 // Ensure has trailing slash 374 // Ensure has trailing slash. 233 375 $url = trailingslashit( $url ); 234 376 if ( ( 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ) ) && ( wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( $url, PHP_URL_HOST ) ) ) { 235 377 $url = set_url_scheme( $url, 'https' ); 236 378 } 237 // Try to save the expense of a search query if the URL is the site URL 379 // Try to save the expense of a search query if the URL is the site URL. 238 380 if ( home_url( '/' ) === $url ) { 239 // Use the Indieweb settings to set the default author 381 // Use the Indieweb settings to set the default author. 240 382 if ( class_exists( 'Indieweb_Plugin' ) && ( get_option( 'iw_single_author' ) || ! is_multi_author() ) ) { 241 383 return get_user_by( 'id', get_option( 'iw_default_author' ) ); 242 384 } 243 $users = get_users( array( ' who' => 'authors') );385 $users = get_users( array( 'capability' => array( 'edit_posts' ) ) ); 244 386 if ( 1 === count( $users ) ) { 245 387 return $users[0]; … … 247 389 return null; 248 390 } 249 // Check if this is a author post URL 391 // Check if this is a author post URL. 250 392 $user = url_to_author( $url ); 251 if ( $user instanceof WP_User ) {393 if ( $user instanceof \WP_User ) { 252 394 return $user; 253 395 } … … 257 399 ); 258 400 $users = get_users( $args ); 259 // check result 401 // check result. 260 402 if ( ! empty( $users ) ) { 261 403 return $users[0]; … … 265 407 } 266 408 267 if ( ! function_exists( 'ifset' ) ) { 268 /** 269 * If set, return otherwise false. 270 * 271 * @param type $var Check if set. 272 * @return $var|false Return either $var or $return. 273 */ 274 function ifset( &$var, $return = false ) { 275 276 return isset( $var ) ? $var : $return; 277 } 278 } 279 280 281 /* 282 Inverse of wp_parse_url 283 * 284 * Slightly modified from p3k-utils (https://github.com/aaronpk/p3k-utils) 285 * Copyright 2017 Aaron Parecki, used with permission under MIT License 286 * 287 * @link http://php.net/parse_url 288 * @param string $parsed_url the parsed URL (wp_parse_url) 289 * @return string the final URL 290 */ 291 if ( ! function_exists( 'build_url' ) ) { 409 if ( ! function_exists( __NAMESPACE__ . '\\build_url' ) ) { 410 /** 411 * Builds a URL from its parts; the inverse of wp_parse_url(). 412 * 413 * Slightly modified from p3k-utils. Copyright 2017 Aaron Parecki, used with 414 * permission under the MIT License. 415 * 416 * @since 1.0.0 417 * 418 * @link https://github.com/aaronpk/p3k-utils 419 * @link https://www.php.net/parse_url 420 * 421 * @param array $parsed_url URL components as returned by wp_parse_url(). 422 * @return string The URL. 423 */ 292 424 function build_url( $parsed_url ) { 293 425 $scheme = ! empty( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : ''; … … 306 438 307 439 308 if ( ! function_exists( 'normalize_url' ) ) { 309 // Adds slash if no path is in the URL, and convert hostname to lowercase 440 if ( ! function_exists( __NAMESPACE__ . '\\normalize_url' ) ) { 441 /** 442 * Normalizes a URL for comparison. 443 * 444 * Lowercases the host and adds a / path if there is none. 445 * 446 * @since 1.0.0 447 * 448 * @param string $url URL to normalize. 449 * @param bool $strip Optional. Whether to remove the query string. Default false. 450 * @return string|null The normalized URL, or null if it has no host. 451 */ 310 452 function normalize_url( $url, $strip = false ) { 311 453 $parts = wp_parse_url( $url ); … … 323 465 } 324 466 325 if ( ! function_exists( 'normalize_iso8601' ) ) { 326 // Tries to normalizes dates to a standard iso8601 string 467 if ( ! function_exists( __NAMESPACE__ . '\\normalize_iso8601' ) ) { 468 /** 469 * Normalizes a date string to W3C (ISO 8601) format. 470 * 471 * @since 1.0.0 472 * 473 * @param string|mixed $string Date string. 474 * @return string|null The date in W3C format, the original string if it cannot 475 * be parsed, or null if it is empty or not a string. 476 */ 327 477 function normalize_iso8601( $string ) { 328 $date = new DateTime( $string ); 329 if ( $date ) { 330 return $date->format( DATE_W3C ); 331 } 332 return $string; 333 } 334 } 335 336 if ( ! function_exists( 'post_type_discovery' ) ) { 478 if ( empty( $string ) || ! is_string( $string ) ) { 479 return null; 480 } 481 try { 482 $date = new \DateTime( $string ); 483 } catch ( \Exception $e ) { 484 return $string; 485 } 486 return $date->format( DATE_W3C ); 487 } 488 } 489 490 if ( ! function_exists( __NAMESPACE__ . '\\pt_find_fragment_element' ) ) { 491 /** 492 * Finds the element a URL fragment points to. 493 * 494 * @since 2.0.0 495 * 496 * @param DOMDocument $doc Parsed page. 497 * @param string $id Fragment, without the #. 498 * @return DOMElement|null The element with that id, or null. 499 */ 500 function pt_find_fragment_element( $doc, $id ) { 501 if ( ! $doc instanceof \DOMDocument || ! is_string( $id ) || '' === $id ) { 502 return null; 503 } 504 $id = rawurldecode( $id ); 505 foreach ( $doc->getElementsByTagName( '*' ) as $element ) { 506 if ( $element->getAttribute( 'id' ) === $id ) { 507 return $element; 508 } 509 } 510 return null; 511 } 512 } 513 514 if ( ! function_exists( __NAMESPACE__ . '\\pt_effective_url' ) ) { 515 /** 516 * Returns the URL a response finally came from, after any redirects. 517 * 518 * A fragment on the requested URL (which is never sent to the server) is 519 * kept, so that it can still pick out part of the page. 520 * 521 * @since 2.0.0 522 * 523 * @param string $url Requested URL. 524 * @param array $response Response from pt_remote_get(). 525 * @return string The final URL, or $url if it isn't known. 526 */ 527 function pt_effective_url( $url, $response ) { 528 $object = ( is_array( $response ) && isset( $response['http_response'] ) && is_object( $response['http_response'] ) && method_exists( $response['http_response'], 'get_response_object' ) ) ? $response['http_response']->get_response_object() : null; 529 if ( ! $object || empty( $object->url ) || ! is_string( $object->url ) || ! wp_http_validate_url( $object->url ) ) { 530 return $url; 531 } 532 $final = $object->url; 533 $fragment = wp_parse_url( $url, PHP_URL_FRAGMENT ); 534 if ( $fragment && ! wp_parse_url( $final, PHP_URL_FRAGMENT ) ) { 535 $final .= '#' . $fragment; 536 } 537 return $final; 538 } 539 } 540 541 if ( ! function_exists( __NAMESPACE__ . '\\pt_sniff_content_type' ) ) { 542 /** 543 * Works out a response's real format when its content type is missing, 544 * generic or wrong. 545 * 546 * Feeds are often served as text/plain or text/html, and JSON as 547 * text/plain or application/octet-stream. A body whose root element is 548 * <rss>, <rdf:RDF> or <feed> is a feed whatever the header says (an HTML 549 * page can't have those roots). A body that is a JSON object is checked 550 * for JSON Feed and mf2 JSON when the header is missing or generic. 551 * 552 * @since 2.0.0 553 * 554 * @param string $content_type Content type from the response, without 555 * parameters. 556 * @param string $body Response body. 557 * @return string The content type to use: application/rss+xml, 558 * application/atom+xml, application/feed+json or 559 * application/mf2+json when sniffed, otherwise 560 * $content_type unchanged. 561 */ 562 function pt_sniff_content_type( $content_type, $body ) { 563 if ( ! is_string( $body ) || '' === $body ) { 564 return $content_type; 565 } 566 $known = array( 'application/rss+xml', 'application/atom+xml', 'application/feed+json', 'application/mf2+json', 'application/jf2+json', 'application/jf2feed+json' ); 567 if ( in_array( $content_type, $known, true ) ) { 568 return $content_type; 569 } 570 // Skip a byte order mark, the XML declaration, processing instructions, comments and a doctype. 571 $start = ltrim( substr( $body, 0, 4096 ), "\xEF\xBB\xBF \t\r\n" ); 572 $start = preg_replace( '/^(\s*(<\?[^>]*\?>|<!--.*?-->|<!DOCTYPE[^>]*>))+\s*/is', '', $start ); 573 if ( preg_match( '/^<(rss|rdf:RDF)[\s>]/i', $start ) ) { 574 return 'application/rss+xml'; 575 } 576 if ( preg_match( '/^<feed[\s>]/i', $start ) ) { 577 return 'application/atom+xml'; 578 } 579 $generic = array( '', 'text/plain', 'application/octet-stream', 'text/html' ); 580 if ( in_array( $content_type, $generic, true ) && '{' === substr( $start, 0, 1 ) ) { 581 $json = json_decode( $body, true ); 582 if ( is_array( $json ) && isset( $json['version'] ) && is_string( $json['version'] ) && 0 === strpos( $json['version'], 'https://jsonfeed.org/version/' ) ) { 583 return 'application/feed+json'; 584 } 585 if ( is_array( $json ) && isset( $json['items'][0]['type'], $json['items'][0]['properties'] ) ) { 586 return 'application/mf2+json'; 587 } 588 } 589 return $content_type; 590 } 591 } 592 593 if ( ! function_exists( __NAMESPACE__ . '\\name_is_content_prefix' ) ) { 594 /** 595 * Checks whether a name only repeats the start of the content. 596 * 597 * Ignores a trailing ellipsis (... or …) on the name, as on truncated 598 * titles, and ignores whitespace in both. 599 * 600 * @since 2.0.0 601 * 602 * @param mixed $name Name. 603 * @param mixed $content Content text. 604 * @return bool True if both are non-empty strings and the content starts 605 * with the name. 606 */ 607 function name_is_content_prefix( $name, $content ) { 608 if ( ! is_string( $name ) || ! is_string( $content ) ) { 609 return false; 610 } 611 $name = preg_replace( '/\\s+/u', '', preg_replace( '/\\s*(\\.\\.\\.|…)\\s*$/u', '', $name ) ); 612 $content = preg_replace( '/\\s+/u', '', $content ); 613 return '' !== $name && '' !== $content && 0 === strpos( $content, $name ); 614 } 615 } 616 617 if ( ! function_exists( __NAMESPACE__ . '\\post_type_discovery' ) ) { 618 /** 619 * Determines the IndieWeb post type of a jf2 or mf2 entry. 620 * 621 * Events, reviews and recipes are their own type. For an entry, response 622 * properties decide the type first (review-of, rsvp, checkin, like-of, 623 * in-reply-to, ...), then media (video, photo, audio). An entry with a name 624 * that is not a prefix of its content (or summary) is an article; anything 625 * else is a note. 626 * 627 * @since 1.0.0 628 * @since 2.0.0 Adds review (from review-of, or the review type) and recipe, 629 * and follows the spec for a name without content. 630 * 631 * @link https://indieweb.org/post-type-discovery 632 * 633 * @param array|mixed $jf2 jf2 object, or an mf2 object (converted first). 634 * @return string The post type, or an empty string for other types. 635 */ 337 636 function post_type_discovery( $jf2 ) { 338 637 if ( ! is_array( $jf2 ) ) { … … 345 644 return ''; 346 645 } 347 if ( 'event' === $jf2['type'] ) { 348 return 'event'; 646 // Events (as in the spec), and reviews and recipes (as XRay does), are their own type. 647 if ( in_array( $jf2['type'], array( 'event', 'review', 'recipe' ), true ) ) { 648 return $jf2['type']; 349 649 } 350 650 if ( 'entry' === $jf2['type'] ) { 351 651 $map = array( 652 'review' => array( 'review-of' ), 352 653 'rsvp' => array( 'rsvp' ), 353 654 'checkin' => array( 'checkin' ), … … 377 678 } 378 679 } 379 if ( isset( $jf2['name'] ) && ! empty( $jf2['name'] ) ) { 380 $jf2['name'] = $jf2['name']; 381 $content = ifset( $jf2['content'] ); 382 if ( ! $content ) { 383 $content = ifset( $jf2['summary'] ); 384 } 385 if ( is_array( $content ) && array_key_exists( 'text', $content ) ) { 386 $content = $content['text']; 387 } 388 if ( is_string( $content ) ) { 389 $content = trim( $content ); 390 if ( 0 !== strpos( $content, $jf2['name'] ) ) { 391 return 'article'; 392 } 393 } 394 } 395 return 'note'; 680 // https://www.w3.org/TR/post-type-discovery/#algorithm: a name that is not 681 // a prefix of the content (or summary, or nothing) makes an article. A 682 // truncated name (ending in an ellipsis) is compared without it. 683 $name = ( isset( $jf2['name'] ) && is_string( $jf2['name'] ) ) ? trim( preg_replace( '/\s+/u', ' ', $jf2['name'] ) ) : ''; 684 if ( '' !== $name ) { 685 $content = $jf2['content'] ?? null; 686 if ( is_array( $content ) ) { 687 $content = $content['text'] ?? ( $content['value'] ?? null ); 688 } 689 if ( ! is_string( $content ) || '' === trim( $content ) ) { 690 $content = $jf2['summary'] ?? ''; 691 } 692 $content = is_string( $content ) ? trim( preg_replace( '/\s+/u', ' ', $content ) ) : ''; 693 if ( ! name_is_content_prefix( $name, $content ) ) { 694 return 'article'; 695 } 696 } 697 return 'note'; 396 698 } 397 699 return ''; … … 399 701 } 400 702 401 if ( ! function_exists( 'seconds_to_iso8601' ) ) { 703 if ( ! function_exists( __NAMESPACE__ . '\\seconds_to_iso8601' ) ) { 704 /** 705 * Converts a number of seconds into an ISO 8601 duration. 706 * 707 * For example 3725 becomes PT1H2M5S. 708 * 709 * @since 1.0.0 710 * 711 * @param int|float $second Duration in seconds. 712 * @return string ISO 8601 duration. 713 */ 402 714 function seconds_to_iso8601( $second ) { 403 715 $h = intval( $second / 3600 ); … … 418 730 } 419 731 420 if ( ! function_exists( 'pt_load_domdocument' ) ) { 732 if ( ! function_exists( __NAMESPACE__ . '\\pt_load_domdocument' ) ) { 733 /** 734 * Parses HTML into a DOMDocument. 735 * 736 * On PHP 8.4 and later, uses PHP's own HTML5 parser (Dom\HTMLDocument), 737 * which builds the same tree a browser does and is several times faster 738 * than masterminds/html5 (see pt_native_html_document()). It can be turned 739 * off with the parse_this_native_html_parser filter. Otherwise, or if that 740 * fails, uses the bundled masterminds/html5 parser, and failing that PHP's 741 * DOMDocument with errors suppressed. 742 * 743 * @since 1.0.0 744 * @since 2.0.0 Uses PHP's HTML5 parser when available. 745 * 746 * @param string $content HTML. 747 * @return DOMDocument The parsed document. 748 */ 421 749 function pt_load_domdocument( $content ) { 750 $content = (string) $content; 751 /** 752 * Filters whether to parse HTML with PHP's own HTML5 parser (PHP 8.4+). 753 * 754 * Return false to use masterminds/html5 even when PHP's parser is 755 * available. The two can differ on invalid markup, where PHP's parser 756 * follows the HTML5 algorithm (as browsers do). 757 * 758 * @since 2.0.0 759 * 760 * @param bool $enabled Whether to use it. Default true. 761 */ 762 if ( class_exists( 'Dom\HTMLDocument' ) && apply_filters( 'parse_this_native_html_parser', true ) ) { 763 $doc = pt_native_html_document( $content ); 764 if ( $doc ) { 765 return $doc; 766 } 767 } 422 768 if ( ! class_exists( '\Masterminds\HTML5', false ) ) { 423 769 $file = plugin_dir_path( __DIR__ ) . 'lib/html5/autoloader.php'; … … 430 776 $doc = $doc->loadHTML( $content ); 431 777 } else { 432 $doc = new DOMDocument(); 778 $doc = new \DOMDocument(); 779 // DOMDocument::loadHTML() throws on an empty string in PHP 8. 780 if ( '' === trim( $content ) ) { 781 return $doc; 782 } 433 783 libxml_use_internal_errors( true ); 434 if ( function_exists( 'mb_convert_encoding' ) ) { 435 $content = mb_convert_encoding( $content, 'HTML-ENTITIES', mb_detect_encoding( $content ) ); 784 if ( function_exists( 'mb_encode_numericentity' ) ) { 785 // DOMDocument assumes ISO-8859-1, so convert to UTF-8 and encode non-ASCII characters as entities. 786 $encoding = mb_detect_encoding( $content, array( 'UTF-8', 'ISO-8859-1' ), true ); 787 if ( $encoding && 'UTF-8' !== $encoding ) { 788 $content = mb_convert_encoding( $content, 'UTF-8', $encoding ); 789 } 790 $content = mb_encode_numericentity( $content, array( 0x80, 0x10FFFF, 0, 0x1FFFFF ), 'UTF-8' ); 436 791 } 437 792 $doc->loadHTML( $content ); … … 441 796 } 442 797 } 443 if ( ! function_exists( 'pt_secure_rewrite' ) ) { 798 799 if ( ! function_exists( __NAMESPACE__ . '\\pt_native_html_document' ) ) { 800 /** 801 * Parses HTML with PHP's HTML5 parser and returns it as a DOMDocument. 802 * 803 * Dom\HTMLDocument (PHP 8.4+) belongs to PHP's new DOM API, while php-mf2 804 * and the rest of Parse This work with DOMDocument. The parsed tree is 805 * passed across as XML, which reproduces it exactly without parsing the 806 * HTML twice. Comments (which may contain "--") and attributes whose names 807 * aren't valid XML (@click, x-on:click, :class) are removed first, since 808 * XML can't hold them and nothing here reads them. Elements are kept out of 809 * the XHTML namespace, so XPath queries such as //meta work as before. 810 * 811 * @since 2.0.0 812 * 813 * @param string $content HTML. 814 * @return DOMDocument|null The parsed document, or null if PHP's parser 815 * isn't available or the document can't be 816 * passed across. 817 */ 818 function pt_native_html_document( $content ) { 819 if ( ! class_exists( 'Dom\HTMLDocument' ) ) { 820 return null; 821 } 822 try { 823 $html = \Dom\HTMLDocument::createFromString( (string) $content, LIBXML_NOERROR | \Dom\HTML_NO_DEFAULT_NS ); 824 $xpath = new \Dom\XPath( $html ); 825 foreach ( iterator_to_array( $xpath->query( '//comment()' ) ) as $comment ) { 826 $comment->remove(); 827 } 828 foreach ( iterator_to_array( $xpath->query( '//@*' ) ) as $attribute ) { 829 if ( ! preg_match( '/^[A-Za-z_][A-Za-z0-9_.\-]*$/', $attribute->name ) ) { 830 $attribute->ownerElement->removeAttribute( $attribute->name ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- PHP DOM property. 831 } 832 } 833 $xml = $html->saveXml(); 834 } catch ( \Throwable $e ) { 835 return null; 836 } 837 $doc = new \DOMDocument(); 838 $errors = libxml_use_internal_errors( true ); 839 $loaded = $doc->loadXML( $xml, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_PARSEHUGE ); 840 libxml_clear_errors(); 841 libxml_use_internal_errors( $errors ); 842 return $loaded ? $doc : null; 843 } 844 } 845 if ( ! function_exists( __NAMESPACE__ . '\\pt_remote_get' ) ) { 846 /** 847 * Retrieves a remote URL, retrying once with a browser user agent if the site rejects the request. 848 * 849 * Fetched documents decide which further URLs are requested (author pages, 850 * alternates, REST API links), so this must stay on wp_safe_remote_get(), 851 * which refuses local and private addresses. Don't switch it to 852 * wp_remote_get(). 853 * 854 * @param string $url URL to retrieve. 855 * @param array $args Optional. Arguments passed to wp_safe_remote_get(). 856 * @param array $retry_codes Optional. Response codes that trigger the retry. 857 * @return array|WP_Error The response, or WP_Error on failure. 858 */ 859 function pt_remote_get( $url, $args = array(), $retry_codes = array( 403, 415 ) ) { 860 $args = wp_parse_args( 861 $args, 862 array( 863 'timeout' => 15, 864 'limit_response_size' => 1048576, 865 'redirection' => 5, 866 ) 867 ); 868 869 $response = wp_safe_remote_get( $url, $args ); 870 if ( is_wp_error( $response ) ) { 871 return $response; 872 } 873 if ( ! in_array( (int) wp_remote_retrieve_response_code( $response ), $retry_codes, true ) ) { 874 return $response; 875 } 876 877 $args['user-agent'] = 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:57.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 Parse This/WP'; 878 $response = wp_safe_remote_get( $url, $args ); 879 if ( is_wp_error( $response ) ) { 880 return $response; 881 } 882 $code = (int) wp_remote_retrieve_response_code( $response ); 883 if ( in_array( $code, $retry_codes, true ) ) { 884 return new \WP_Error( 'source_error', __( 'Unable to retrieve the URL.', 'parse-this' ), array( 'response_code' => $code ) ); 885 } 886 return $response; 887 } 888 } 889 890 if ( ! function_exists( __NAMESPACE__ . '\\pt_secure_rewrite' ) ) { 891 /** 892 * Upgrades http:// URLs to https:// for hosts known to support HTTPS. 893 * 894 * @since 1.0.1 895 * 896 * @param string $url URL. 897 * @return string The URL, with https:// if its domain is on the list. 898 */ 444 899 function pt_secure_rewrite( $url ) { 445 900 $host = wp_parse_url( $url, PHP_URL_HOST ); … … 460 915 'google.com', 461 916 'gravatar.com', 462 'gstatic.com', 917 'gstatic.com', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent -- A domain to rewrite to https, not content loaded from it. 463 918 'kernel.org', 464 919 'lwn.net', 465 920 'tumblr.com', 466 921 'twitter.com', 922 'x.com', 467 923 'vimeo.com', 468 924 'wikipedia.org', … … 470 926 'youtube.com', 471 927 ); 928 /** 929 * Filters the domains whose http:// URLs are upgraded to https://. 930 * 931 * Matching is on the registrable domain, so listing example.com also 932 * covers www.example.com. 933 * 934 * @since 1.0.1 935 * 936 * @param string[] $secure Domains known to support HTTPS. 937 */ 472 938 $secure = apply_filters( 'pt_rewrite_secure', $secure ); 473 939 if ( in_array( $host, $secure, true ) ) { … … 479 945 480 946 481 /** 482 * Parses Link Headers 483 * 484 * 485 * @return array 486 */ 487 488 if ( ! function_exists( 'pt_parse_header_links' ) ) { 947 if ( ! function_exists( __NAMESPACE__ . '\\pt_parse_header_links' ) ) { 948 /** 949 * Parses HTTP Link headers. 950 * 951 * @since 2.0.0 952 * 953 * @link https://www.rfc-editor.org/rfc/rfc8288 954 * 955 * @param string|string[] $links One Link header value, or several. A value may 956 * hold multiple comma-separated links. 957 * @return array[] One entry per link and rel value, with 'uri', 'rel' and any 958 * other parameters (type, title, ...). 959 */ 489 960 function pt_parse_header_links( $links ) { 490 961 $items = array(); 491 962 963 if ( is_string( $links ) ) { 964 $links = array( $links ); 965 } 966 492 967 if ( is_array( $links ) && 1 <= count( $links ) ) { 968 // A header may hold several comma-separated links. Only split on commas that start a new <uri>, as URIs may contain commas. 969 $split = array(); 493 970 foreach ( $links as $link ) { 494 $item = array(); 495 $pieces = explode( ';', $link ); 496 $uri = array_shift( $pieces ); 971 $split = array_merge( $split, preg_split( '/,(?=\s*<)/', $link ) ); 972 } 973 foreach ( $split as $link ) { 974 $item = array(); 975 if ( preg_match( '/^\s*<([^>]*)>(.*)$/s', $link, $match ) ) { 976 $uri = $match[1]; 977 $pieces = explode( ';', $match[2] ); 978 } else { 979 $pieces = explode( ';', $link ); 980 $uri = array_shift( $pieces ); 981 } 497 982 foreach ( $pieces as $p ) { 498 $elements = explode( '=', $p ); 499 500 $item[ trim( $elements[0] ) ] = trim( $elements[1], '"\'' ); 983 $elements = explode( '=', $p, 2 ); 984 $name = trim( $elements[0] ); 985 if ( '' === $name ) { 986 continue; 987 } 988 989 $item[ $name ] = isset( $elements[1] ) ? trim( $elements[1], " \t\"'" ) : ''; 501 990 } 502 991 … … 519 1008 } 520 1009 521 /** 522 * Find WordPress REST Alternate 523 * 524 * 525 * @return string|false 526 */ 527 528 if ( ! function_exists( 'pt_find_rest_alternate' ) ) { 1010 if ( ! function_exists( __NAMESPACE__ . '\\pt_find_rest_alternate' ) ) { 1011 /** 1012 * Finds the application/json alternate link among parsed Link headers. 1013 * 1014 * WordPress sends this for each post, pointing at its REST API URL. 1015 * 1016 * @since 2.0.0 1017 * 1018 * @param array[] $links Links from pt_parse_header_links(). 1019 * @return string|false The URL, or false if there is none. 1020 */ 529 1021 function pt_find_rest_alternate( $links ) { 530 1022 foreach ( $links as $link ) { 531 if ( 'alternate' === ifset( $link['rel'] ) && 'application/json' === ifset( $link['type']) ) {1023 if ( 'alternate' === ( $link['rel'] ?? null ) && 'application/json' === ( $link['type'] ?? null ) ) { 532 1024 return $link['uri']; 533 1025 } … … 538 1030 } 539 1031 540 /** 541 * Find WordPress REST Endpoint 542 * 543 * 544 * @return string|false 545 */ 546 547 if ( ! function_exists( 'pt_find_rest_endpoint' ) ) { 1032 if ( ! function_exists( __NAMESPACE__ . '\\pt_find_rest_endpoint' ) ) { 1033 /** 1034 * Finds the WordPress REST API root among parsed Link headers. 1035 * 1036 * @since 2.0.0 1037 * 1038 * @param array[] $links Links from pt_parse_header_links(). 1039 * @return string|false The REST API root URL, or false if there is none. 1040 */ 548 1041 function pt_find_rest_endpoint( $links ) { 549 1042 foreach ( $links as $link ) { 550 if ( 'https://api.w.org/' === ifset( $link['rel']) ) {1043 if ( 'https://api.w.org/' === ( $link['rel'] ?? null ) ) { 551 1044 return $link['uri']; 552 1045 } … … 556 1049 } 557 1050 558 if ( ! function_exists( 'pt_make_absolute_url' ) ) { 1051 if ( ! function_exists( __NAMESPACE__ . '\\pt_absolute_urls_in_html' ) ) { 1052 /** 1053 * Makes the links and media URLs in an HTML fragment absolute. 1054 * 1055 * Resolves href, src and poster attributes against a base URL, for content 1056 * taken from a document (such as a JSON Feed item) whose own URL differs 1057 * from where it will be shown. 1058 * 1059 * @since 2.0.0 1060 * 1061 * @param string $html HTML fragment. 1062 * @param string $base Base URL. 1063 * @return string The HTML with absolute URLs, or $html unchanged if it has 1064 * none to resolve or $base is not a URL. 1065 */ 1066 function pt_absolute_urls_in_html( $html, $base ) { 1067 if ( ! is_string( $html ) || '' === trim( $html ) || ! wp_http_validate_url( $base ) || ! preg_match( '/\s(href|src|poster)\s*=/i', $html ) ) { 1068 return $html; 1069 } 1070 $doc = pt_load_domdocument( '<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>' . $html . '</body></html>' ); 1071 $body = $doc->getElementsByTagName( 'body' )->item( 0 ); 1072 if ( ! $body ) { 1073 return $html; 1074 } 1075 foreach ( ( new \DOMXPath( $doc ) )->query( '//body//*[@href or @src or @poster]' ) as $element ) { 1076 foreach ( array( 'href', 'src', 'poster' ) as $attribute ) { 1077 $value = $element->getAttribute( $attribute ); 1078 if ( '' !== $value && '#' !== $value[0] ) { 1079 $element->setAttribute( $attribute, pt_make_absolute_url( $value, $base ) ); 1080 } 1081 } 1082 } 1083 $out = ''; 1084 foreach ( $body->childNodes as $node ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- PHP DOM property. 1085 $out .= $doc->saveHTML( $node ); 1086 } 1087 return $out; 1088 } 1089 } 1090 1091 if ( ! function_exists( __NAMESPACE__ . '\\pt_make_absolute_url' ) ) { 1092 /** 1093 * Resolves a relative URL against a base URL. 1094 * 1095 * @since 2.0.0 1096 * 1097 * @param string $maybe_relative_path URL or path to resolve. 1098 * @param string $url Base URL. 1099 * @return string The absolute URL, or $maybe_relative_path unchanged if it is 1100 * already absolute or either value cannot be parsed. 1101 */ 559 1102 function pt_make_absolute_url( $maybe_relative_path, $url ) { 560 1103 if ( empty( $url ) ) { … … 592 1135 } 593 1136 } 594 1137 595 1138 // Start off with the absolute URL path. 596 1139 $path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/'; … … 600 1143 $path = $relative_url_parts['path']; 601 1144 602 // Else it's a relative path.1145 // Else it's a relative path. 603 1146 } elseif ( ! empty( $relative_url_parts['path'] ) ) { 604 1147 // Strip off any file components from the absolute path. … … 627 1170 } 628 1171 629 630 1172 return $absolute_path . '/' . ltrim( $path, '/' ); 631 1173 } -
indieweb-post-kinds/trunk/lib/parse-this/lib/html5/HTML5.php
r2351228 r3738687 147 147 * 148 148 * @param string $input 149 * @param array $options150 149 * 151 150 * @return \DOMDocument -
indieweb-post-kinds/trunk/lib/parse-this/lib/html5/HTML5/Elements.php
r2110745 r3738687 71 71 */ 72 72 const BLOCK_ONLY_INLINE = 128; 73 74 /** 75 * Elements with optional end tags that cause auto-closing of previous and parent tags, 76 * as example most of the table related tags, see https://www.w3.org/TR/html401/struct/tables.html 77 * Structure is as follows: 78 * TAG-NAME => [PARENT-TAG-NAME-TO-CLOSE1, PARENT-TAG-NAME-TO-CLOSE2, ...]. 79 * 80 * Order is important, after auto-closing one parent with might have to close also their parent. 81 * 82 * @var array<string, string[]> 83 */ 84 public static $optionalEndElementsParentsToClose = array( 85 'tr' => array('td', 'tr'), 86 'td' => array('td', 'th'), 87 'th' => array('td', 'th'), 88 'tfoot' => array('td', 'th', 'tr', 'tbody', 'thead'), 89 'tbody' => array('td', 'th', 'tr', 'thead'), 90 ); 73 91 74 92 /** … … 186 204 'ul' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG 187 205 'var' => 1, 188 'video' => 65, // NORMAL | BLOCK_TAG206 'video' => 1, 189 207 'wbr' => 9, // NORMAL | VOID_TAG 190 208 … … 293 311 'feDisplacementMap' => 1, 294 312 'feDistantLight' => 1, 313 'feDropShadow' => 1, 295 314 'feFlood' => 1, 296 315 'feFuncA' => 1, … … 446 465 'fedisplacementmap' => 'feDisplacementMap', 447 466 'fedistantlight' => 'feDistantLight', 467 'fedropshadow' => 'feDropShadow', 448 468 'feflood' => 'feFlood', 449 469 'fefunca' => 'feFuncA', -
indieweb-post-kinds/trunk/lib/parse-this/lib/html5/HTML5/Parser/CharacterReference.php
r2110745 r3738687 49 49 50 50 /** 51 * Given a hex idecimal number, return the UTF-8 character.51 * Given a hexadecimal number, return the UTF-8 character. 52 52 * 53 53 * @param $hexdec -
indieweb-post-kinds/trunk/lib/parse-this/lib/html5/HTML5/Parser/DOMTreeBuilder.php
r2623705 r3738687 232 232 * This is used for handling Processor Instructions as they are 233 233 * inserted. If omitted, PI's are inserted directly into the DOM tree. 234 *235 * @param InstructionProcessor $proc236 234 */ 237 235 public function setInstructionProcessor(InstructionProcessor $proc) … … 360 358 } 361 359 360 // some elements as table related tags might have optional end tags that force us to auto close multiple tags 361 // https://www.w3.org/TR/html401/struct/tables.html 362 if ($this->current instanceof \DOMElement && isset(Elements::$optionalEndElementsParentsToClose[$lname])) { 363 foreach (Elements::$optionalEndElementsParentsToClose[$lname] as $parentElName) { 364 if ($this->current instanceof \DOMElement && $this->current->tagName === $parentElName) { 365 $this->autoclose($parentElName); 366 } 367 } 368 } 369 362 370 try { 363 371 $prefix = ($pos = strpos($lname, ':')) ? substr($lname, 0, $pos) : ''; … … 390 398 if ($pushes > 0 && !Elements::isA($name, Elements::VOID_TAG)) { 391 399 // PHP tends to free the memory used by DOM, 392 // to avoid spl_object_ hash collisions whe have to avoid garbage collection of $ele storing it into $pushes400 // to avoid spl_object_id collisions we have to avoid garbage collection of $ele storing it into $pushes 393 401 // see https://bugs.php.net/bug.php?id=67459 394 $this->pushes[spl_object_ hash($ele)] = array($pushes, $ele);402 $this->pushes[spl_object_id($ele)] = array($pushes, $ele); 395 403 } 396 404 … … 514 522 } 515 523 516 $cid = spl_object_ hash($this->current);524 $cid = spl_object_id($this->current); 517 525 518 526 // XXX: HTML has no parent. What do we do, though, -
indieweb-post-kinds/trunk/lib/parse-this/lib/html5/HTML5/Parser/StringInputStream.php
r2110745 r3738687 184 184 * @return string The current character. 185 185 */ 186 #[\ReturnTypeWillChange] 186 187 public function current() 187 188 { … … 193 194 * This is part of the Iterator interface. 194 195 */ 196 #[\ReturnTypeWillChange] 195 197 public function next() 196 198 { … … 201 203 * Rewind to the start of the string. 202 204 */ 205 #[\ReturnTypeWillChange] 203 206 public function rewind() 204 207 { … … 211 214 * @return bool Whether the current pointer location is valid. 212 215 */ 216 #[\ReturnTypeWillChange] 213 217 public function valid() 214 218 { … … 325 329 } 326 330 331 #[\ReturnTypeWillChange] 327 332 public function key() 328 333 { -
indieweb-post-kinds/trunk/lib/parse-this/lib/html5/HTML5/Parser/Tokenizer.php
r2839509 r3738687 132 132 $tok = $this->scanner->next(); 133 133 134 if ('!' === $tok) { 134 if (false === $tok) { 135 // end of string 136 $this->parseError('Illegal tag opening'); 137 } elseif ('!' === $tok) { 135 138 $this->markupDeclaration(); 136 139 } elseif ('/' === $tok) { … … 138 141 } elseif ('?' === $tok) { 139 142 $this->processingInstruction(); 140 } elseif ( ctype_alpha($tok)) {143 } elseif ($this->is_alpha($tok)) { 141 144 $this->tagName(); 142 145 } else { … … 348 351 // EOF -> parse error 349 352 // -> parse error 350 if (! ctype_alpha($tok)) {353 if (!$this->is_alpha($tok)) { 351 354 $this->parseError("Expected tag name, got '%s'", $tok); 352 355 if ("\0" == $tok || false === $tok) { … … 505 508 506 509 $val = $this->attributeValue(); 507 if ($isValidAttribute ) {510 if ($isValidAttribute && !array_key_exists($name, $attributes)) { 508 511 $attributes[$name] = $val; 509 512 } … … 727 730 if ('!' == $this->scanner->current() && '>' == $this->scanner->peek()) { 728 731 $this->scanner->consume(); // Consume the last '>' 732 729 733 return true; 730 734 } … … 1125 1129 } 1126 1130 1127 // Hex idecimal encoding.1131 // Hexadecimal encoding. 1128 1132 // X[0-9a-fA-F]+; 1129 1133 // x[0-9a-fA-F]+; … … 1195 1199 return '&'; 1196 1200 } 1201 1202 /** 1203 * Checks whether a (single-byte) character is an ASCII letter or not. 1204 * 1205 * @param string $input A single-byte string 1206 * 1207 * @return bool True if it is a letter, False otherwise 1208 */ 1209 protected function is_alpha($input) 1210 { 1211 if (!is_string($input) || 1 !== strlen($input)) { 1212 return false; 1213 } 1214 1215 $code = ord($input); 1216 1217 return ($code >= 97 && $code <= 122) || ($code >= 65 && $code <= 90); 1218 } 1197 1219 } -
indieweb-post-kinds/trunk/lib/parse-this/lib/html5/HTML5/Parser/TreeBuildingRules.php
r2110745 r3738687 81 81 case 'tfoot': 82 82 case 'table': // Spec isn't explicit about this, but it's necessary. 83 84 83 return $this->closeIfCurrentMatches($new, $current, array( 85 84 'thead', -
indieweb-post-kinds/trunk/lib/parse-this/lib/html5/HTML5/Parser/UTF8Utils.php
r2110745 r3738687 39 39 /** 40 40 * Count the number of characters in a string. 41 * UTF-8 aware. This will try (in order) iconv, MB, libxml,and finally a custom counter.41 * UTF-8 aware. This will try (in order) iconv, MB, and finally a custom counter. 42 42 * 43 43 * @param string $string … … 54 54 if (function_exists('iconv_strlen')) { 55 55 return iconv_strlen($string, 'utf-8'); 56 }57 58 if (function_exists('utf8_decode')) {59 // MPB: Will this work? Won't certain decodes lead to two chars60 // extrapolated out of 2-byte chars?61 return strlen(utf8_decode($string));62 56 } 63 57 -
indieweb-post-kinds/trunk/lib/parse-this/lib/html5/HTML5/Serializer/OutputRules.php
r2351228 r3738687 17 17 class OutputRules implements RulesInterface 18 18 { 19 /**20 * Defined in http://www.w3.org/TR/html51/infrastructure.html#html-namespace-0.21 */22 19 const NAMESPACE_HTML = 'http://www.w3.org/1999/xhtml'; 23 20 … … 50 47 51 48 const IM_IN_MATHML = 3; 52 53 /**54 * Used as cache to detect if is available ENT_HTML5.55 *56 * @var bool57 */58 private $hasHTML5 = false;59 49 60 50 protected $traverser; … … 168 158 $this->outputMode = static::IM_IN_HTML; 169 159 $this->out = $output; 170 $this->hasHTML5 = defined('ENT_HTML5');171 160 } 172 161 … … 207 196 } 208 197 198 /** 199 * @param \DOMElement $ele 200 */ 209 201 public function element($ele) 210 202 { … … 228 220 229 221 $this->openTag($ele); 222 // The tag is already self-closed (`<svg />` or `<math />`) in `openTag` if there are no child nodes. 223 $handledAsVoidTag = $this->outputMode !== static::IM_IN_HTML && !$ele->hasChildNodes(); 224 230 225 if (Elements::isA($name, Elements::TEXT_RAW)) { 231 226 foreach ($ele->childNodes as $child) { … … 249 244 250 245 // If not unary, add a closing tag. 251 if (! Elements::isA($name, Elements::VOID_TAG)) {246 if (!$handledAsVoidTag && !Elements::isA($name, Elements::VOID_TAG)) { 252 247 $this->closeTag($ele); 253 248 } … … 449 444 protected function wr($text) 450 445 { 451 fwrite($this->out, $text);446 fwrite($this->out, (string) $text); 452 447 453 448 return $this; … … 484 479 * encoding here will just escape &'<>". 485 480 * 486 * Note, PHP 5.4+ has better html5 encoding.487 *488 * @todo Use the Entities class in php 5.3 to have html5 entities.489 *490 481 * @param string $text Text to encode. 491 * @param bool $attribute True if we are encoding an attr ubute, false otherwise.482 * @param bool $attribute True if we are encoding an attribute, false otherwise. 492 483 * 493 484 * @return string The encoded text. … … 500 491 } 501 492 502 // If we are in PHP 5.4+ we can use the native html5 entity functionality to 503 // convert the named character references. 504 505 if ($this->hasHTML5) { 506 return htmlentities($text, ENT_HTML5 | ENT_SUBSTITUTE | ENT_QUOTES, 'UTF-8', false); 507 } // If a version earlier than 5.4 html5 entities are not entirely handled. 508 // This manually handles them. 509 else { 510 return strtr($text, HTML5Entities::$map); 511 } 493 return htmlentities($text, ENT_HTML5 | ENT_SUBSTITUTE | ENT_QUOTES, 'UTF-8', false); 512 494 } 513 495 -
indieweb-post-kinds/trunk/lib/parse-this/lib/mf2/Parser.php
r2839509 r3738687 146 146 147 147 /** 148 * Registered with the XPath object and used within XPaths for finding root elements.149 * @param string $class150 * @return bool151 */152 function classHasMf2RootClassname($class) {153 return count(mfNamesFromClass($class, 'h-')) > 0;154 }155 156 /**157 148 * Get Nested µf Property Name From Class 158 149 * … … 365 356 */ 366 357 public function __construct($input, $url = null, $jsonMode = false) { 367 $emptyDocDefault = '<html><body></body></html>';368 358 libxml_use_internal_errors(true); 369 359 if (is_string($input)) { 370 if (empty($input)) {371 $input = $emptyDocDefault;372 }373 374 360 if (class_exists('Masterminds\\HTML5')) { 375 361 $doc = new \Masterminds\HTML5(array('disable_html_ns' => true)); … … 383 369 } else { 384 370 $doc = new DOMDocument(); 385 @$doc->loadHTML($emptyDocDefault); 386 } 387 388 // Create an XPath object and allow some PHP functions to be used within XPath queries. 371 @$doc->loadHTML(''); 372 } 373 389 374 $this->xpath = new DOMXPath($doc); 390 $this->xpath->registerNamespace('php', 'http://php.net/xpath');391 $this->xpath->registerPhpFunctions('\\Mf2\\classHasMf2RootClassname');392 375 393 376 $baseurl = $url; … … 1182 1165 'properties' => $return 1183 1166 ); 1184 1167 1185 1168 if(trim($e->getAttribute('id')) !== '') { 1186 1169 $parsed['id'] = trim($e->getAttribute("id")); … … 1524 1507 // start with mf2 root class name xpath 1525 1508 $xpaths = array( 1526 ' (php:function("\\Mf2\\classHasMf2RootClassname", normalize-space(@class)))'1509 'contains(concat(" ",normalize-space(@class)), " h-")' 1527 1510 ); 1528 1511 … … 1579 1562 break; 1580 1563 1581 case 'hfeed':1582 $this->upgradeRelTagToCategory($el);1583 break;1584 1585 case 'hproduct':1586 $review_and_hreview_aggregate = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " review ") and contains(concat(" ", normalize-space(@class), " "), " hreview-aggregate ")]', $el);1587 1588 if ( $review_and_hreview_aggregate->length ) {1589 foreach ( $review_and_hreview_aggregate as $tempEl ) {1590 if ( !$this->hasRootMf2($tempEl) ) {1591 $this->backcompat($tempEl, 'hreview-aggregate');1592 $this->addMfClasses($tempEl, 'p-review h-review-aggregate');1593 $this->addUpgraded($tempEl, array('review hreview-aggregate'));1594 }1595 }1596 }1597 1598 $review_and_hreview = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " review ") and contains(concat(" ", normalize-space(@class), " "), " hreview ")]', $el);1599 1600 if ( $review_and_hreview->length ) {1601 foreach ( $review_and_hreview as $tempEl ) {1602 if ( !$this->hasRootMf2($tempEl) ) {1603 $this->backcompat($tempEl, 'hreview');1604 $this->addMfClasses($tempEl, 'p-review h-review');1605 $this->addUpgraded($tempEl, array('review hreview'));1606 }1607 }1608 }1609 1610 break;1611 1612 case 'hreview-aggregate':1613 1564 case 'hreview': 1614 1565 $item_and_vcard = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " item ") and contains(concat(" ", normalize-space(@class), " "), " vcard ")]', $el); … … 1652 1603 1653 1604 case 'vevent': 1654 $location _and_vcard = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " location ") and contains(concat(" ", normalize-space(@class), " "), " vcard")]', $el);1655 1656 if ( $location _and_vcard->length ) {1657 foreach ( $location _and_vcardas $tempEl ) {1605 $location = $this->xpath->query('.//*[contains(concat(" ", normalize-space(@class), " "), " location ")]', $el); 1606 1607 if ( $location->length ) { 1608 foreach ( $location as $tempEl ) { 1658 1609 if ( !$this->hasRootMf2($tempEl) ) { 1659 $this->addMfClasses($tempEl, ' p-locationh-card');1610 $this->addMfClasses($tempEl, 'h-card'); 1660 1611 $this->backcompat($tempEl, 'vcard'); 1661 1612 } … … 1737 1688 public function hasRootMf2(\DOMElement $el) { 1738 1689 $class = str_replace(array("\t", "\n"), ' ', $el->getAttribute('class')); 1739 1740 // Check for valid mf2 root classnames, not just any classname with a h- prefix. 1741 return count(mfNamesFromClass($class, 'h-')) > 0; 1690 $classes = array_filter(explode(' ', $class)); 1691 1692 foreach ( $classes as $classname ) { 1693 if ( strpos($classname, 'h-') === 0 ) { 1694 return true; 1695 } 1696 } 1697 1698 return false; 1742 1699 } 1743 1700 … … 1799 1756 'vevent' => 'h-event', 1800 1757 'hreview' => 'h-review', 1801 'hreview-aggregate' => 'h-review-aggregate',1802 1758 'hproduct' => 'h-product', 1803 1759 'adr' => 'h-adr', 1804 'geo' => 'h-geo'1805 1760 ); 1806 1761 … … 1919 1874 ), 1920 1875 'hfeed' => array( 1921 'author' => array( 1922 'replace' => 'p-author h-card', 1923 'context' => 'vcard' 1924 ), 1925 'url' => array( 1926 'replace' => 'u-url' 1927 ), 1928 'photo' => array( 1929 'replace' => 'u-photo' 1930 ), 1931 'category' => array( 1932 'replace' => 'p-category' 1933 ), 1876 # nothing currently 1934 1877 ), 1935 1878 'hentry' => array( … … 2041 1984 ), 2042 1985 'location' => array( 2043 'replace' => 'p-location', 1986 'replace' => 'h-card', 1987 'context' => 'vcard' 2044 1988 ), 2045 1989 'geo' => array( 2046 1990 'replace' => 'p-location h-geo' 2047 1991 ), 2048 'attendee' => array(2049 'replace' => 'p-attendee h-card',2050 'context' => 'vcard'2051 )2052 1992 ), 2053 1993 'hreview' => array( … … 2085 2025 ), 2086 2026 ), 2087 'hreview-aggregate' => array(2088 'summary' => array(2089 'replace' => 'p-name'2090 ),2091 # fn: see item.fn below2092 # photo: see item.photo below2093 # url: see item.url below2094 'item' => array(2095 'replace' => 'p-item h-item',2096 'context' => 'item'2097 ),2098 'rating' => array(2099 'replace' => 'p-rating'2100 ),2101 'best' => array(2102 'replace' => 'p-best'2103 ),2104 'worst' => array(2105 'replace' => 'p-worst'2106 ),2107 'average' => array(2108 'replace' => 'p-average'2109 ),2110 'count' => array(2111 'replace' => 'p-count'2112 ),2113 'votes' => array(2114 'replace' => 'p-votes'2115 ),2116 ),2117 2027 'hproduct' => array( 2118 2028 'fn' => array( … … 2137 2047 'replace' => 'u-url', 2138 2048 ), 2139 // review is handled in the special processing section to allow for 'review hreview-aggregate' 2049 'review' => array( 2050 'replace' => 'p-review h-review', 2051 ), 2140 2052 'price' => array( 2141 2053 'replace' => 'p-price' -
indieweb-post-kinds/trunk/lib/parse-this/parse-this.php
r2507837 r3738687 3 3 * Plugin Name: Parse This 4 4 * Plugin URI: https://github.com/dshanske/parse-this 5 * Description: 6 * Version: 1.0.15 * Description: Turns URLs into structured jf2 data from microformats2, JSON-LD, meta tags and RSS, Atom and JSON feeds. 6 * Version: 2.0.0 7 7 * Author: David Shanske 8 8 * Author URI: https://david.shanske.com 9 * Requires at least: 6.2 10 * Requires PHP: 7.4 11 * License: GPLv2 or later 12 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 13 * Text Domain: parse-this 10 * Domain Path: /languages 14 * 15 * @package Parse_This 11 16 */ 12 17 13 14 /* 15 Parse This Load 16 */ 18 defined( 'ABSPATH' ) || exit; 17 19 18 20 if ( ! function_exists( 'parse_this_loader' ) ) { 21 /** 22 * Loads Parse This. 23 * 24 * Runs on plugins_loaded at priority 9, so that the standalone plugin loads 25 * ahead of copies bundled in other plugins (Post Kinds loads its copy at 26 * priority 11). Functions are guarded by function_exists() and the first 27 * autoloader registered serves the classes, so whichever copy loads first 28 * wins. It also registers the deprecated global names in aliases.php, so 29 * that plugins bundling an older copy use this one. 30 * 31 * Defines PARSE_THIS_VERSION for the copy that loads, and fires 32 * parse_this_loaded once everything is available. A copy older than 2.0.0 33 * defines neither, so a plugin can tell when an older bundled copy won. 34 * 35 * @since 1.0.0 36 * @since 2.0.0 Defines PARSE_THIS_VERSION and fires parse_this_loaded. 37 */ 19 38 function parse_this_loader() { 39 if ( ! defined( 'PARSE_THIS_VERSION' ) ) { 40 /** 41 * Version of the copy of Parse This that loaded. Keep in step with the 42 * Version header. 43 * 44 * @since 2.0.0 45 * @var string 46 */ 47 define( 'PARSE_THIS_VERSION', '2.0.0' ); 48 } 20 49 require_once plugin_dir_path( __FILE__ ) . 'includes/autoload.php'; 50 require_once plugin_dir_path( __FILE__ ) . 'includes/functions.php'; 21 51 22 // Functions Not Available in Earlier Versions of WordPress 23 require_once plugin_dir_path( __FILE__ ) . 'includes/compat-functions.php'; 52 new ParseThis\REST_Endpoint(); 24 53 25 require_once plugin_dir_path( __FILE__ ) . 'includes/functions.php'; 26 // Parse This REST Endpoint 27 require_once plugin_dir_path( __FILE__ ) . 'includes/class-rest-parse-this.php'; 28 54 /** 55 * Fires once Parse This has loaded: its classes, functions and REST route 56 * are available. 57 * 58 * Plugins that use Parse This can hook here, or check PARSE_THIS_VERSION, 59 * rather than depend on load order. 60 * 61 * @since 2.0.0 62 * 63 * @param string $version Version of the copy of Parse This that loaded. 64 */ 65 do_action( 'parse_this_loaded', PARSE_THIS_VERSION ); 29 66 } 30 67 add_action( 'plugins_loaded', 'parse_this_loader', 9 ); 31 68 } 32 -
indieweb-post-kinds/trunk/lib/parse-this/readme.txt
r2507837 r3738687 1 1 === Parse This === 2 2 Contributors: dshanske 3 Tags: indieweb 4 Stable tag: trunk5 Requires at least: 4.96 Requires PHP: 5.67 Tested up to: 5.63 Tags: indieweb, microformats, jf2, json-ld, feeds 4 Stable tag: 2.0.0 5 Requires at least: 6.2 6 Requires PHP: 7.4 7 Tested up to: 7.1 8 8 License: GPLv2 or later 9 License URI: http://www.gnu.org/licenses/gpl-2.0.html 10 11 Parse This turns URLs into structured jf2 data 12 13 == Description == 14 15 Parse This is based on a variety of projects including the parsing code from Press This, which was removed from WordPress. 16 17 * It supports parsing from MF2 if present 18 * For sites that are not marked up with Microformats 2(MF2) it will fall back onto parsing JSON-LD, then HTML/OpenGraph/Dublin Core Tags/etc. 19 * It supports parsing of JSONFeed and RSS/Atom feeds 20 * It supports parsing of WordPress REST API endpoints to generate a site feed 21 22 The goal is to produce structured jf2 data that can be used for previewing links as well as feed readers and other options. It is also bundled in the Post Kinds and Yarns Microsub plugins as a library. 23 24 It can be installed as a standalone plugin which will provide the necessary libraries and functionality as well as the REST API endpoint for getting JF2 data from an arbitrary URL or a WordPress Post. 25 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 11 Parse This turns URLs into structured jf2 data. 12 13 == Description == 14 15 Parse This fetches a URL and turns it into [jf2](https://jf2.spec.indieweb.org/), a simple JSON format for posts, people and feeds. The result can be used for link previews, feed readers, replies and likes, and similar features. 16 17 It started from the parsing code in Press This, which was removed from WordPress, and has grown from there. It runs as a standalone plugin that other plugins, such as Post Kinds and Yarns Microsub, can require or bundle. 18 19 It also runs on ClassicPress 2.x. 20 21 = What it parses = 22 23 * **Microformats2.** When a page is marked up with microformats, they are used first: entries, cards, events, reviews (including h-entry reviews with `review-of`), recipes, products and any other type. On a busy page it picks the item the page is about, and it follows the IndieWeb authorship algorithm. 24 * **Other metadata.** Other sources only fill in what microformats don't provide: a site-specific parser for YouTube or X (Twitter), then JSON-LD (articles, events, reviews, products, recipes, media and people) and Open Graph, Dublin Core and other meta tags. If there is still no content, the page's WordPress REST API version is fetched, when the site advertises one. 25 * **Parts of pages.** A link with a fragment (`#comment-12`) is parsed as just that part of the page. A WordPress comment link whose page has no microformats for it is read from the site's REST API as a reply. 26 * **Feeds.** RSS and Atom (through WordPress's SimplePie), JSON Feed 1 and 1.1, jf2 and mf2 JSON, and WordPress REST API post collections, including feeds served with the wrong content type. 27 * **Feed discovery.** It lists the feeds a page offers: alternates, `rel=feed` links, h-feeds and the WordPress REST API. 28 29 On PHP 8.4 and later, HTML is parsed with PHP's own HTML5 parser; on older PHP, with the bundled masterminds/html5 parser. 30 31 = Using it from PHP = 32 33 $parse = new ParseThis\Parser( 'https://example.com/a-post/' ); 34 $result = $parse->fetch(); 35 if ( ! is_wp_error( $result ) ) { 36 $parse->parse( array( 'return' => 'single' ) ); 37 $jf2 = $parse->get(); // jf2 array. 38 $mf2 = $parse->get( 'mf2' ); // Or the same result as mf2. 39 } 40 41 `ParseThis\Parser::parse()` accepts these arguments: 42 43 * `return`: `single` for one item (default) or `feed` for a list of items. 44 * `follow`: fetch and parse external author pages. Default false. 45 * `limit`: maximum number of feed items. Default 150. 46 * `jsonld`: try JSON-LD. Default true. 47 * `html`: fall back to meta tags. Default true. 48 * `references`: move nested objects with a URL (citations, cards, events, ...) into `refs`, as the jf2 spec describes. Default true. 49 * `location`: flatten a nested location into `latitude`, `longitude` and `altitude` properties, with `location` as a plain string. Default false. 50 * `alternate`: use a `rel=alternate` jf2 or mf2 version of the page if it has one. Default false. 51 * `require_content`: whether a summary alone isn't enough, so the page's WordPress REST API version is fetched for full content. Default: true for feeds, false otherwise. 52 * `always_arrays`: always return `category`, `photo`, `video`, `audio`, `syndication`, `like-of`, `repost-of`, `bookmark-of` and `in-reply-to` as arrays, as Microsub does. Default false, which follows jf2: a single value is not wrapped in an array. 53 * `debug`: include the raw data each source was read from (`_meta`, `_jsonld`, `_json`, `_yt`, `_ombed`, `_rest`). Default false. Before 2.0.0 this was added whenever `WP_DEBUG` was on. 54 55 To list a page's feeds instead, use `( new ParseThis\Discovery() )->fetch( $url )`. 56 57 = Output format = 58 59 Results follow [jf2](https://jf2.spec.indieweb.org/), with a few deliberate differences: 60 61 * A feed's entries are in `items`, as in Microsub timelines, rather than the JF2 Feed profile's `children`. 62 * Nested objects with a URL (citations, cards, events, products and any other type) are stored in `refs`, keyed by URL, as XRay does, rather than jf2's `references`. The property holds the URL. Authors stay cards. 63 * Microformats properties that Parse This doesn't know are passed through as they are. 64 * Entries get a `post-type` property from [Post Type Discovery](https://www.w3.org/TR/post-type-discovery/). 65 * Reviews can be an h-review, or an h-entry with `review-of` (a URL, or a nested h-cite, h-card, h-event, h-item or h-product) and `rating`, `best` and `worst`, as proposed in [microformats/h-entry#32](https://github.com/microformats/h-entry/issues/32) and published by Post Kinds. Both, and an h-entry that is also an h-review, get the post type `review`. 66 * `author` is always a card (or a list of cards), never a plain string. 67 * Properties starting with an underscore (`_links`, `_rest`, ...) are internal or debugging data. 68 * A fetched result has `_code`, the HTTP status (or the status the page declares with `<meta http-equiv="Status">`, such as 410 for a deleted post's stub), and `_source_format`, what it was read from: `mf2+html` (microformats), `html` (meta tags only), `mf2+json`, `jf2+json`, `feed+json` (JSON Feed), `xml` (RSS or Atom) or `wordpress` (REST API). After redirects, the document is read from, and relative links resolve against, the address it ended up at. 69 * When a URL can't be parsed, `fetch()` returns a `WP_Error`: `invalid-url`, `not_found` (404), `unauthorized` (401), `forbidden` (403), `http_error` (any other 4xx or 5xx, with the status as `response_code` in the error data), `content-type` (a format Parse This doesn't read), or the request error itself (a timeout, for example). 70 71 Values from fetched pages are sanitized: URL properties only contain `http` and `https` URLs, plain-text properties (`name`, `summary`, `category`, `content`'s `text`) have no HTML tags, and `content`'s `html` is limited to a safe set of tags. Escape values when you output them all the same, as with any remote data. Debugging data (`_jsonld`, `_meta`, ...) is not sanitized. 72 73 = REST API = 74 75 `GET /wp-json/parse-this/1.0/parse?url=https://example.com/` 76 77 * `url` (required): the URL to parse. 78 * `return`, `follow`, `references`, `location`, `require_content`, `always_arrays`, `debug`: as for `parse()` above. 79 * `mf2`: return mf2 instead of jf2. 80 * `discovery`: list the URL's feeds instead of parsing it. 81 * `nocache`: fetch the URL again instead of using a cached result. 82 83 Results are cached for 15 minutes per URL and set of parameters, so pasting the same link again doesn't fetch it again. Requests with `debug` are never cached. 84 85 The endpoint requires the `edit_posts` capability (Contributors and above); the `parse_this_rest_capability` filter changes it. Anyone who can use it can also try it from **Tools > Parse This**. 86 87 = Filters = 88 89 * `pt_rewrite_secure`: the list of domains whose `http://` URLs are upgraded to `https://` before fetching. 90 * `parse_this_img_filters`: an image URL found in a page, after the built-in exclusions (ads, spinners, tracking pixels and so on). Return an empty string to drop it. 91 * `parse_this_url_shorteners`: the hosts treated as link shorteners. Links to them in a summary are expanded to where they redirect; other links are left as they are, without a request. 92 * `parse_this_native_html_parser`: whether to parse HTML with PHP's own HTML5 parser on PHP 8.4 and later. Default true. Return false to use the bundled masterminds/html5 parser instead; the two only differ on invalid markup, where PHP's parser does what browsers do. 93 * `parse_this_max_requests`: how many further requests one parse may make for followed author pages and short-link expansion. Default 10. Receives the URL. 94 * `parse_this_cache_lifetime`: how long REST endpoint results are cached, in seconds. Default 15 minutes. Return 0 to turn caching off. Receives the URL. 95 * `parse_this_rest_api_jf2_type`: the jf2 type for a post read through a site's WordPress REST API. Default `entry`. Receives the REST API post object, which includes its WordPress post type, and the site's REST API root URL. 96 97 = Helper functions = 98 99 All classes and functions are in the `ParseThis` namespace. 100 101 * `ParseThis\mf2_to_jf2()` and `ParseThis\jf2_to_mf2()` convert between the two formats. 102 * `ParseThis\post_type_discovery()` returns the IndieWeb post type (note, article, reply, like, photo, ...) of a jf2 or mf2 entry. 103 * `ParseThis\jf2_references()` and `ParseThis\jf2_location()` apply the `references` and `location` transformations to any jf2 object. 104 105 Every function and class is documented in the source. 106 107 = Upgrading from 1.x = 108 109 Version 2.0.0 moved everything into the `ParseThis` namespace and dropped the `Parse_This_` prefix from class names (for example `Parse_This_MF2` is now `ParseThis\MF2`, and `Parse_This` is `ParseThis\Parser`). The old names that Post Kinds and Yarns use still work for now, as deprecated aliases: 110 111 * Classes: `Parse_This`, `Parse_This_Discovery`, `Parse_This_MF2`, `Parse_This_MF2_Utils` and `REST_Parse_This`. 112 * Functions: `mf2_to_jf2()`, `jf2_to_mf2()`, `post_type_discovery()`, `pt_load_domdocument()` and `seconds_to_iso8601()`. 113 114 Other old global names are no longer defined. Please switch to the namespaced names. 115 116 = Using Parse This from another plugin = 117 118 The recommended way is to require the Parse This plugin rather than bundle a copy. On WordPress 6.5 and later, add `Requires Plugins: parse-this` to your plugin's header. On older WordPress and ClassicPress, which don't read that header, check that Parse This is loaded and new enough before using it: 119 120 add_action( 'plugins_loaded', function () { 121 if ( ! defined( 'PARSE_THIS_VERSION' ) || version_compare( PARSE_THIS_VERSION, '2.0.0', '<' ) ) { 122 // Show a notice asking for Parse This 2.0.0 or later, and skip the features that need it. 123 return; 124 } 125 // Use ParseThis\Parser and the rest here. 126 }, 11 ); 127 128 `PARSE_THIS_VERSION` is the version of the copy of Parse This that loaded. It isn't defined by copies older than 2.0.0, so an older copy bundled in another plugin shows up as missing. The `parse_this_loaded` action fires, with the version, once Parse This has loaded. 129 130 = Bundling Parse This in another plugin = 131 132 If you do bundle it, copy the plugin into your plugin (for example under `lib/parse-this/`) and load it only if the standalone plugin hasn't already, from `plugins_loaded` at priority 10 or later: 133 134 add_action( 'plugins_loaded', function () { 135 if ( ! function_exists( 'parse_this_loader' ) ) { 136 require_once __DIR__ . '/lib/parse-this/parse-this.php'; 137 parse_this_loader(); 138 } 139 }, 11 ); 140 141 The standalone plugin loads at priority 9, so when it is active it is used instead of your copy. Every function is also wrapped in `function_exists()` and classes are loaded by an autoloader, so if two copies do load, the first one wins. 142 143 Copies of Parse This 1.x bundled in other plugins keep working alongside 2.0: when the standalone plugin is active, their calls to the old names listed under "Upgrading from 1.x" are served by the standalone plugin. 26 144 27 145 == Frequently Asked Questions == 28 146 147 = What is jf2? = 148 149 jf2 is a JSON format derived from microformats2, but simpler: single values aren't wrapped in arrays, and types drop the `h-` prefix. See the [jf2 specification](https://jf2.spec.indieweb.org/). 150 151 = Why does a page return only a title and a URL? = 152 153 That page has no microformats, JSON-LD or useful meta tags, or it blocks automated requests. The **Tools > Parse This** page shows exactly what was parsed for a URL. 154 155 = Which copy is used when several plugins bundle Parse This? = 156 157 The first one loaded. If the standalone plugin is active, it is that one. See "Bundling Parse This in another plugin" above. 158 159 = Does it work with ClassicPress? = 160 161 Yes. It is tested with ClassicPress 2.7 on PHP 7.4 to 8.3. 162 29 163 == Changelog == 164 165 = 2.0.0 ( 2026-10-03 ) = 166 167 A major update. Please read "Upgrading from 1.x" if another plugin of yours uses Parse This. 168 169 Requirements: 170 171 * Requires PHP 7.4 and WordPress 6.2, or ClassicPress 2.x. Tested up to WordPress 7.1 and PHP 8.5. 172 173 Changes to be aware of: 174 175 * Everything is in the `ParseThis` namespace, and class names drop the `Parse_This_` prefix (`Parse_This` is now `ParseThis\Parser`). The old names Post Kinds and Yarns use still work, as deprecated aliases. 176 * Output: nested objects with a URL move to `refs`; authors are always cards; RSS and Atom items use `post-type` (not `post_type`); unknown microformats properties are kept; and fetched results carry `_code` and `_source_format`. 177 * `fetch()` returns a `WP_Error` for HTTP error pages (`not_found`, `unauthorized`, `forbidden`, `http_error`) instead of parsing them; a 410 Gone page is still parsed. 178 * The REST endpoint and the Tools > Parse This page require the `edit_posts` capability (filterable with `parse_this_rest_capability`), and its parameters are validated. 179 * Raw source data (`_meta`, `_jsonld` and so on) is only included with the new `debug` argument, not whenever `WP_DEBUG` is on. 180 * Plugins that use Parse This should require it rather than bundle it: see "Using Parse This from another plugin". `PARSE_THIS_VERSION` and the `parse_this_loaded` action help with that. 181 * The Instagram parser and the `ifset()` helper are removed. 182 183 New: 184 185 * Microformats always come first; JSON-LD, meta tags and the REST API only fill gaps. The `require_content` argument controls when a summary is enough. 186 * Microformats: choose the item a busy page is about; the full authorship algorithm, with feed entries inheriting the feed's author; reviews as an h-entry with `review-of`, `rating`, `best` and `worst` (as Post Kinds publishes them); complete h-event, h-review and h-recipe parsing; `follow-of`; and every value of repeated properties. 187 * Links with a fragment parse just that element; WordPress comment links fall back to the site's REST API. 188 * JSON-LD: reviews, products, recipes, and Article and Event subtypes, preferred over the WebPage node SEO plugins add. 189 * Feeds and JSON served with the wrong content type are recognized. JSON Feed items get plain text, a `post-type` and absolute URLs. 190 * Discovery finds mf2 JSON alternates, `rel=feed` links, RSS 1.0, multi-value `rel` attributes and JSON Feeds advertised as `application/json`, and follows permanent redirects. 191 * PHP's own HTML5 parser is used on PHP 8.4 and later: several times faster, and it handles invalid markup as browsers do (`parse_this_native_html_parser` turns it off). 192 * The `always_arrays` argument returns Microsub-style arrays. 193 * New filters: `parse_this_rest_api_jf2_type`, `parse_this_url_shorteners`, `parse_this_max_requests`, `parse_this_cache_lifetime`, `parse_this_rest_capability` and `parse_this_native_html_parser`. 194 195 Fixes: 196 197 * More than 50 bugs, many of them fatal errors on PHP 8, across RSS and Atom, the WordPress REST API, JSON-LD, meta tags, microformats, JSON Feed, X (Twitter), YouTube and feed discovery. 198 * Content HTML no longer loses the text before its first tag, and escaped text (`<code>`) no longer turns into markup. 199 * REST API posts are dated from their GMT dates; YouTube pages are read in full; titles that only repeat the content are dropped; names and ratings of "0" are kept; post types follow Post Type Discovery. 200 201 Performance: 202 203 * Feeds are downloaded once rather than twice; only link-shortener URLs in summaries are expanded; REST API tags and site details need far fewer requests; followed author pages are fetched once per request; REST endpoint results are cached for 15 minutes (`nocache` and `parse_this_cache_lifetime`). 204 205 Security: 206 207 * Output from fetched pages is sanitized (URLs limited to http and https, tags stripped from plain text, a safe set of HTML tags in content); one parse makes at most 10 further requests; the debug page sends its nonce in a header; plugin files exit when loaded outside WordPress; the OPML class handles invalid input safely. 208 209 Developers: 210 211 * Core's `fetch_feed()` is used for RSS and Atom; all requests go through `ParseThis\pt_remote_get()`. 212 * The bundled php-mf2 (0.5.0) and masterminds/html5 (2.11.0) are updated, and polyfills WordPress 6.2 provides are removed. 213 * Every function, class and hook is documented, and the plugin is tested on WordPress 6.2, the latest WordPress and ClassicPress 2.7 with PHP 7.4 to 8.5. 30 214 31 215 = 1.0.1 ( 2021-04-02 ) = … … 35 219 = 1.0.0 ( 2020-12-15 ) = 36 220 * First Official Release. Prior to this point it was in a point release. 221 222 == Upgrade Notice == 223 224 = 2.0.0 = 225 A major update: classes and functions move to the ParseThis namespace (the old names Post Kinds and Yarns use still work), the output format changes in places, and the REST endpoint now requires the edit_posts capability. Requires PHP 7.4 and WordPress 6.2. -
indieweb-post-kinds/trunk/readme.txt
r3067251 r3738687 2 2 Contributors: dshanske 3 3 Tags: indieweb, interaction, posts, webmention, share, like, scrobble 4 Stable tag: 3.7.35 Requires at least: 4.9.96 Requires PHP: 7. 07 Tested up to: 6.54 Stable tag: 4.0.0 5 Requires at least: 6.2 6 Requires PHP: 7.4 7 Tested up to: 7.1 8 8 License: GPLv2 or later 9 9 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 21 21 Many sites will not need all of the kinds set up. What kinds of posts you wish to publish are specific to your needs. 22 22 23 Post Kinds requires PHP 7.4 or later and WordPress 6.2 or later. It also supports ClassicPress 2.7.3 or later. 24 23 25 24 26 == Screenshots == … … 42 44 43 45 == Upgrade Notice == 46 47 = 4.0.0 = 48 49 Requires PHP 7.4 and WordPress 6.2 or ClassicPress 2.7.3. Response data is now escaped, changing some display helpers; stored post data upgrades in the background. Posts from apps and the REST API now get the Default Kind (Note unless changed), not Article. See the changelog. 44 50 45 51 = 3.7.0 = … … 118 124 either a response to something, such as a URL, or a more passive type of post where you are recording/logging something you did, for 119 125 example, watched a movie. 126 127 When you write a post in the Classic Editor, choose its kind in the Kind box. For kinds that respond to something, fill in the Response Properties box: 128 129 * **URL** - the page you are responding to. When you leave this field, the plugin fetches the page and fills in the other fields from it where it can. You can edit or clear anything it fills in. 130 * **Name** - the title of what you are responding to. 131 * **RSVP** and **Rating** - shown only for the kinds that use them: RSVP for RSVPs, and a rating for reviews, listens, watches, reads, plays and similar. 132 * **Details** - more about what you are responding to: a summary, quote or caption (plain text), the site name, publication or album, when it was published and updated, its tags (separated by semicolons) and a featured image URL. For listens, watches, photos and similar, it also has the duration, or a start and end time. 133 * **Author** - the name, URL and photo of the author or artist. Separate several authors with semicolons. 134 * **Upload or Attach Media** - for photo, video and audio posts, attaches a file from the media library. 135 * **Clear** - empties every field. 136 137 The plugin uses these fields to display the response above your post's content, marked up with [microformats](https://microformats.org/wiki/h-entry) so other sites can read it. 120 138 121 139 = How do I interact with other sites? = … … 203 221 However if you are interested in creating your own there is functionality around it. 204 222 205 register_post_kind(206 'reply',223 register_post_kind( 224 'reply', 207 225 array( 208 'singular_name' => __( 'Reply', 'indieweb-post-kinds' ), // Name for one instance of the kind 209 'name' => __( 'Replies', 'indieweb-post-kinds' ), // General name for the kind plural 210 'verb' => __( 'Replied to', 'indieweb-post-kinds' ), // The string for the verb or action (liked this) 211 'property' => 'in-reply-to', // microformats 2 property 212 'format' => 'link', // Post Format that maps to this 213 'description' => __( 'a reply to content typically on another site', 'indieweb-post-kinds' ), 214 'description-url' => 'http://indieweb.org/reply', 215 'title' => false, // Should this kind have an explicit title 216 'show' => true, // Show in Settings 217 ) 226 'singular_name' => __( 'Reply', 'indieweb-post-kinds' ), // Name for one instance of the kind. 227 'name' => __( 'Replies', 'indieweb-post-kinds' ), // General name for the kind, plural. 228 'verb' => __( 'Replied to', 'indieweb-post-kinds' ), // The verb or action, such as "Liked". 229 'property' => 'in-reply-to', // Microformats 2 property. 230 'format' => 'link', // Post format that maps to this kind. 231 'description' => __( 'a reply to content typically on another site', 'indieweb-post-kinds' ), 232 'description_url' => 'http://indieweb.org/reply', 233 'title' => false, // Whether this kind should have an explicit title. 234 'show' => true, // Whether to show the kind in Settings. 218 235 ) 219 );220 221 Add a function with your kind in the above format, hooking it in the init hook and it will add the Kind to the system.236 ); 237 238 Call it from a function hooked to `init` and it will add the Kind to the system. The [developer notes](https://github.com/dshanske/indieweb-post-kinds/blob/trunk/develop.txt) list the other arguments. 222 239 223 240 = Can I enable one of the Kinds you plan to offer in future? = … … 225 242 `set_post_kind_visibility( $slug, $show = true )` - If you add this function in early on, it will change the visibility of a kind. 226 243 227 = Can I create archives for each kind? = =244 = Can I create archives for each kind? = 228 245 229 246 Post Kinds automatically handles the display of archives of individual types. So to view all the posts marked as "note", for example, one could visit the URL http://www.YOURSITE.COM/kind/note/. 230 247 Simply replace YOURSITE.COM with your particular site name and the particular post kind name to access the others. 231 248 249 To use another word than "kind" in these URLs, for example in your own language, set the Kind base on Settings > Permalinks. Links to the old URLs then stop working, as when the category base changes. 250 232 251 You can also add the date /kind/note/2018/12/24 to see date-based archives. 233 252 234 For archives if you add exclude_kind as a query variable it will exclude specific kinds from the query `?exclude=kind&exclude_terms=note`. You can also do this as /exclude/kind/note,checkin as it accepts multiple values 235 236 There is also a special photo photo included, using ?kind_photos=1 or /photos or /photos/yyyy or /photos/yyyy/mm or /photos/kind/note or any other taxonomy. This will use the photo enhancements introduced in 3.4.0 to only show photos from253 To leave specific kinds out of an archive, use `?exclude=kind&exclude_terms=note` or /exclude/kind/note,checkin, which accepts several kinds. 254 255 There is also a special photo archive, using ?kind_photos=1 or /photos or /photos/yyyy or /photos/yyyy/mm or /photos/kind/note or any other taxonomy. This will use the photo enhancements introduced in 3.4.0 to only show photos from 237 256 all types of posts. 238 257 … … 270 289 == Theme Support == 271 290 272 Post Kinds automatically adds information to `the_content` and `the_excerpt` filter. Being as this is inside the content block, which may or may not be desirable, you may remove these filters as noted 273 below and call `kind_display` directly. This will allow it to appear outside the content block. To remove the automatic display, add the following to your theme. 274 * `add_filter( 'kind_content_display', '__return_false' );` 275 276 The functions `has_post_kind`, `set_post_kind`, and `set_post_kind` will allow you to manipulate the kind settings in a post. `get_post_kind_string` will return the display name of a kind. 277 278 If you want to customize the look of the display, you can create a directory in your theme called `kind_views`, copy the file from the views directory of the plugin, and modify it. This will persist 279 through future plugin updates. 291 Post Kinds works with most themes without changes. It adds the response (what you replied to, liked, listened to and so on) to the post's content and excerpt, and to feeds. 292 293 To place the response somewhere else in your theme, turn off the automatic display and call `kind_display()` in your template where you want it: 294 295 add_filter( 'kind_content_display', '__return_false' ); 296 297 Many kinds have no title. In templates that list posts, `kind_get_the_title()` returns the post's title, or generates one from the kind and what it responds to. 298 299 To change how a kind looks, create a `kind_views` folder in your theme, copy the view from the plugin's `views` folder into it, and edit the copy. It persists through plugin updates, but it doesn't receive fixes: compare it with the plugin's views after upgrading. 300 301 To style responses, target the `.response` wrapper that each view uses, and `.kind-embed` around embedded content. 302 303 The template functions, hooks and view variables are listed in the [developer notes](https://github.com/dshanske/indieweb-post-kinds/blob/trunk/develop.txt). 280 304 281 305 282 306 == Changelog == 283 307 284 * 3.7.3 ( 2024-04-09 ) = 308 = 4.0.0 ( 2026-10-10 ) = 309 * Raise the minimum requirements to PHP 7.4 and WordPress 6.2, and declare ClassicPress 2.7.3 support 310 * The `kind` field in the REST API now has a description and lists the valid kinds; an unknown kind is rejected with `rest_invalid_param` 311 * When the old IndieWeb Taxonomy plugin is active, activation now stops with a translatable message and deactivates Post Kinds itself, instead of a file that isn't a plugin 312 * The Default Kind setting applies to every new post without a kind, including posts from apps, the REST API, Quick Draft and scheduled drafts, which used to get Article. Set Default Kind to Article to keep the old behaviour. A translated site no longer gets a stray copy of the Article kind 313 * Escape response data in views, the editor, widgets and settings; summaries are displayed as plain text 314 * Sanitize input from the editor, the media modal's artist fields, settings and widgets; citation summaries are saved as plain text 315 * An RSVP can now be cleared in the editor 316 * Fix the Response Properties box disappearing for every kind after another box was collapsed while editing a note or article; it can no longer be hidden through Screen Options, since the editor shows it only for kinds that use it 317 * Kind icons can be resized from a theme with any rule, such as `.svg-icon { width: 1rem; height: 1rem; }`. The inline style is gone, the plugin's styles have no specificity, and icons have a default size where the stylesheet doesn't load, such as feed readers 318 * Deleting the plugin removes its settings and scheduled events, on every site of a multisite network. Posts, their kinds and their response data are kept 319 * Several authors are saved as one h-card each, pairing the semicolon-separated names, URLs and photos by position, and every author is displayed. Lists of authors from Micropub or link previews are shown and filled in too. Authors saved by earlier versions are read the same way. An author with a URL but no photo is linked, and clearing the author fields removes the authors 320 * Add a Kind base setting to Settings > Permalinks, to change the `kind` in kind archive URLs such as /kind/like/, and the `kind_base_slug` filter 321 * Fix durations calculated from the start and end times never being shown; `calculate_duration()` now also accepts date objects 322 * Require the edit_posts capability for the post-kinds/1.0/fields REST route; an unknown kind returns a 404 323 * Add a background upgrade that moves and repairs stored citations and records the media in each post; `wp post-kinds upgrade` runs it on demand (`--dry-run`, `--post=<id>`) 324 * Fix citations losing their type when viewed (a checkin lost its h-card, for example) and nesting inside themselves when saved again; the upgrade repairs existing posts 325 * Fix old citations stored as a plain URL being lost when viewed, and a PHP 8 fatal error when MF2_Post read very old response data 326 * Reading a post no longer writes to the database or downloads remote photos 327 * Performance: the media scan on save covers posts only and reads the content once; attachment lookups are cached; the photos archive and the home page kind filter use simpler queries; the settings page only writes kind terms that changed; icons, time zone offsets and permalink kinds are cached; the media fragment script loads only where there is audio or video, deferred 328 * The Kind Post widget prints nothing when it has no posts, instead of an unclosed wrapper 329 * New kind terms are named after the kind rather than its slug 330 * Feed change: The RSS and Atom feeds use WordPress's own templates instead of copies, and other plugins' feed handlers are no longer removed. Posts without a title have an empty title element instead of none, so the Atom feed is valid; turn on "Automatically add the Kind to the Title" to fill it in. Dates are written in UTC. In comment feeds, comments on untitled posts name the post. 331 * Deprecated: The global functions `display_formatted_datetime()`, `divide_datetime()`, `build_datetime()`, `get_datetime_offset()`, `divide_interval()` and `build_interval()`. Use the `Kind_Time` methods of the same names. The global names still work but report a deprecation notice when debugging is on, and they will be removed in the next major release (5.0.0). Custom views copied into a theme may call them, so update those to the `Kind_Time` methods. 332 * Display change: Custom views copied into a theme's `kind_views` folder keep their old code. Copy them again from the plugin's `views` folder, or escape their output. 333 * Display change: Citation summaries are plain text, marked up as `p-summary` instead of `e-summary`. `Kind_View::get_summary()` returns the summary markup. 334 * Display change: `Kind_View::get_hcard()`, `get_cite_title()`, `get_site_name()`, `get_embed()`, `get_url_link()` and `display_duration()` return escaped markup; HTML passed as a name is shown as text. 335 * Display change: `kind_get_the_title()` escapes the content before the `kind_get_the_title_content` filter. 336 * Display change: `Kind_View::rsvp_text()` takes optional URL and name arguments and returns the full sentence. Called with only a type, it still returns a printf-ready string. The RSVP strings have changed, so translations need updating. 337 * Display change: `Kind_View::rating_text()` ignores non-numeric ratings and shows at most 10 stars. 338 * Upgrade change: Published posts with no kind are given one, based on their stored response properties, or Article. If the home page shows only selected kinds, Article is added to them so those posts stay; posts given another kind, such as Like, follow the selection. 339 * Upgrade change: Until the background upgrade reaches them, older posts may be missing from the photos archive; `wp post-kinds upgrade` finishes it at once. 340 * Removed: The `kind_photo_shortcode_exclude` filter, which never ran. 341 * Behaviour change: `MF2_Post::get_images()` no longer imports remote photos into the media library; it returns media library attachments only. 342 * Deprecated: The `MF2_Post` class. Use `Kind_Post`. Creating an `MF2_Post` reports a deprecation notice when debugging is on; the class will be removed in a future major version. 343 344 = 3.7.3 ( 2024-04-09 ) = 285 345 * Ensure widget array is instantiated 286 346 -
indieweb-post-kinds/trunk/templates/reply-author.php
r2399112 r3738687 1 <?php 2 /** 3 * Author tab of the Post Kinds metabox. 4 * 5 * Included by reply-metabox.php. 6 * 7 * @package Post_Kinds 8 * @since 3.0.0 9 * 10 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(). 11 */ 12 13 defined( 'ABSPATH' ) || exit; 14 ?> 1 15 <div id="kind-author" class="hide-if-js"> 2 <h4> <?php _e( 'Information on the Author or Artist of the Piece', 'indieweb-post-kinds' ); ?></h4>3 <?php _e( '(Multiple Entries separated by semicolon)', 'indieweb-post-kinds' ); ?><BR />16 <h4> <?php esc_html_e( 'Information on the Author or Artist of the Piece', 'indieweb-post-kinds' ); ?></h4> 17 <?php esc_html_e( '(Multiple Entries separated by semicolon)', 'indieweb-post-kinds' ); ?><BR /> 4 18 <p class="field-row"> 5 19 <label for="cite_author_name" class="three-quarters"> 6 <?php _e( 'Author', 'indieweb-post-kinds' ); ?>7 <input type="text" name="cite_author_name" id="cite_author_name" class="widefat" value="<?php echo $cite['author']['name']; ?>" />20 <?php esc_html_e( 'Author', 'indieweb-post-kinds' ); ?> 21 <input type="text" name="cite_author_name" id="cite_author_name" class="widefat" value="<?php echo esc_attr( $cite['author']['name'] ); ?>" /> 8 22 </label> 9 23 </p> 10 24 <p class="field-row"> 11 25 <label for="cite_author_url" class="three-quarters"> 12 <?php _e( 'Author URL', 'indieweb-post-kinds' ); ?>13 <input type="text" name="cite_author_url" id="cite_author_url" class="widefat" value="<?php echo $cite['author']['url']; ?>" />26 <?php esc_html_e( 'Author URL', 'indieweb-post-kinds' ); ?> 27 <input type="text" name="cite_author_url" id="cite_author_url" class="widefat" value="<?php echo esc_attr( $cite['author']['url'] ); ?>" /> 14 28 </label> 15 29 </p> 16 30 <p class="field-row"> 17 31 <label for="cite_author_photo" class="three-quarters"> 18 <?php _e( 'Author Photo URL', 'indieweb-post-kinds' ); ?>19 <input type="text" name="cite_author_photo" id="cite_author_photo" class="widefat" value="<?php echo $cite['author']['photo']; ?>" />32 <?php esc_html_e( 'Author Photo URL', 'indieweb-post-kinds' ); ?> 33 <input type="text" name="cite_author_photo" id="cite_author_photo" class="widefat" value="<?php echo esc_attr( $cite['author']['photo'] ); ?>" /> 20 34 </label> 21 35 </p> -
indieweb-post-kinds/trunk/templates/reply-details.php
r2399112 r3738687 1 <?php 2 /** 3 * Details tab of the Post Kinds metabox. 4 * 5 * Included by reply-metabox.php; includes reply-time.php. 6 * 7 * @package Post_Kinds 8 * @since 3.0.0 9 * 10 * @var Kind_Post $kind_post The post being edited. 11 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(). 12 */ 13 14 defined( 'ABSPATH' ) || exit; 15 ?> 1 16 <div id="kind-details" class="hide-if-js"> 2 <h4><?php_e( 'Information on what you are responding to', 'indieweb-post-kinds' ); ?></h4>17 <h4><?php esc_html_e( 'Information on what you are responding to', 'indieweb-post-kinds' ); ?></h4> 3 18 <label for="cite_summary"> 4 <?php _e( 'Summary/Quote/Caption', 'indieweb-post-kinds' ); ?>5 <textarea name="cite_summary" id="cite_summary" data-role="none" class="widefat"><?php echo $cite['summary']; ?></textarea>19 <?php esc_html_e( 'Summary/Quote/Caption', 'indieweb-post-kinds' ); ?> 20 <textarea name="cite_summary" id="cite_summary" data-role="none" class="widefat"><?php echo esc_textarea( html_entity_decode( wp_strip_all_tags( (string) $cite['summary'] ), ENT_QUOTES, get_bloginfo( 'charset' ) ) ); ?></textarea> 6 21 </label> 7 22 <p class="field-row"> 8 23 <label for="cite_publication" class="three-quarters"> 9 <?php _e( 'Site Name/Publication/Album', 'indieweb-post-kinds' ); ?>10 <input type="text" name="cite_publication" id="cite_publication" class="widefat" value="<?php echo $cite['publication']; ?>" />24 <?php esc_html_e( 'Site Name/Publication/Album', 'indieweb-post-kinds' ); ?> 25 <input type="text" name="cite_publication" id="cite_publication" class="widefat" value="<?php echo esc_attr( $cite['publication'] ); ?>" /> 11 26 </label> 12 27 </p> 13 28 <p class="field-row"> 14 <?php echo Kind_Metabox::kind_the_time( 'cite_published', __( 'Published/Released', 'indieweb-post-kinds' ), $cite['published'], 'published' ); ?>15 <?php echo Kind_Metabox::kind_the_time( 'cite_updated', __( 'Updated', 'indieweb-post-kinds' ), $cite['updated'], 'updated' ); ?>29 <?php echo Kind_Metabox::kind_the_time( 'cite_published', __( 'Published/Released', 'indieweb-post-kinds' ), $cite['published'], 'published' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::kind_the_time() escapes its output. ?> 30 <?php echo Kind_Metabox::kind_the_time( 'cite_updated', __( 'Updated', 'indieweb-post-kinds' ), $cite['updated'], 'updated' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::kind_the_time() escapes its output. ?> 16 31 </p> 17 32 <label for="cite_tags"> 18 <?php _e( 'Tags (semicolon separated)', 'indieweb-post-kinds' ); ?>19 <textarea name="cite_tags" id="cite_tags" data-role="none" class="widefat"><?php echo $cite['category']?></textarea>33 <?php esc_html_e( 'Tags (semicolon separated)', 'indieweb-post-kinds' ); ?> 34 <textarea name="cite_tags" id="cite_tags" data-role="none" class="widefat"><?php echo esc_textarea( $cite['category'] ); ?></textarea> 20 35 </label> 21 36 <p class="field-row"> 22 37 <label for="cite_featured" class="three-quarters"> 23 <?php _e( 'Featured Image', 'indieweb-post-kinds' ); ?>24 <input type="text" name="cite_featured" id="cite_featured" class="widefat" value="<?php echo $cite['featured']; ?>" />38 <?php esc_html_e( 'Featured Image', 'indieweb-post-kinds' ); ?> 39 <input type="text" name="cite_featured" id="cite_featured" class="widefat" value="<?php echo esc_attr( $cite['featured'] ); ?>" /> 25 40 </label> 26 41 </p> -
indieweb-post-kinds/trunk/templates/reply-metabox.php
r3001401 r3738687 1 1 <?php 2 /** 3 * Post Kinds metabox for the Classic Editor. 4 * 5 * Loaded by Kind_Metabox::reply_metabox(). It sets up $kind_post, $kind, $type 6 * and $cite, then includes reply-details.php and reply-author.php, which use them. 7 * 8 * @package Post_Kinds 9 * @since 3.0.0 10 */ 11 12 defined( 'ABSPATH' ) || exit; 13 2 14 wp_nonce_field( 'replykind_metabox', 'replykind_metabox_nonce' ); 3 15 $kind_post = new Kind_Post( get_the_ID() ); … … 10 22 } 11 23 12 if ( empty( $cite['url'] ) && array_key_exists( 'kindurl', $_GET ) ) { 13 $cite['url'] = esc_url_raw( $_GET['kindurl'] ); 24 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Prefills the URL on the new post screen; nothing is saved. 25 if ( empty( $cite['url'] ) && isset( $_GET['kindurl'] ) && is_string( $_GET['kindurl'] ) ) { 26 $cite['url'] = sanitize_url( wp_unslash( $_GET['kindurl'] ) ); 14 27 } 28 // phpcs:enable WordPress.Security.NonceVerification.Recommended 15 29 16 30 $attachment = 0; … … 18 32 19 33 if ( in_array( $kind, array( 'audio', 'video', 'photo' ) ) ) { 20 $attachment = attachment_url_to_postid( $cite['url'] );34 $attachment = empty( $cite['url'] ) ? 0 : Kind_Post::get_attachment_id( $cite['url'] ); 21 35 if ( $attachment ) { 22 36 $attachment_post = new Kind_Post( $attachment ); … … 28 42 29 43 ?> 30 <a href="#kind-details" class="show-kind-details button hide-if-no-js"><?php _e( 'Details', 'indieweb-post-kinds' ); ?></a>31 <a href="#kind-author" class="show-kind-author-details button hide-if-no-js"><?php _e( 'Author', 'indieweb-post-kinds' ); ?></a>32 <a id="add-kind-media" class="button hide-if-no-js hidden" href="javascript:;"> Upload or Attach Media</a>33 <button class="clear-kindmeta-button button hide-if-no-js"><?php _e( 'Clear', 'indieweb-post-kinds' ); ?></button>44 <a href="#kind-details" class="show-kind-details button hide-if-no-js"><?php esc_html_e( 'Details', 'indieweb-post-kinds' ); ?></a> 45 <a href="#kind-author" class="show-kind-author-details button hide-if-no-js"><?php esc_html_e( 'Author', 'indieweb-post-kinds' ); ?></a> 46 <a id="add-kind-media" class="button hide-if-no-js hidden" href="javascript:;"><?php esc_html_e( 'Upload or Attach Media', 'indieweb-post-kinds' ); ?></a> 47 <button class="clear-kindmeta-button button hide-if-no-js"><?php esc_html_e( 'Clear', 'indieweb-post-kinds' ); ?></button> 34 48 <p class="field-row"> 35 49 <label for="cite_url" class="three-quarters"> 36 <?php _e( 'URL', 'indieweb-post-kinds' ); ?>37 <input type="text" name="cite_url" id="cite_url" class="widefat" value="<?php echo $cite['url']; ?>" />50 <?php esc_html_e( 'URL', 'indieweb-post-kinds' ); ?> 51 <input type="text" name="cite_url" id="cite_url" class="widefat" value="<?php echo esc_attr( $cite['url'] ); ?>" /> 38 52 </label> 39 53 </p> 40 54 <p class="field-row"> 41 55 <label for="cite_name" class="three-quarters"> 42 <?php _e( 'Name', 'indieweb-post-kinds' ); ?>43 <input type="text" name="cite_name" id="cite_name" class="widefat" value="<?php echo $cite['name']; ?>" />56 <?php esc_html_e( 'Name', 'indieweb-post-kinds' ); ?> 57 <input type="text" name="cite_name" id="cite_name" class="widefat" value="<?php echo esc_attr( $cite['name'] ); ?>" /> 44 58 </label> 45 59 </p> 46 60 <p class="field-row hide-if-js" id="rsvp-option"> 47 <?php echo Kind_Metabox::rsvp_select( $kind_post->get( 'rsvp' ) ); ?>61 <?php echo Kind_Metabox::rsvp_select( $kind_post->get( 'rsvp' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::rsvp_select() escapes its output. ?> 48 62 </p> 49 63 <p class="field-row hide-if-js" id="rating-option"> 50 <?php echo Kind_Metabox::rating_select( $kind_post->get( 'rating' ) ); ?>64 <?php echo Kind_Metabox::rating_select( $kind_post->get( 'rating' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::rating_select() escapes its output. ?> 51 65 </p> 52 66 <p id="kind-media hide-if-no-js"> … … 59 73 } elseif ( wp_attachment_is( 'audio', $attachment ) ) { 60 74 $view = new Kind_Media_View( $attachment, 'audio' ); 61 echo $view->get(); 75 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 62 76 } elseif ( wp_attachment_is( 'video', $attachment ) ) { 63 77 $view = new Kind_Media_View( $attachment, 'video' ); 64 echo $view->get(); 78 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 65 79 } 66 80 } 67 81 ?> 68 82 </div> 69 <input type="hidden" id="cite_media" name="cite_media" value="<?php echo $attachment; ?>" >83 <input type="hidden" id="cite_media" name="cite_media" value="<?php echo absint( $attachment ); ?>" > 70 84 </p> 71 85 -
indieweb-post-kinds/trunk/templates/reply-time.php
r2399112 r3738687 1 <?php $duration = divide_interval( $kind_post->get_duration() ); ?> 1 <?php 2 /** 3 * Duration fields of the Post Kinds metabox. 4 * 5 * Included by reply-details.php. 6 * 7 * @package Post_Kinds 8 * @since 3.0.0 9 * 10 * @var Kind_Post $kind_post The post being edited. 11 */ 12 13 defined( 'ABSPATH' ) || exit; 14 15 $duration = Kind_Time::divide_interval( $kind_post->get_duration() ); ?> 2 16 3 17 <div id="kind-time" class="hide-if-js"> 4 <h4><?php _e( 'Duration', 'indieweb-post-kinds' ); ?></h4>18 <h4><?php esc_html_e( 'Duration', 'indieweb-post-kinds' ); ?></h4> 5 19 <p class="field-row"> 6 20 <label for="duration" class="eighth"> 7 <?php _e( 'Years', 'indieweb-post-kinds' ); ?>8 <input type="number" name="duration_years" id="duration_years" size="3" value="<?php echo $duration['Y']; ?>" />21 <?php esc_html_e( 'Years', 'indieweb-post-kinds' ); ?> 22 <input type="number" name="duration_years" id="duration_years" size="3" value="<?php echo absint( $duration['Y'] ); ?>" /> 9 23 </label> 10 24 <label for="duration" class="eighth"> 11 <?php _e( 'Months', 'indieweb-post-kinds' ); ?>12 <input type="number" name="duration_months" id="duration_months" size="2" min="0" max="12" value="<?php echo $duration['M']; ?>" />25 <?php esc_html_e( 'Months', 'indieweb-post-kinds' ); ?> 26 <input type="number" name="duration_months" id="duration_months" size="2" min="0" max="12" value="<?php echo absint( $duration['M'] ); ?>" /> 13 27 </label> 14 28 <label for="duration" class="eighth"> 15 <?php _e( 'Days', 'indieweb-post-kinds' ); ?>16 <input type="number" name="duration_days" id="duration_days" size="2" min="0" max="30" value="<?php echo $duration['D']; ?>" />29 <?php esc_html_e( 'Days', 'indieweb-post-kinds' ); ?> 30 <input type="number" name="duration_days" id="duration_days" size="2" min="0" max="30" value="<?php echo absint( $duration['D'] ); ?>" /> 17 31 </label> 18 32 <p> 19 33 <p class="field-row"> 20 34 <label for="duration" class="eighth"> 21 <?php _e( 'Hours', 'indieweb-post-kinds' ); ?>22 <input type="number" name="duration_hours" id="duration_hours" size="2" min="0" max="12" value="<?php echo $duration['H']; ?>" />35 <?php esc_html_e( 'Hours', 'indieweb-post-kinds' ); ?> 36 <input type="number" name="duration_hours" id="duration_hours" size="2" min="0" max="12" value="<?php echo absint( $duration['H'] ); ?>" /> 23 37 </label> 24 38 <label for="duration" class="eighth"> 25 <?php _e( 'Minutes', 'indieweb-post-kinds' ); ?>26 <input type="number" name="duration_minutes" id="duration_minutes" size="2" min="0" max="60" value="<?php echo $duration['I']; ?>" />39 <?php esc_html_e( 'Minutes', 'indieweb-post-kinds' ); ?> 40 <input type="number" name="duration_minutes" id="duration_minutes" size="2" min="0" max="60" value="<?php echo absint( $duration['I'] ); ?>" /> 27 41 </label> 28 42 <label for="duration" class="eighth"> 29 <?php _e( 'Seconds', 'indieweb-post-kinds' ); ?>30 <input type="number" name="duration_seconds" id="duration_seconds" size="2" min="0" max="60" value="<?php echo $duration['S']; ?>" />43 <?php esc_html_e( 'Seconds', 'indieweb-post-kinds' ); ?> 44 <input type="number" name="duration_seconds" id="duration_seconds" size="2" min="0" max="60" value="<?php echo absint( $duration['S'] ); ?>" /> 31 45 </label> 32 46 </p> 33 <h4> <?php _e( 'Start Time and End Time will be Used to Calculate Duration if Duration Not Set.', 'indieweb-post-kinds' ); ?> </h4>47 <h4> <?php esc_html_e( 'Start Time and End Time will be Used to Calculate Duration if Duration Not Set.', 'indieweb-post-kinds' ); ?> </h4> 34 48 <p class="field-row"> 35 <?php echo Kind_Metabox::kind_the_time( 'mf2_start', __( 'Start Time', 'indieweb-post-kinds' ), $kind_post->get_datetime_property( 'start' ), 'start' );?>36 <?php echo Kind_Metabox::kind_the_time( 'mf2_end', __( 'End Time', 'indieweb-post-kinds' ), $kind_post->get_datetime_property( 'end' ), 'end' );?>37 </p>49 <?php echo Kind_Metabox::kind_the_time( 'mf2_start', __( 'Start Time', 'indieweb-post-kinds' ), $kind_post->get_datetime_property( 'start' ), 'start' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::kind_the_time() escapes its output. ?> 50 <?php echo Kind_Metabox::kind_the_time( 'mf2_end', __( 'End Time', 'indieweb-post-kinds' ), $kind_post->get_datetime_property( 'end' ), 'end' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Metabox::kind_the_time() escapes its output. ?> 51 </p> 38 52 </div> -
indieweb-post-kinds/trunk/views/kind-article.php
r1828595 r3738687 1 1 <?php 2 /* 3 * Article Template2 /** 3 * Article view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.4.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; -
indieweb-post-kinds/trunk/views/kind-audio.php
r2406063 r3738687 1 1 <?php 2 /* 3 * Audio Template2 /** 3 * Audio view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 6 $audios = $kind_post->get_audio(); 7 $duration = null; 22 23 defined( 'ABSPATH' ) || exit; 24 25 $audios = $kind_post->get_audio(); 26 $duration = null; 8 27 $publication = null; 9 28 … … 11 30 if ( 1 === count( $audios ) && 0 !== $audios[0] ) { 12 31 $audio_attachment = new Kind_Post( $audios[0] ); 13 $cite =mf2_to_jf2( $audio_attachment->get_cite() );32 $cite = \ParseThis\mf2_to_jf2( $audio_attachment->get_cite() ); 14 33 if ( ! $cite ) { 15 34 $cite = array(); … … 18 37 $author = Kind_View::get_hcard( $cite['author'] ); 19 38 } else { 20 $author = null;39 $author = null; 21 40 } 22 41 if ( array_key_exists( 'duration', $cite ) ) { … … 30 49 <section class="response"> 31 50 <header> 32 <?php echo Kind_Taxonomy::get_before_kind( 'audio' ); 51 <?php 52 echo Kind_Taxonomy::get_before_kind( 'audio' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 33 53 if ( isset( $cite['name'] ) ) { 34 echo sprintf( '<span>%1s</a>', $cite['name']);54 printf( '<span class="p-name">%1$s</span>', esc_html( $cite['name'] ) ); 35 55 } 36 56 if ( $duration ) { 37 printf( '(%1$s)', $duration ); 57 printf( '(%1$s)', $duration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 38 58 } 39 59 ?> … … 42 62 <?php 43 63 if ( $embed ) { 44 printf( '<blockquote class="e-summary">%1 s</blockquote>', $embed );64 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 45 65 } elseif ( $audios ) { 46 66 $view = new Kind_Media_View( $audios, 'audio' ); 47 echo $view->get(); 67 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 48 68 } -
indieweb-post-kinds/trunk/views/kind-bookmark.php
r2467905 r3738687 1 1 <?php 2 /* 3 * Bookmark Template2 /** 3 * Bookmark view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 15 33 <header> 16 34 <?php 17 echo Kind_Taxonomy::get_before_kind( 'bookmark' ); 35 echo Kind_Taxonomy::get_before_kind( 'bookmark' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 18 36 if ( ! $embed ) { 19 if ( ! empty( $cite['url'] ) ) { 20 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $cite['url'], $cite['name'] ); 21 } else { 22 echo sprintf( '<span class="p-name">%1s</span>', esc_html( $cite['name'] ) ); 37 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 38 if ( $author ) { 39 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 23 40 } 24 if ( $author ) { 25 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author; 26 } 27 if ( ! empty( $cite['publication'] ) ) { 28 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', esc_html( $cite['publication'] ) ); 41 $site_name = Kind_View::get_site_name( $cite ); 42 if ( $site_name ) { 43 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 29 44 } 30 45 } … … 33 48 <?php 34 49 if ( ! empty( $embed ) ) { 35 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );36 } else if ( ! empty( $cite['summary'] ) ){37 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );50 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 51 } else { 52 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 38 53 } 39 54 40 // Close Response 55 // Close Response. 41 56 ?> 42 57 </section> -
indieweb-post-kinds/trunk/views/kind-checkin.php
r2459401 r3738687 1 1 <?php 2 /* 3 * Checkin Template2 /** 3 * Checkin view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 14 32 <header> 15 33 <?php 16 echo Kind_Taxonomy::get_before_kind( 'checkin' ); 34 echo Kind_Taxonomy::get_before_kind( 'checkin' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 17 35 if ( ! $embed ) { 18 36 if ( empty( $cite['name'] ) ) { … … 20 38 } 21 39 if ( ! empty( $url ) ) { 22 echo sprintf( '<a href="%1s" class="u-checkin h-card">%2s</a>', $url, $cite['name']);40 printf( '<a href="%1$s" class="u-checkin h-card">%2$s</a>', esc_url( $url ), esc_html( $cite['name'] ) ); 23 41 } else { 24 echo sprintf( '<span class="h-card p-checkin">%1s</span>', $cite['name']);42 printf( '<span class="h-card p-checkin">%1$s</span>', esc_html( $cite['name'] ) ); 25 43 } 26 44 } … … 30 48 if ( $cite ) { 31 49 if ( $embed ) { 32 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );50 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 33 51 } 34 52 } … … 36 54 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 37 55 $view = new Kind_Media_View( $photos, 'photo' ); 38 echo $view->get(); 56 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 39 57 } 40 // Close Response 58 // Close Response. 41 59 ?> 42 60 </section> -
indieweb-post-kinds/trunk/views/kind-drink.php
r2459401 r3738687 1 1 <?php 2 /* 3 * Drink Template2 /** 3 * Drink view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 13 31 <header> 14 32 <?php 15 echo Kind_Taxonomy::get_before_kind( 'drink' ); 33 echo Kind_Taxonomy::get_before_kind( 'drink' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 16 34 if ( ! $embed ) { 17 if ( ! empty( $url ) ) { 18 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 19 } else { 20 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 21 } 22 if ( array_key_exists( 'publication', $cite ) ) { 23 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 35 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 36 $site_name = Kind_View::get_site_name( $cite ); 37 if ( $site_name ) { 38 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 24 39 } 25 40 } … … 29 44 if ( $cite ) { 30 45 if ( $embed ) { 31 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );32 } else if ( array_key_exists( 'summary', $cite ) ){33 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );46 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 47 } else { 48 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 34 49 } 35 50 } … … 37 52 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 38 53 $view = new Kind_Media_View( $photos, 'photo' ); 39 echo $view->get(); 54 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 40 55 } 41 // Close Response 56 // Close Response. 42 57 ?> 43 58 </section> -
indieweb-post-kinds/trunk/views/kind-eat.php
r3001401 r3738687 1 1 <?php 2 /* 3 * Eat Template2 /** 3 * Eat view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 9 27 } 10 28 11 $rating = $kind_post->get( 'rating', true );29 $rating = $kind_post->get( 'rating', true ); 12 30 ?> 13 31 … … 16 34 <header> 17 35 <?php 18 echo Kind_Taxonomy::get_before_kind( 'eat' ); 36 echo Kind_Taxonomy::get_before_kind( 'eat' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 19 37 if ( ! $embed ) { 20 if ( ! empty( $url ) ) { 21 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 22 } else { 23 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 24 } 25 if ( ! empty( $cite['publication'] ) ) { 26 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 38 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 39 $site_name = Kind_View::get_site_name( $cite ); 40 if ( $site_name ) { 41 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 27 42 } 28 43 } … … 32 47 if ( $cite ) { 33 48 if ( $embed ) { 34 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );35 } else if ( array_key_exists( 'summary', $cite ) ){36 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );49 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 50 } else { 51 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 37 52 } 38 53 } 39 54 40 55 if ( $rating ) { 41 echo '<data class="p-rating" value="' . $rating . '">' . sprintf( Kind_View::rating_text( $rating ), $url, $title) . '</data>';42 } 56 echo '<data class="p-rating" value="' . esc_attr( $rating ) . '">' . esc_html( Kind_View::rating_text( $rating ) ) . '</data>'; 57 } 43 58 44 59 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 45 60 $view = new Kind_Media_View( $photos, 'photo' ); 46 echo $view->get(); 61 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 47 62 } 48 // Close Response 63 // Close Response. 49 64 ?> 50 65 </section> -
indieweb-post-kinds/trunk/views/kind-event.php
r2459401 r3738687 1 1 <?php 2 /* 3 * Event Template2 /** 3 * Event view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 3.2.0 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 ?> … … 10 28 <header> 11 29 <?php 12 echo Kind_Taxonomy::get_before_kind( 'event' ); 13 echo get_the_title();30 echo Kind_Taxonomy::get_before_kind( 'event' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 31 the_title(); 14 32 ?> 15 33 </header> 16 34 <?php 17 35 $start = $kind_post->get( 'start' ); 18 $end = $kind_post->get( 'end' ); 19 $timestring = '<p>%1$s: <time class="%2$s" datetime="%3$s">%4$s</time></p>'; 36 $end = $kind_post->get( 'end' ); 20 37 21 38 if ( $start ) { 22 printf( $timestring, __( 'Start', 'indieweb-post-kinds' ), 'dt-start', $start->format( DATE_W3C ), display_formatted_datetime( $start) );23 } 39 printf( '<p>%1$s: <time class="dt-start" datetime="%2$s">%3$s</time></p>', esc_html__( 'Start', 'indieweb-post-kinds' ), esc_attr( $start->format( DATE_W3C ) ), esc_html( Kind_Time::display_formatted_datetime( $start ) ) ); 40 } 24 41 if ( $end ) { 25 printf( $timestring, __( 'End', 'indieweb-post-kinds' ), 'dt-end', $end->format( DATE_W3C ), display_formatted_datetime( $end) );26 } 42 printf( '<p>%1$s: <time class="dt-end" datetime="%2$s">%3$s</time></p>', esc_html__( 'End', 'indieweb-post-kinds' ), esc_attr( $end->format( DATE_W3C ) ), esc_html( Kind_Time::display_formatted_datetime( $end ) ) ); 43 } 27 44 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 28 45 $view = new Kind_Media_View( $photos, 'photo' ); 29 echo $view->get(); 46 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 30 47 } 31 // Close Response 48 // Close Response. 32 49 ?> 33 50 </section> -
indieweb-post-kinds/trunk/views/kind-favorite.php
r2455262 r3738687 1 1 <?php 2 /* 3 * Favorite Template2 /** 3 * Favorite view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 14 32 <header> 15 33 <?php 16 echo Kind_Taxonomy::get_before_kind( 'favorite' ); 34 echo Kind_Taxonomy::get_before_kind( 'favorite' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 17 35 if ( ! $embed ) { 18 if ( ! empty( $url ) ) { 19 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 20 } else { 21 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 36 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 37 if ( $author ) { 38 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 22 39 } 23 if ( $author ) { 24 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author; 25 } 26 if ( ! empty( $cite['publication'] ) ) { 27 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 40 $site_name = Kind_View::get_site_name( $cite ); 41 if ( $site_name ) { 42 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 28 43 } 29 44 } … … 33 48 if ( $cite ) { 34 49 if ( $embed ) { 35 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );36 } else if ( array_key_exists( 'summary', $cite ) ){37 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );50 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 51 } else { 52 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 38 53 } 39 54 } 40 55 41 // Close Response 56 // Close Response. 42 57 ?> 43 58 </section> -
indieweb-post-kinds/trunk/views/kind-issue.php
r2467905 r3738687 1 1 <?php 2 /* 3 * Issue Template2 /** 3 * Issue view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 14 32 <header> 15 33 <?php 16 echo Kind_Taxonomy::get_before_kind( 'issue' ); 34 echo Kind_Taxonomy::get_before_kind( 'issue' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 17 35 if ( ! $embed ) { 18 if ( ! empty( $url ) ) { 19 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 20 } else { 21 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 36 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 37 if ( $author ) { 38 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 22 39 } 23 if ( $author ) { 24 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author; 25 } 26 if ( ! empty( $cite['publication'] ) ) { 27 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 40 $site_name = Kind_View::get_site_name( $cite ); 41 if ( $site_name ) { 42 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 28 43 } 29 44 } … … 33 48 if ( $cite ) { 34 49 if ( $embed ) { 35 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );36 } else if ( array_key_exists( 'summary', $cite ) ){37 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );50 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 51 } else { 52 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 38 53 } 39 54 } 40 55 41 // Close Response 56 // Close Response. 42 57 ?> 43 58 </section> -
indieweb-post-kinds/trunk/views/kind-itinerary.php
r2387393 r3738687 1 1 <?php 2 /* 3 * Event Template2 /** 3 * Itinerary view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 3.2.0 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 6 22 23 defined( 'ABSPATH' ) || exit; 7 24 8 25 $itineraries = $kind_post->get( 'itinerary', false ); 26 $itineraries = is_array( $itineraries ) ? $itineraries : array(); 9 27 10 foreach( $itineraries as $key => $value ) { 11 $itineraries [ $key ] = mf2_to_jf2( $value ); 28 foreach ( $itineraries as $key => $value ) { 29 $leg = \ParseThis\mf2_to_jf2( $value ); 30 // Each field is shown as text; fill in any that are missing. 31 foreach ( array( 'operator', 'number', 'transit-type', 'origin', 'destination', 'departure', 'arrival' ) as $field ) { 32 $leg[ $field ] = ( isset( $leg[ $field ] ) && is_scalar( $leg[ $field ] ) ) ? (string) $leg[ $field ] : ''; 33 } 34 $itineraries[ $key ] = $leg; 12 35 } 13 36 … … 18 41 <header> 19 42 <?php 20 echo Kind_Taxonomy::get_before_kind( 'itinerary' ); 21 echo get_the_title();43 echo Kind_Taxonomy::get_before_kind( 'itinerary' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 44 the_title(); 22 45 ?> 23 46 </header> 24 47 <?php 25 foreach ( $itineraries as $itinerary ) {26 ?>27 <div class="h-leg p-itinerary">28 <h3><span class="p-operator"><?php echo $itinerary['operator']; ?></span>29 <span class="p-number"><?php echo $itinerary['number']; ?></span></h3>30 <data class="p-transit-type" value="<?php echo $itinerary['transit-type']; ?>">31 <ul>48 foreach ( $itineraries as $itinerary ) { 49 ?> 50 <div class="h-leg p-itinerary"> 51 <h3><span class="p-operator"><?php echo esc_html( $itinerary['operator'] ); ?></span> 52 <span class="p-number"><?php echo esc_html( $itinerary['number'] ); ?></span></h3> 53 <data class="p-transit-type" value="<?php echo esc_attr( $itinerary['transit-type'] ); ?>"></data> 54 <ul> 32 55 <li> 33 <?php _e( 'Departs: ', 'indieweb-post-kinds' ); ?>34 <span class="p-origin"><?php echo $itinerary['origin']; ?></span>35 <time class="dt-departure" datetime="<?php echo $itinerary['departure']; ?>"><?php echo display_formatted_datetime( $itinerary['departure']); ?></time>56 <?php esc_html_e( 'Departs: ', 'indieweb-post-kinds' ); ?> 57 <span class="p-origin"><?php echo esc_html( $itinerary['origin'] ); ?></span> 58 <time class="dt-departure" datetime="<?php echo esc_attr( $itinerary['departure'] ); ?>"><?php echo esc_html( Kind_Time::display_formatted_datetime( $itinerary['departure'] ) ); ?></time> 36 59 </li> 37 60 <li> 38 <?php _e( 'Arrives: ', 'indieweb-post-kinds' ); ?>39 <span class="p-destination"><?php echo $itinerary['destination']; ?></span>40 <time class="dt-arrival" datetime="<?php echo $itinerary['arrival']; ?>"><?php echo display_formatted_datetime( $itinerary['arrival']); ?></time>61 <?php esc_html_e( 'Arrives: ', 'indieweb-post-kinds' ); ?> 62 <span class="p-destination"><?php echo esc_html( $itinerary['destination'] ); ?></span> 63 <time class="dt-arrival" datetime="<?php echo esc_attr( $itinerary['arrival'] ); ?>"><?php echo esc_html( Kind_Time::display_formatted_datetime( $itinerary['arrival'] ) ); ?></time> 41 64 </li> 42 </ul>43 </div>44 <?php65 </ul> 66 </div> 67 <?php 45 68 } 46 69 47 70 48 // Close Response 71 // Close Response. 49 72 ?> 50 73 </section> -
indieweb-post-kinds/trunk/views/kind-jam.php
r2467905 r3738687 1 1 <?php 2 /* 3 Jam Template2 /** 3 * Jam view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 20 38 <header> 21 39 <?php 22 echo Kind_Taxonomy::get_before_kind( 'jam' ); 40 echo Kind_Taxonomy::get_before_kind( 'jam' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 23 41 if ( ! $embed ) { 24 42 if ( $title ) { 25 echo $title; 43 echo $title; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 26 44 } 27 45 if ( $author ) { 28 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author;46 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 29 47 } 30 48 if ( $site_name ) { 31 echo __( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>';49 echo esc_html__( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 32 50 } 33 51 if ( $duration ) { 34 echo Kind_View::display_duration( $duration ); 52 echo Kind_View::display_duration( $duration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 35 53 } 36 54 } … … 40 58 if ( $cite ) { 41 59 if ( $embed ) { 42 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );43 } else if ( array_key_exists( 'summary', $cite ) ){44 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );60 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 61 } else { 62 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 45 63 } 46 64 } … … 48 66 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 49 67 $view = new Kind_Media_View( $photos, 'photo' ); 50 echo $view->get(); 68 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 51 69 } 52 // Close Response 70 // Close Response. 53 71 ?> 54 72 </section> -
indieweb-post-kinds/trunk/views/kind-like.php
r2467905 r3738687 1 1 <?php 2 /* 3 * Like Template2 /** 3 * Like view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 15 33 <header> 16 34 <?php 17 echo Kind_Taxonomy::get_before_kind( 'like' ); 35 echo Kind_Taxonomy::get_before_kind( 'like' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 18 36 if ( ! $embed ) { 19 if ( ! empty( $url ) ) { 20 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 21 } else { 22 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 37 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 38 if ( $author ) { 39 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 23 40 } 24 if ( $author ) { 25 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author; 26 } 27 if ( ! empty( $cite['publication'] ) ) { 28 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 41 $site_name = Kind_View::get_site_name( $cite ); 42 if ( $site_name ) { 43 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 29 44 } 30 45 } … … 34 49 if ( $cite ) { 35 50 if ( $embed ) { 36 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );37 } else if ( array_key_exists( 'summary', $cite ) ){38 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );51 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 52 } else { 53 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 39 54 } 40 55 } 41 56 42 // Close Response 57 // Close Response. 43 58 ?> 44 59 </section> -
indieweb-post-kinds/trunk/views/kind-listen.php
r3001401 r3738687 1 1 <?php 2 /* 3 Listen Template2 /** 3 * Listen view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 11 29 $title = Kind_View::get_cite_title( $cite ); 12 30 $duration = $kind_post->get( 'duration', true ); 13 $rating = $kind_post->get( 'rating', true );31 $rating = $kind_post->get( 'rating', true ); 14 32 if ( ! $duration ) { 15 33 $duration = calculate_duration( $kind_post->get( 'start' ), $kind_post->get( 'end' ) ); … … 22 40 <header> 23 41 <?php 24 echo Kind_Taxonomy::get_before_kind( 'listen' ); 42 echo Kind_Taxonomy::get_before_kind( 'listen' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 25 43 if ( ! $embed ) { 26 44 if ( $title ) { 27 echo $title; 45 echo $title; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 28 46 } 29 47 if ( $author ) { 30 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author;48 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 31 49 } 32 50 if ( $site_name ) { 33 echo __( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>';51 echo esc_html__( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 34 52 } 35 53 if ( $duration ) { 36 echo '(' . Kind_View::display_duration( $duration ) . ')'; 54 echo '(' . Kind_View::display_duration( $duration ) . ')'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 37 55 } 38 56 } … … 42 60 if ( $cite ) { 43 61 if ( $embed ) { 44 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );45 } else if ( array_key_exists( 'summary', $cite ) ){46 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );62 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 63 } else { 64 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 47 65 } 48 66 } 49 67 50 // Close Response 68 // Close Response. 51 69 ?> 52 70 </section> 53 71 54 <?php if ( $rating ) { 55 echo '<data class="p-rating" value="' . $rating . '">' . sprintf( Kind_View::rating_text( $rating ), $url, $title ) . '</data>'; 56 } ?> 72 <?php 73 if ( $rating ) { 74 echo '<data class="p-rating" value="' . esc_attr( $rating ) . '">' . esc_html( Kind_View::rating_text( $rating ) ) . '</data>'; 75 } 76 ?> 57 77 58 78 <?php 59 79 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 60 80 $view = new Kind_Media_View( $photos, 'photo' ); 61 echo $view->get(); 81 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 62 82 } 63 83 ?> -
indieweb-post-kinds/trunk/views/kind-note.php
r1828595 r3738687 1 1 <?php 2 /* 3 * Note Template2 /** 3 * Note view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.4.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; -
indieweb-post-kinds/trunk/views/kind-photo.php
r2459401 r3738687 1 1 <?php 2 /* 3 * Photo Template2 /** 3 * Photo view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 24 6 25 if ( $photos ) { 7 26 $embed = null; … … 11 30 if ( 1 === count( $photos ) ) { 12 31 $photos_attachment = new Kind_Post( $photos[0] ); 13 $cite = $photos_attachment->get_cite();14 $cite = $photos_attachment->normalize_cite( $cite );15 $author = Kind_View::get_hcard( $cite['author'] );32 $cite = $photos_attachment->get_cite(); 33 $cite = $photos_attachment->normalize_cite( $cite ); 34 $author = Kind_View::get_hcard( $cite['author'] ); 16 35 } 17 36 } … … 21 40 <header> 22 41 <?php 23 echo Kind_Taxonomy::get_before_kind( 'photo' ); 42 echo Kind_Taxonomy::get_before_kind( 'photo' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 24 43 if ( ! empty( $cite['name'] ) ) { 25 echo sprintf( '<span>%1s</span>', $cite['name']);44 printf( '<span class="p-name">%1$s</span>', esc_html( $cite['name'] ) ); 26 45 } 27 46 ?> … … 29 48 <?php 30 49 if ( $embed ) { 31 printf( '<blockquote class="e-summary">%1 s</blockquote>', $embed );50 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 32 51 } elseif ( $photos ) { 33 52 $view = new Kind_Media_View( $photos, 'photo' ); 34 echo $view->get(); 35 } ?> 53 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 54 } 55 ?> 36 56 </section> -
indieweb-post-kinds/trunk/views/kind-read.php
r3001401 r3738687 1 1 <?php 2 /* 3 * Read Template2 /** 3 * Read view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 9 27 } 10 28 $read = $kind_post->get( 'read-status', true ); 11 $rating = $kind_post->get( 'rating', true );29 $rating = $kind_post->get( 'rating', true ); 12 30 13 31 ?> … … 15 33 <header> 16 34 <?php 17 echo Kind_Taxonomy::get_before_kind( 'read' ); 35 echo Kind_Taxonomy::get_before_kind( 'read' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 18 36 if ( ! $embed ) { 19 37 if ( $read ) { 20 echo sprintf( ' - <span class="p-read-status">%1s</span>', Kind_View::read_text( $read) );38 printf( ' - <span class="p-read-status">%1$s</span>', esc_html( Kind_View::read_text( $read ) ) ); 21 39 } 22 if ( ! empty( $url ) ) { 23 echo sprintf( '<a href="%1s" class="p-name u-url">%2s</a>', $url, $cite['name'] ); 24 } else { 25 echo sprintf( '<span class="p-name">%1s</span>', $cite['name'] ); 40 echo Kind_View::get_cite_title( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 41 if ( $author ) { 42 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 26 43 } 27 if ( $author ) { 28 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author; 29 } 30 if ( empty( $cite['publication'] ) ) { 31 echo sprintf( ' <em>(<span class="p-publication">%1s</span>)</em>', $cite['publication'] ); 44 $site_name = Kind_View::get_site_name( $cite ); 45 if ( $site_name ) { 46 echo ' <em>(' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 32 47 } 33 48 } … … 37 52 if ( $cite ) { 38 53 if ( $embed ) { 39 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );40 } else if ( array_key_exists( 'summary', $cite ) && ! empty( $cite['summary'] ) ){41 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );54 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 55 } else { 56 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 42 57 } 43 58 } 44 59 45 60 if ( $rating ) { 46 echo '<data class="p-rating" value="' . $rating . '">' . sprintf( Kind_View::rating_text( $rating ), $url, $title) . '</data>';61 echo '<data class="p-rating" value="' . esc_attr( $rating ) . '">' . esc_html( Kind_View::rating_text( $rating ) ) . '</data>'; 47 62 } 48 63 … … 50 65 if ( $photos && ! has_post_thumbnail( get_the_ID() ) ) { 51 66 $view = new Kind_Media_View( $photos, 'photo' ); 52 echo $view->get(); 67 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 53 68 } 54 // Close Response 69 // Close Response. 55 70 ?> 56 71 </section> -
indieweb-post-kinds/trunk/views/kind-rsvp.php
r2394107 r3738687 1 1 <?php 2 /* 3 * RSVP Template2 /** 3 * RSVP view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { 8 26 return; 9 27 } 10 $title = isset( $cite['name'] ) ? $cite['name'] : $url;11 $rsvp = $kind_post->get( 'rsvp', true );28 $title = isset( $cite['name'] ) ? $cite['name'] : $url; 29 $rsvp = $kind_post->get( 'rsvp', true ); 12 30 13 31 ?> … … 16 34 <header> 17 35 <?php 18 echo Kind_Taxonomy::get_before_kind( 'rsvp' ); 36 echo Kind_Taxonomy::get_before_kind( 'rsvp' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 19 37 20 38 if ( ! $embed ) { 21 39 if ( $rsvp ) { 22 echo '<data class="p-rsvp" value="' . $rsvp . '">' . sprintf( Kind_View::rsvp_text( $rsvp ), $url, $title ) . '</data>';40 echo '<data class="p-rsvp" value="' . esc_attr( $rsvp ) . '">' . Kind_View::rsvp_text( $rsvp, $url, $title ) . '</data>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::rsvp_text() escapes the URL and name. 23 41 } 24 42 } … … 27 45 <?php 28 46 if ( $embed ) { 29 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );30 } else if ( array_key_exists( 'summary', $cite ) ){31 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );47 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 48 } else { 49 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 32 50 } 33 51 34 // Close Response 52 // Close Response. 35 53 ?> 36 54 </section> -
indieweb-post-kinds/trunk/views/kind-video.php
r2459401 r3738687 1 1 <?php 2 /* 3 * Video Template2 /** 3 * Video view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 6 22 7 $videos = $kind_post->get_video(); 23 defined( 'ABSPATH' ) || exit; 24 25 $videos = $kind_post->get_video(); 8 26 $duration = null; 9 27 if ( is_array( $videos ) ) { 10 28 if ( 1 === count( $videos ) && 0 !== $videos[0] ) { 11 29 $video_attachment = new Kind_Post( $videos[0] ); 12 $cite = $video_attachment->get_cite();13 $cite = $video_attachment->normalize_cite( $cite );14 $author = Kind_View::get_hcard( $cite['author'] );15 30 $cite = $video_attachment->get_cite(); 31 $cite = $video_attachment->normalize_cite( $cite ); 32 $author = Kind_View::get_hcard( $cite['author'] ); 33 16 34 if ( array_key_exists( 'duration', $cite ) ) { 17 35 $duration = Kind_View::display_duration( $cite['duration'] ); … … 22 40 } 23 41 $first_photo = null; 24 if ( is_ countable( $photos ) ) {42 if ( is_array( $photos ) && ! empty( $photos ) ) { 25 43 $first_photo = $photos[0]; 26 44 } 27 45 if ( is_array( $cite ) && ! $videos ) { 28 46 if ( ! $embed ) { 29 $view = new Kind_Media_View( $url, 'video' );47 $view = new Kind_Media_View( $url, 'video' ); 30 48 $embed = $view->get(); 31 49 } … … 37 55 <header> 38 56 <?php 39 echo Kind_Taxonomy::get_before_kind( 'video' ); 57 echo Kind_Taxonomy::get_before_kind( 'video' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 40 58 if ( isset( $cite['name'] ) ) { 41 echo sprintf( '<span>%1s</a>', $cite['name']);59 printf( '<span class="p-name">%1$s</span>', esc_html( $cite['name'] ) ); 42 60 } 43 61 44 62 if ( $author ) { 45 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author;63 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 46 64 } 47 65 if ( $duration ) { 48 printf( '(%1$s)', $duration ); 66 printf( '(%1$s)', $duration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 49 67 } 50 68 … … 54 72 <?php 55 73 if ( $embed ) { 56 printf( '<blockquote class="e-summary">%1 s</blockquote>', $embed );74 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 57 75 } elseif ( $videos ) { 58 76 59 77 $poster = wp_get_attachment_image_url( $first_photo, 'full' ); 60 $view = new Kind_Media_View( $videos, 'video' );61 echo $view->get(); 78 $view = new Kind_Media_View( $videos, 'video' ); 79 echo $view->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_Media_View::get() returns core gallery, audio or video shortcode markup. 62 80 } 63 81 ?> -
indieweb-post-kinds/trunk/views/kind-watch.php
r3001401 r3738687 1 1 <?php 2 /* 3 Watch Template2 /** 3 * Watch view. 4 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.6.1 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 if ( ! $cite ) { … … 11 29 $title = Kind_View::get_cite_title( $cite, $url ); 12 30 $duration = $kind_post->get( 'duration', true ); 13 $rating = $kind_post->get( 'rating', true );31 $rating = $kind_post->get( 'rating', true ); 14 32 if ( ! $duration ) { 15 33 $duration = calculate_duration( $kind_post->get( 'start' ), $kind_post->get( 'end' ) ); … … 21 39 <header> 22 40 <?php 23 echo Kind_Taxonomy::get_before_kind( 'watch' ); 41 echo Kind_Taxonomy::get_before_kind( 'watch' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 24 42 if ( ! $embed ) { 25 43 if ( $title ) { 26 echo $title; 44 echo $title; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 27 45 } 28 46 if ( $author ) { 29 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author;47 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 30 48 } 31 49 if ( $site_name ) { 32 echo __( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>';50 echo esc_html__( ' from ', 'indieweb-post-kinds' ) . '<em>' . $site_name . '</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 33 51 } 34 52 if ( $duration ) { 35 echo Kind_View::display_duration( $duration ); 53 echo Kind_View::display_duration( $duration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 36 54 } 37 55 } … … 41 59 if ( $cite ) { 42 60 if ( $embed ) { 43 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );44 } else if ( array_key_exists( 'summary', $cite ) ){45 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );61 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 62 } else { 63 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 46 64 } 47 65 } 48 66 49 // Close Response 67 // Close Response. 50 68 ?> 51 69 </section> 52 70 53 <?php if ( $rating ) { 54 echo '<data class="p-rating" value="' . $rating . '">' . sprintf( Kind_View::rating_text( $rating ), $url, $title ) . '</data>'; 55 } ?> 71 <?php 72 if ( $rating ) { 73 echo '<data class="p-rating" value="' . esc_attr( $rating ) . '">' . esc_html( Kind_View::rating_text( $rating ) ) . '</data>'; 74 } 75 ?> 56 76 57 77 -
indieweb-post-kinds/trunk/views/kind.php
r3001401 r3738687 1 1 <?php 2 /* 3 Default Template 4 * The Goal of this Template is to be a general all-purpose model that will be replaced by customization in other templates 2 /** 3 * Default view, used for any kind without its own view. 4 * 5 * Rendered by Kind_View::get_view_part(). To change it, copy this file into a 6 * kind_views folder in your theme; see develop.txt. 7 * 8 * @package Post_Kinds 9 * @since 2.3.0 10 * 11 * @var Kind_Post $kind_post The post being displayed. 12 * @var string $kind The kind's slug, such as 'like'. 13 * @var string $type The kind's microformats property, such as 'like-of'. 14 * @var array $cite The citation, normalized by Kind_Post::normalize_cite(), 15 * so every key is present. 'name' falls back to the URL. 16 * @var string $url The cited URL. 17 * @var string|false $author The cited authors as h-card markup, listed as in "Ann, Bob and Cat", or false if there are none. 18 * @var string $embed Embed markup for the cited URL, or an empty string. 19 * @var int[]|false $photos Attachment IDs of the post's photos, from Kind_Post::get_photo(). 20 * @var array $args Arguments passed to Kind_View::get_view_part(); 'post_id' is the post's ID. 5 21 */ 22 23 defined( 'ABSPATH' ) || exit; 6 24 7 25 $site_name = Kind_View::get_site_name( $cite ); … … 11 29 $duration = calculate_duration( $kind_post->get( 'start' ), $kind_post->get( 'end' ) ); 12 30 } 13 $rsvp = $kind_post->get( 'rsvp', true );31 $rsvp = $kind_post->get( 'rsvp', true ); 14 32 $rating = $kind_post->get( 'rating', true ); 15 33 … … 18 36 } 19 37 20 // Add in the appropriate type 38 // Add in the appropriate type. 21 39 if ( ! empty( $type ) ) { 22 40 $type = ( empty( $url ) ? 'p-' : 'u-' ) . $type; … … 24 42 ?> 25 43 26 <section class="h-cite response <?php echo $type; ?>">44 <section class="h-cite response <?php echo esc_attr( $type ); ?>"> 27 45 <header> 28 46 <?php 29 echo Kind_Taxonomy::get_before_kind( $kind ); 47 echo Kind_Taxonomy::get_before_kind( $kind ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_before_kind() escapes the kind name; the icon is the plugin's own SVG. 30 48 if ( ! $embed ) { 31 49 if ( $title ) { 32 echo $title; 50 echo $title; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_cite_title() escapes its output. 33 51 } 34 52 if ( ! empty( $author ) ) { 35 echo ' ' . __( 'by', 'indieweb-post-kinds' ) . ' ' . $author;53 echo ' ' . esc_html__( 'by', 'indieweb-post-kinds' ) . ' ' . $author; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- h-cards from Kind_View::get_hcard(), which escapes them. 36 54 } 37 55 if ( $site_name ) { 38 echo '<em> (' . $site_name . ')</em>'; 56 echo '<em> (' . $site_name . ')</em>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_site_name() escapes its output. 39 57 } 40 58 if ( in_array( $kind, array( 'jam', 'listen', 'play', 'read', 'watch', 'audio', 'video' ) ) ) { 41 59 if ( $duration ) { 42 echo ' ' . Kind_View::display_duration( $duration ); 60 echo ' ' . Kind_View::display_duration( $duration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::display_duration() escapes its output. 43 61 } 44 62 } … … 49 67 if ( $cite && is_array( $cite ) ) { 50 68 if ( $embed ) { 51 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $embed );52 } else if ( array_key_exists( 'summary', $cite ) ){53 echo sprintf( '<blockquote class="e-summary">%1s</blockquote>', $cite['summary'] );69 printf( '<blockquote class="e-summary">%1$s</blockquote>', $embed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- oEmbed or media markup generated by WordPress core. 70 } else { 71 echo Kind_View::get_summary( $cite ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Kind_View::get_summary() escapes its output. 54 72 } 55 73 } 56 74 57 // Close Response 75 // Close Response. 58 76 ?> 59 77 </section> 60 78 61 <?php if ( $rsvp && in_array( $kind, array( 'rsvp' ) ) ) { 62 echo 'RSVP <span class="p-rsvp">' . $rsvp . '</span>'; 79 <?php 80 if ( $rsvp && in_array( $kind, array( 'rsvp' ) ) ) { 81 echo esc_html__( 'RSVP', 'indieweb-post-kinds' ) . ' <span class="p-rsvp">' . esc_html( $rsvp ) . '</span>'; 63 82 } 64 83 65 84 if ( $rating ) { 66 echo '<data class="p-rating" value="' . $rating . '">' . sprintf( Kind_View::rating_text( $rating ), $url, $title ) . '</data>'; 67 } ?> 85 echo '<data class="p-rating" value="' . esc_attr( $rating ) . '">' . esc_html( Kind_View::rating_text( $rating ) ) . '</data>'; 86 } 87 ?> 68 88 69 89 <?php
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)