| assets/css/icons.css | ●●●●● patch | view | raw | blame | history | |
| inc/blocks/MenuBlock.php | ●●●●● patch | view | raw | blame | history | |
| inc/meta/Group.php | ●●●●● patch | view | raw | blame | history | |
| inc/meta/Meta.php | ●●●●● patch | view | raw | blame | history | |
| inc/meta/MetaOld.php | ●●●●● patch | view | raw | blame | history | |
| inc/meta/Repeater.php | ●●●●● patch | view | raw | blame | history |
assets/css/icons.css
@@ -1 +1 @@ .icon-google-logo{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjQsMTI4YTk2LDk2LDAsMSwxLTIxLjk1LTYxLjA5LDgsOCwwLDEsMS0xMi4zMywxMC4xOEE4MCw4MCwwLDEsMCwyMDcuNiwxMzZIMTI4YTgsOCwwLDAsMSwwLTE2aDg4QTgsOCwwLDAsMSwyMjQsMTI4WiIvPjwvc3ZnPg==');}.icon-apple-logo{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjMuMywxNjkuNTlhOC4wNyw4LjA3LDAsMCwwLTIuOC0zLjRDMjAzLjUzLDE1NC41MywyMDAsMTM0LjY0LDIwMCwxMjBjMC0xNy42NywxMy40Ny0zMy4wNiwyMS41LTQwLjY3YTgsOCwwLDAsMCwwLTExLjYyQzIwOC44Miw1NS43NCwxODcuODIsNDgsMTY4LDQ4YTcyLjIsNzIuMiwwLDAsMC00MCwxMi4xMyw3MS41Niw3MS41NiwwLDAsMC05MC43MSw5LjA5QTc0LjYzLDc0LjYzLDAsMCwwLDE2LDEyMy40YTEyNy4wNiwxMjcuMDYsMCwwLDAsNDAuMTQsODkuNzNBMzkuOCwzOS44LDAsMCwwLDgzLjU5LDIyNGg4Ny42OGEzOS44NCwzOS44NCwwLDAsMCwyOS4xMi0xMi41NywxMjUsMTI1LDAsMCwwLDE3LjgyLTI0LjZDMjI1LjIzLDE3NCwyMjQuMzMsMTcyLDIyMy4zLDE2OS41OVptLTM0LjYzLDMwLjk0YTIzLjc2LDIzLjc2LDAsMCwxLTE3LjQsNy40N0g4My41OWEyMy44MiwyMy44MiwwLDAsMS0xNi40NC02LjUxQTExMS4xNCwxMTEuMTQsMCwwLDEsMzIsMTIzLDU4LjUsNTguNSwwLDAsMSw0OC42NSw4MC40Nyw1NC44MSw1NC44MSwwLDAsMSw4OCw2NGguNzhBNTUuNDUsNTUuNDUsMCwwLDEsMTIzLDc2LjI4YTgsOCwwLDAsMCwxMCwwQTU1LjQ0LDU1LjQ0LDAsMCwxLDE2OCw2NGE3MC42NCw3MC42NCwwLDAsMSwzNiwxMC4zNWMtMTMsMTQuNTItMjAsMzAuNDctMjAsNDUuNjUsMCwyMy43Nyw3LjY0LDQyLjczLDIyLjE4LDU1LjNBMTA1LjgyLDEwNS44MiwwLDAsMSwxODguNjcsMjAwLjUzWk0xMjguMjMsMzBBNDAsNDAsMCwwLDEsMTY3LDBoMWE4LDgsMCwwLDEsMCwxNmgtMWEyNCwyNCwwLDAsMC0yMy4yNCwxOCw4LDgsMCwxLDEtMTUuNS00WiIvPjwvc3ZnPg==');}.icon-check-circle{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNzMuNjYsOTguMzRhOCw4LDAsMCwxLDAsMTEuMzJsLTU2LDU2YTgsOCwwLDAsMS0xMS4zMiwwbC0yNC0yNGE4LDgsMCwwLDEsMTEuMzItMTEuMzJMMTEyLDE0OC42OWw1MC4zNC01MC4zNUE4LDgsMCwwLDEsMTczLjY2LDk4LjM0Wk0yMzIsMTI4QTEwNCwxMDQsMCwxLDEsMTI4LDI0LDEwNC4xMSwxMDQuMTEsMCwwLDEsMjMyLDEyOFptLTE2LDBhODgsODgsMCwxLDAtODgsODhBODguMSw4OC4xLDAsMCwwLDIxNiwxMjhaIi8+PC9zdmc+');}details.all-filters summary::after,.icon-faders-horizontal{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNzYsODBhOCw4LDAsMCwxLDgtOGgzMmE4LDgsMCwwLDEsMCwxNkgxODRBOCw4LDAsMCwxLDE3Niw4MFpNNDAsODhIMTQ0djE2YTgsOCwwLDAsMCwxNiwwVjU2YTgsOCwwLDAsMC0xNiwwVjcySDQwYTgsOCwwLDAsMCwwLDE2Wm0xNzYsODBIMTIwYTgsOCwwLDAsMCwwLDE2aDk2YTgsOCwwLDAsMCwwLTE2Wk04OCwxNDRhOCw4LDAsMCwwLTgsOHYxNkg0MGE4LDgsMCwwLDAsMCwxNkg4MHYxNmE4LDgsMCwwLDAsMTYsMFYxNTJBOCw4LDAsMCwwLDg4LDE0NFoiLz48L3N2Zz4=');}.icon-cloud-slash{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik01My45MiwzNC42MkE4LDgsMCwxLDAsNDIuMDgsNDUuMzhMODEuMzIsODguNTVsLS4wNi4xMkE2NSw2NSwwLDAsMCw3Miw4OGE2NCw2NCwwLDAsMCwwLDEyOGg4OGE4Ny4zNCw4Ny4zNCwwLDAsMCwzMS44LTUuOTNsMTAuMjgsMTEuMzFhOCw4LDAsMSwwLDExLjg0LTEwLjc2Wk0xNjAsMjAwSDcyYTQ4LDQ4LDAsMCwxLDAtOTZjMS4xLDAsMi4yLDAsMy4zLjEyQTg4LjQsODguNCwwLDAsMCw3MiwxMjhhOCw4LDAsMCwwLDE2LDAsNzIuMjUsNzIuMjUsMCwwLDEsNS4wNi0yNi41NGw4Nyw5NS43QTcxLjY2LDcxLjY2LDAsMCwxLDE2MCwyMDBabTg4LTcyYTg3Ljg5LDg3Ljg5LDAsMCwxLTIyLjM1LDU4LjYxQTgsOCwwLDAsMSwyMTMuNzEsMTc2LDcyLDcyLDAsMCwwLDExNy4zNyw3MGE4LDgsMCwwLDEtOS40OC0xMi44OUE4OCw4OCwwLDAsMSwyNDgsMTI4WiIvPjwvc3ZnPg==');}.icon-exclamation-mark{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNDQsMjAwYTE2LDE2LDAsMSwxLTE2LTE2QTE2LDE2LDAsMCwxLDE0NCwyMDBabS0xNi00MGE4LDgsMCwwLDAsOC04VjQ4YTgsOCwwLDAsMC0xNiwwVjE1MkE4LDgsMCwwLDAsMTI4LDE2MFoiLz48L3N2Zz4=');}.icon-cloud-arrow-down{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yNDgsMTI4YTg3LjM0LDg3LjM0LDAsMCwxLTE3LjYsNTIuODEsOCw4LDAsMSwxLTEyLjgtOS42MkE3MS4zNCw3MS4zNCwwLDAsMCwyMzIsMTI4YTcyLDcyLDAsMCwwLTE0NCwwLDgsOCwwLDAsMS0xNiwwLDg4LDg4LDAsMCwxLDMuMjktMjMuODhDNzQuMiwxMDQsNzMuMSwxMDQsNzIsMTA0YTQ4LDQ4LDAsMCwwLDAsOTZIOTZhOCw4LDAsMCwxLDAsMTZINzJBNjQsNjQsMCwxLDEsODEuMjksODguNjgsODgsODgsMCwwLDEsMjQ4LDEyOFptLTY5LjY2LDQyLjM0TDE2MCwxODguNjlWMTI4YTgsOCwwLDAsMC0xNiwwdjYwLjY5bC0xOC4zNC0xOC4zNWE4LDgsMCwwLDAtMTEuMzIsMTEuMzJsMzIsMzJhOCw4LDAsMCwwLDExLjMyLDBsMzItMzJhOCw4LDAsMCwwLTExLjMyLTExLjMyWiIvPjwvc3ZnPg==');}details summary::after,.icon-caret-down{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTMuNjYsMTAxLjY2bC04MCw4MGE4LDgsMCwwLDEtMTEuMzIsMGwtODAtODBBOCw4LDAsMCwxLDUzLjY2LDkwLjM0TDEyOCwxNjQuNjlsNzQuMzQtNzQuMzVhOCw4LDAsMCwxLDExLjMyLDExLjMyWiIvPjwvc3ZnPg==');}.icon-cloud-arrow-up{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNzguMzQsMTY1LjY2LDE2MCwxNDcuMzFWMjA4YTgsOCwwLDAsMS0xNiwwVjE0Ny4zMWwtMTguMzQsMTguMzVhOCw4LDAsMCwxLTExLjMyLTExLjMybDMyLTMyYTgsOCwwLDAsMSwxMS4zMiwwbDMyLDMyYTgsOCwwLDAsMS0xMS4zMiwxMS4zMlpNMTYwLDQwQTg4LjA4LDg4LjA4LDAsMCwwLDgxLjI5LDg4LjY4LDY0LDY0LDAsMSwwLDcyLDIxNmg0MGE4LDgsMCwwLDAsMC0xNkg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTJBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDEwMC44LDY2LDgsOCwwLDAsMCwzLjIsMTUuMzQsNy45LDcuOSwwLDAsMCwzLjItLjY4QTg4LDg4LDAsMCwwLDE2MCw0MFoiLz48L3N2Zz4=');}.icon-cloud-check{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDBBODguMDksODguMDksMCwwLDAsODEuMjksODguNjcsNjQsNjQsMCwxLDAsNzIsMjE2aDg4YTg4LDg4LDAsMCwwLDAtMTc2Wm0wLDE2MEg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTFBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDcyLDcyWm0zNy42Ni05My42NmE4LDgsMCwwLDEsMCwxMS4zMmwtNDgsNDhhOCw4LDAsMCwxLTExLjMyLDBsLTI0LTI0YTgsOCwwLDAsMSwxMS4zMi0xMS4zMkwxNDQsMTQ4LjY5bDQyLjM0LTQyLjM1QTgsOCwwLDAsMSwxOTcuNjYsMTA2LjM0WiIvPjwvc3ZnPg==');}.icon-cloud-warning{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDBBODguMDksODguMDksMCwwLDAsODEuMjksODguNjcsNjQsNjQsMCwxLDAsNzIsMjE2aDg4YTg4LDg4LDAsMCwwLDAtMTc2Wm0wLDE2MEg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTFBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDcyLDcyWm0tOC03MlY4OGE4LDgsMCwwLDEsMTYsMHY0MGE4LDgsMCwwLDEtMTYsMFptMjAsMzZhMTIsMTIsMCwxLDEtMTItMTJBMTIsMTIsMCwwLDEsMTcyLDE2NFoiLz48L3N2Zz4=');}.icon-syncing{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0iY3VycmVudENvbG9yIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PHBhdGggaWQ9InJlZnJlc2giIGQ9Ik0xNjAuMDQ3IDEyMi44NzVhMzAuNzg0IDMwLjc4NCAwIDAgMC0yMS43NSA4Ljc5N2MtMi44NDIgMy4wMDMtLjQ2NyA0Ljk3MSAxLjMxMiAzLjE1NiAxMS4wNDMtMTAuNzg2IDI4LjcxLTEwLjY4IDM5LjYyNS4yMzRsNy4yMDMgNy4yMDRoLTEyLjg3NWMtMy4zNDcuMDA4LTMuMTY1IDMuODc1IDAgMy44NzVoMTYuMTFjMi4wNjIgMCAyLjU0LTEuNDE4IDIuNTYyLTQuOTdsLjA5NC0xNC45MjFjLjAyLTMuMjktMy40MzctMy4xNjUtMy40MzcgMHYxMi44NmwtNy4yMDMtNy4xODhhMzAuNzY4IDMwLjc2OCAwIDAgMC0yMS42NDEtOS4wNDd6bS0yOS41OTQgMzkuNzk3Yy0yLjA2MiAwLTIuNTI0IDEuNDAyLTIuNTQ3IDQuOTUzbC0uMDk0IDE0LjkyMmMtLjAyIDMuMjkgMy40MjIgMy4xNjQgMy40MjIgMHYtMTIuODZsNy4yMDMgNy4yMDRjMTEuOTU2IDExLjk1NSAzMS4zMTIgMTIuMDY0IDQzLjQwNy4yNSAyLjg0Mi0zLjAwMy40NTEtNC45ODgtMS4zMjgtMy4xNzItMTEuMDQzIDEwLjc4Ni0yOC43MSAxMC42OC0zOS42MjUtLjIzNWwtNy4xODgtNy4yMDNoMTIuODZjMy4zNDctLjAwOCAzLjE2NS0zLjg2IDAtMy44NmgtMTYuMTF6Ii8+PHBhdGggZD0iTTE2MCA0NGE4NC4xMSA4NC4xMSAwIDAgMC03Ni40MSA0OS4xMkE2MC43MSA2MC43MSAwIDAgMCA3MiA5MmE2MCA2MCAwIDAgMCAwIDEyMGg4OGE4NCA4NCAwIDAgMCAwLTE2OFptMCAxNjBINzJhNTIgNTIgMCAxIDEgOC41NS0xMDMuM0E4My42NiA4My42NiAwIDAgMCA3NiAxMjhhNCA0IDAgMCAwIDggMCA3NiA3NiAwIDEgMSA3NiA3NloiLz48L3N2Zz4=');}.icon-cloud-x{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDBBODguMDksODguMDksMCwwLDAsODEuMjksODguNjcsNjQsNjQsMCwxLDAsNzIsMjE2aDg4YTg4LDg4LDAsMCwwLDAtMTc2Wm0wLDE2MEg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTFBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDcyLDcyWm0yOS42Ni04Mi4zNEwxNzEuMzEsMTM2bDE4LjM1LDE4LjM0YTgsOCwwLDAsMS0xMS4zMiwxMS4zMkwxNjAsMTQ3LjMxbC0xOC4zNCwxOC4zNWE4LDgsMCwwLDEtMTEuMzItMTEuMzJMMTQ4LjY5LDEzNmwtMTguMzUtMTguMzRhOCw4LDAsMCwxLDExLjMyLTExLjMyTDE2MCwxMjQuNjlsMTguMzQtMTguMzVhOCw4LDAsMCwxLDExLjMyLDExLjMyWiIvPjwvc3ZnPg==');}.icon-arrows-clockwise{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjQsNDhWOTZhOCw4LDAsMCwxLTgsOEgxNjhhOCw4LDAsMCwxLDAtMTZoMjguNjlMMTgyLjA2LDczLjM3YTc5LjU2LDc5LjU2LDAsMCwwLTU2LjEzLTIzLjQzaC0uNDVBNzkuNTIsNzkuNTIsMCwwLDAsNjkuNTksNzIuNzEsOCw4LDAsMCwxLDU4LjQxLDYxLjI3YTk2LDk2LDAsMCwxLDEzNSwuNzlMMjA4LDc2LjY5VjQ4YTgsOCwwLDAsMSwxNiwwWk0xODYuNDEsMTgzLjI5YTgwLDgwLDAsMCwxLTExMi40Ny0uNjZMNTkuMzEsMTY4SDg4YTgsOCwwLDAsMCwwLTE2SDQwYTgsOCwwLDAsMC04LDh2NDhhOCw4LDAsMCwwLDE2LDBWMTc5LjMxbDE0LjYzLDE0LjYzQTk1LjQzLDk1LjQzLDAsMCwwLDEzMCwyMjIuMDZoLjUzYTk1LjM2LDk1LjM2LDAsMCwwLDY3LjA3LTI3LjMzLDgsOCwwLDAsMC0xMS4xOC0xMS40NFoiLz48L3N2Zz4=');}.icon-share-fat{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzcuNjYsMTA2LjM1bC04MC04MEE4LDgsMCwwLDAsMTQ0LDMyVjcyLjM1Yy0yNS45NCwyLjIyLTU0LjU5LDE0LjkyLTc4LjE2LDM0LjkxLTI4LjM4LDI0LjA4LTQ2LjA1LDU1LjExLTQ5Ljc2LDg3LjM3YTEyLDEyLDAsMCwwLDIwLjY4LDkuNThoMGMxMS0xMS43MSw1MC4xNC00OC43NCwxMDcuMjQtNTJWMTkyYTgsOCwwLDAsMCwxMy42Niw1LjY1bDgwLTgwQTgsOCwwLDAsMCwyMzcuNjYsMTA2LjM1Wk0xNjAsMTcyLjY5VjE0NGE4LDgsMCwwLDAtOC04Yy0yOC4wOCwwLTU1LjQzLDcuMzMtODEuMjksMjEuOGExOTYuMTcsMTk2LjE3LDAsMCwwLTM2LjU3LDI2LjUyYzUuOC0yMy44NCwyMC40Mi00Ni41MSw0Mi4wNS02NC44NkM5OS40MSw5OS43NywxMjcuNzUsODgsMTUyLDg4YTgsOCwwLDAsMCw4LThWNTEuMzJMMjIwLjY5LDExMloiLz48L3N2Zz4=');}.icon-trash{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTYsNDhIMTc2VjQwYTI0LDI0LDAsMCwwLTI0LTI0SDEwNEEyNCwyNCwwLDAsMCw4MCw0MHY4SDQwYTgsOCwwLDAsMCwwLDE2aDhWMjA4YTE2LDE2LDAsMCwwLDE2LDE2SDE5MmExNiwxNiwwLDAsMCwxNi0xNlY2NGg4YTgsOCwwLDAsMCwwLTE2Wk05Niw0MGE4LDgsMCwwLDEsOC04aDQ4YTgsOCwwLDAsMSw4LDh2OEg5NlptOTYsMTY4SDY0VjY0SDE5MlpNMTEyLDEwNHY2NGE4LDgsMCwwLDEtMTYsMFYxMDRhOCw4LDAsMCwxLDE2LDBabTQ4LDB2NjRhOCw4LDAsMCwxLTE2LDBWMTA0YTgsOCwwLDAsMSwxNiwwWiIvPjwvc3ZnPg==');}.icon-star{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzkuMTgsOTcuMjZBMTYuMzgsMTYuMzgsMCwwLDAsMjI0LjkyLDg2bC01OS00Ljc2TDE0My4xNCwyNi4xNWExNi4zNiwxNi4zNiwwLDAsMC0zMC4yNywwTDkwLjExLDgxLjIzLDMxLjA4LDg2YTE2LjQ2LDE2LjQ2LDAsMCwwLTkuMzcsMjguODZsNDUsMzguODNMNTMsMjExLjc1YTE2LjM4LDE2LjM4LDAsMCwwLDI0LjUsMTcuODJMMTI4LDE5OC40OWw1MC41MywzMS4wOEExNi40LDE2LjQsMCwwLDAsMjAzLDIxMS43NWwtMTMuNzYtNTguMDcsNDUtMzguODNBMTYuNDMsMTYuNDMsMCwwLDAsMjM5LjE4LDk3LjI2Wm0tMTUuMzQsNS40Ny00OC43LDQyYTgsOCwwLDAsMC0yLjU2LDcuOTFsMTQuODgsNjIuOGEuMzcuMzcsMCwwLDEtLjE3LjQ4Yy0uMTguMTQtLjIzLjExLS4zOCwwbC01NC43Mi0zMy42NWE4LDgsMCwwLDAtOC4zOCwwTDY5LjA5LDIxNS45NGMtLjE1LjA5LS4xOS4xMi0uMzgsMGEuMzcuMzcsMCwwLDEtLjE3LS40OGwxNC44OC02Mi44YTgsOCwwLDAsMC0yLjU2LTcuOTFsLTQ4LjctNDJjLS4xMi0uMS0uMjMtLjE5LS4xMy0uNXMuMTgtLjI3LjMzLS4yOWw2My45Mi01LjE2QTgsOCwwLDAsMCwxMDMsOTEuODZsMjQuNjItNTkuNjFjLjA4LS4xNy4xMS0uMjUuMzUtLjI1cy4yNy4wOC4zNS4yNUwxNTMsOTEuODZhOCw4LDAsMCwwLDYuNzUsNC45Mmw2My45Miw1LjE2Yy4xNSwwLC4yNCwwLC4zMy4yOVMyMjQsMTAyLjYzLDIyMy44NCwxMDIuNzNaIi8+PC9zdmc+');}.icon-alphabetical{--icon:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTgzLjc4IDE4NC4wNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNTkuNTg2IDY5Ljc0MmMtMC44NTEzIDAtMS40NjEgMC4xOTY1Ni0xLjgzNjYgMC41OTcxOC0wLjM1MDU0IDAuMzc1NTgtMC41Mjk1OCAxLjAyMjktMC41Mjk1OCAxLjk0OTNzMC4xNzkwMyAxLjU5MzcgMC41Mjk1OCAxLjk5NDRjMC4zNzU1OCAwLjM3NTU4IDAuOTg1MjkgMC41NjMzOCAxLjgzNjYgMC41NjMzOGg3LjAxOTdsLTEyLjQyOCAzNC4zNjZoLTIuMTA3Yy0wLjg1MTMgMC0xLjQ2MSAwLjE5NjU2LTEuODM2NiAwLjU5NzE4LTAuMzUwNTQgMC4zNzU1OC0wLjUyOTU3IDEuMDM0MS0wLjUyOTU3IDEuOTYwNiAwIDAuOTI2NDQgMC4xNzkwMyAxLjU4MjUgMC41Mjk1NyAxLjk4MyAwLjM3NTU4IDAuMzc1NTkgMC45ODUyOSAwLjU2MzM4IDEuODM2NiAwLjU2MzM4aDEyLjU1MmMwLjg1MTMgMCAxLjQ1MjItMC4xODc3OSAxLjgwMjgtMC41NjMzOCAwLjM3NTU4LTAuNDAwNjIgMC41NjMzNy0xLjA1NjYgMC41NjMzNy0xLjk4MyAwLTAuOTI2NDUtMC4xODc3OS0xLjU4NS0wLjU2MzM3LTEuOTYwNi0wLjM1MDU0LTAuNDAwNjItMC45NTE0Ny0wLjU5NzE4LTEuODAyOC0wLjU5NzE4aC00LjU1MjFsMy4xMjExLTguOTM0OWgxOC4yMmwzLjA3NiA4LjkzNDloLTUuMDcwNGMtMC44NTEzIDAtMS40NjEgMC4xOTY1Ni0xLjgzNjYgMC41OTcxOC0wLjM1MDU0IDAuMzc1NTgtMC41Mjk1OCAxLjAzNDEtMC41Mjk1OCAxLjk2MDYgMCAwLjkyNjQ0IDAuMTc5MDMgMS41ODI1IDAuNTI5NTggMS45ODMgMC4zNzU1OCAwLjM3NTU5IDAuOTg1MjkgMC41NjMzOCAxLjgzNjYgMC41NjMzOGgxMy4yOTZjMC44NTEzIDAgMS40NTIyLTAuMTg3NzkgMS44MDI4LTAuNTYzMzggMC4zNzU1OC0wLjQwMDYyIDAuNTYzMzctMS4wNTY2IDAuNTYzMzctMS45ODMgMC0wLjkyNjQ1LTAuMTg3NzktMS41ODUtMC41NjMzNy0xLjk2MDYtMC4zNTA1NC0wLjQwMDYyLTAuOTUxNDctMC41OTcxOC0xLjgwMjgtMC41OTcxOGgtMi4yODczbC0xMy4yNjItMzcuMDM2Yy0wLjMwMDQ3LTAuODUxMy0wLjc1OTk0LTEuNDYxLTEuMzg1OS0xLjgzNjYtMC42MDA5My0wLjQwMDYyLTEuNDA5Ny0wLjU5NzE4LTIuNDExMy0wLjU5NzE4em00NC4xNDYgMGMtMC44NTEzIDAtMS40NzIzIDAuMTk2NTYtMS44NDc4IDAuNTk3MTgtMC4zNTA1NSAwLjM3NTU4LTAuNTE4MyAxLjAyMjktMC41MTgzIDEuOTQ5M3YxMS45MWMwIDAuODc2MzMgMC4yMDUzMiAxLjUwNjEgMC42MzA5OCAxLjg4MTcgMC40MjU2NiAwLjM3NTU4IDEuMTU5MyAwLjU2MzM3IDIuMTg1OSAwLjU2MzM3czEuNzQ5LTAuMTg3NzkgMi4xNzQ3LTAuNTYzMzdjMC40MjU2OS0wLjM3NTU4IDAuNjQyMjYtMS4wMDUzIDAuNjQyMjYtMS44ODE3di05LjM1MTdoMTguODUxbC0yNC43NTQgMzUuMzAxYy0wLjM1MDU0IDAuNTI1ODItMC41MTgzMSAxLjA3MTctMC41MTgzMSAxLjYyMjYgMCAwLjkyNjQ1IDAuMTY3NzcgMS41ODI1IDAuNTE4MzEgMS45ODMxIDAuMzc1NTggMC4zNzU1OCAwLjk5NjU0IDAuNTYzMzggMS44NDc4IDAuNTYzMzhoMjguNzY2YzAuODUxMyAwIDEuNDUyMi0wLjE4NzggMS44MDI4LTAuNTYzMzggMC4zNzU1OC0wLjQwMDYyIDAuNTYzMzgtMS4wNTY2IDAuNTYzMzgtMS45ODMxdi0xMi42NjVjMC0wLjg3NjMzLTAuMjE2NTgtMS40OTQ4LTAuNjQyMjUtMS44NzA0LTAuNDI1NjYtMC4zNzU1OC0xLjE0OC0wLjU2MzM4LTIuMTc0Ny0wLjU2MzM4LTEuMDI2NiAwLTEuNzQ5IDAuMTg3NzktMi4xNzQ3IDAuNTYzMzgtMC40MjU2NiAwLjM3NTU4LTAuNjQyMjQgMC45OTQwMi0wLjY0MjI0IDEuODcwNHYxMC4xMDdoLTE5Ljk3OGwyNC45MDEtMzUuNDU5YzAuMjUwMzktMC4zNTA1NCAwLjM3MTgzLTAuODM4ODMgMC4zNzE4My0xLjQ2NDggMC0wLjkyNjQ1LTAuMTg3OC0xLjU3MzctMC41NjMzOC0xLjk0OTMtMC4zNTA1NS0wLjQwMDYyLTAuOTUxNDctMC41OTcxOC0xLjgwMjgtMC41OTcxOHptLTMxLjc1MiA1LjEwNDJoMC43MDk4NWw2Ljk4NTkgMjAuMzE1aC0xNC43MTZ6bS0zNy43MjMtNDkuMTgzYy00LjczNDIgMC04LjYzMTMgMy44OTctOC42MzEzIDguNjMxM3YxMTUuNDdjMCA0LjczNDIgMy44OTcgOC42MzEzIDguNjMxMyA4LjYzMTNoMTE1LjI2YzQuNzM0MiAwIDguNjQyMS0zLjg5NyA4LjY0MjEtOC42MzEzdi0xMTUuNDdjMC00LjczNDItMy45MDgyLTguNjMxMy04LjY0MjEtOC42MzEzem0wIDUuNzI0aDExNS4yNmMxLjY1OCAwIDIuOTA3IDEuMjQ5MSAyLjkwNyAyLjkwNzF2MTE1LjQ3YzAgMS42NTgtMS4yNDkxIDIuOTA3LTIuOTA3IDIuOTA3aC0xMTUuMjZjLTEuNjU4IDAtMi44OTU4LTEuMjQ5MS0yLjg5NTgtMi45MDd2LTExNS40N2MwLTEuNjU4IDEuMjM3OC0yLjkwNzEgMi44OTU4LTIuOTA3MXoiIGZpbGw9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIuNzIxMTQiLz48L3N2Zz4=');}.icon-star-half-fi{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzkuMTgsOTcuMjZBMTYuMzgsMTYuMzgsMCwwLDAsMjI0LjkyLDg2bC01OS00Ljc2TDE0My4xNCwyNi4xNWExNi4zNiwxNi4zNiwwLDAsMC0zMC4yNywwTDkwLjExLDgxLjIzLDMxLjA4LDg2YTE2LjQ2LDE2LjQ2LDAsMCwwLTkuMzcsMjguODZsNDUsMzguODNMNTMsMjExLjc1YTE2LjQsMTYuNCwwLDAsMCwyNC41LDE3LjgyTDEyOCwxOTguNDlsNTAuNTMsMzEuMDhBMTYuNCwxNi40LDAsMCwwLDIwMywyMTEuNzVsLTEzLjc2LTU4LjA3LDQ1LTM4LjgzQTE2LjQzLDE2LjQzLDAsMCwwLDIzOS4xOCw5Ny4yNlptLTE1LjM0LDUuNDctNDguNyw0MmE4LDgsMCwwLDAtMi41Niw3LjkxbDE0Ljg4LDYyLjhhLjM3LjM3LDAsMCwxLS4xNy40OGMtLjE4LjE0LS4yMy4xMS0uMzgsMGwtNTQuNzItMzMuNjVBOCw4LDAsMCwwLDEyOCwxODEuMVYzMmMuMjQsMCwuMjcuMDguMzUuMjZMMTUzLDkxLjg2YTgsOCwwLDAsMCw2Ljc1LDQuOTJsNjMuOTEsNS4xNmMuMTYsMCwuMjUsMCwuMzQuMjlTMjI0LDEwMi42MywyMjMuODQsMTAyLjczWiIvPjwvc3ZnPg==');}.icon-star-fi{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzQuMjksMTE0Ljg1bC00NSwzOC44M0wyMDMsMjExLjc1YTE2LjQsMTYuNCwwLDAsMS0yNC41LDE3LjgyTDEyOCwxOTguNDksNzcuNDcsMjI5LjU3QTE2LjQsMTYuNCwwLDAsMSw1MywyMTEuNzVsMTMuNzYtNTguMDctNDUtMzguODNBMTYuNDYsMTYuNDYsMCwwLDEsMzEuMDgsODZsNTktNC43NiwyMi43Ni01NS4wOGExNi4zNiwxNi4zNiwwLDAsMSwzMC4yNywwbDIyLjc1LDU1LjA4LDU5LDQuNzZhMTYuNDYsMTYuNDYsMCwwLDEsOS4zNywyOC44NloiLz48L3N2Zz4=');} .icon-google-logo{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjQsMTI4YTk2LDk2LDAsMSwxLTIxLjk1LTYxLjA5LDgsOCwwLDEsMS0xMi4zMywxMC4xOEE4MCw4MCwwLDEsMCwyMDcuNiwxMzZIMTI4YTgsOCwwLDAsMSwwLTE2aDg4QTgsOCwwLDAsMSwyMjQsMTI4WiIvPjwvc3ZnPg==');}.icon-apple-logo{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjMuMywxNjkuNTlhOC4wNyw4LjA3LDAsMCwwLTIuOC0zLjRDMjAzLjUzLDE1NC41MywyMDAsMTM0LjY0LDIwMCwxMjBjMC0xNy42NywxMy40Ny0zMy4wNiwyMS41LTQwLjY3YTgsOCwwLDAsMCwwLTExLjYyQzIwOC44Miw1NS43NCwxODcuODIsNDgsMTY4LDQ4YTcyLjIsNzIuMiwwLDAsMC00MCwxMi4xMyw3MS41Niw3MS41NiwwLDAsMC05MC43MSw5LjA5QTc0LjYzLDc0LjYzLDAsMCwwLDE2LDEyMy40YTEyNy4wNiwxMjcuMDYsMCwwLDAsNDAuMTQsODkuNzNBMzkuOCwzOS44LDAsMCwwLDgzLjU5LDIyNGg4Ny42OGEzOS44NCwzOS44NCwwLDAsMCwyOS4xMi0xMi41NywxMjUsMTI1LDAsMCwwLDE3LjgyLTI0LjZDMjI1LjIzLDE3NCwyMjQuMzMsMTcyLDIyMy4zLDE2OS41OVptLTM0LjYzLDMwLjk0YTIzLjc2LDIzLjc2LDAsMCwxLTE3LjQsNy40N0g4My41OWEyMy44MiwyMy44MiwwLDAsMS0xNi40NC02LjUxQTExMS4xNCwxMTEuMTQsMCwwLDEsMzIsMTIzLDU4LjUsNTguNSwwLDAsMSw0OC42NSw4MC40Nyw1NC44MSw1NC44MSwwLDAsMSw4OCw2NGguNzhBNTUuNDUsNTUuNDUsMCwwLDEsMTIzLDc2LjI4YTgsOCwwLDAsMCwxMCwwQTU1LjQ0LDU1LjQ0LDAsMCwxLDE2OCw2NGE3MC42NCw3MC42NCwwLDAsMSwzNiwxMC4zNWMtMTMsMTQuNTItMjAsMzAuNDctMjAsNDUuNjUsMCwyMy43Nyw3LjY0LDQyLjczLDIyLjE4LDU1LjNBMTA1LjgyLDEwNS44MiwwLDAsMSwxODguNjcsMjAwLjUzWk0xMjguMjMsMzBBNDAsNDAsMCwwLDEsMTY3LDBoMWE4LDgsMCwwLDEsMCwxNmgtMWEyNCwyNCwwLDAsMC0yMy4yNCwxOCw4LDgsMCwxLDEtMTUuNS00WiIvPjwvc3ZnPg==');}.icon-check-circle{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNzMuNjYsOTguMzRhOCw4LDAsMCwxLDAsMTEuMzJsLTU2LDU2YTgsOCwwLDAsMS0xMS4zMiwwbC0yNC0yNGE4LDgsMCwwLDEsMTEuMzItMTEuMzJMMTEyLDE0OC42OWw1MC4zNC01MC4zNUE4LDgsMCwwLDEsMTczLjY2LDk4LjM0Wk0yMzIsMTI4QTEwNCwxMDQsMCwxLDEsMTI4LDI0LDEwNC4xMSwxMDQuMTEsMCwwLDEsMjMyLDEyOFptLTE2LDBhODgsODgsMCwxLDAtODgsODhBODguMSw4OC4xLDAsMCwwLDIxNiwxMjhaIi8+PC9zdmc+');}details.all-filters summary::after,.icon-faders-horizontal{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNzYsODBhOCw4LDAsMCwxLDgtOGgzMmE4LDgsMCwwLDEsMCwxNkgxODRBOCw4LDAsMCwxLDE3Niw4MFpNNDAsODhIMTQ0djE2YTgsOCwwLDAsMCwxNiwwVjU2YTgsOCwwLDAsMC0xNiwwVjcySDQwYTgsOCwwLDAsMCwwLDE2Wm0xNzYsODBIMTIwYTgsOCwwLDAsMCwwLDE2aDk2YTgsOCwwLDAsMCwwLTE2Wk04OCwxNDRhOCw4LDAsMCwwLTgsOHYxNkg0MGE4LDgsMCwwLDAsMCwxNkg4MHYxNmE4LDgsMCwwLDAsMTYsMFYxNTJBOCw4LDAsMCwwLDg4LDE0NFoiLz48L3N2Zz4=');}.icon-cloud-slash{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik01My45MiwzNC42MkE4LDgsMCwxLDAsNDIuMDgsNDUuMzhMODEuMzIsODguNTVsLS4wNi4xMkE2NSw2NSwwLDAsMCw3Miw4OGE2NCw2NCwwLDAsMCwwLDEyOGg4OGE4Ny4zNCw4Ny4zNCwwLDAsMCwzMS44LTUuOTNsMTAuMjgsMTEuMzFhOCw4LDAsMSwwLDExLjg0LTEwLjc2Wk0xNjAsMjAwSDcyYTQ4LDQ4LDAsMCwxLDAtOTZjMS4xLDAsMi4yLDAsMy4zLjEyQTg4LjQsODguNCwwLDAsMCw3MiwxMjhhOCw4LDAsMCwwLDE2LDAsNzIuMjUsNzIuMjUsMCwwLDEsNS4wNi0yNi41NGw4Nyw5NS43QTcxLjY2LDcxLjY2LDAsMCwxLDE2MCwyMDBabTg4LTcyYTg3Ljg5LDg3Ljg5LDAsMCwxLTIyLjM1LDU4LjYxQTgsOCwwLDAsMSwyMTMuNzEsMTc2LDcyLDcyLDAsMCwwLDExNy4zNyw3MGE4LDgsMCwwLDEtOS40OC0xMi44OUE4OCw4OCwwLDAsMSwyNDgsMTI4WiIvPjwvc3ZnPg==');}.icon-exclamation-mark{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNDQsMjAwYTE2LDE2LDAsMSwxLTE2LTE2QTE2LDE2LDAsMCwxLDE0NCwyMDBabS0xNi00MGE4LDgsMCwwLDAsOC04VjQ4YTgsOCwwLDAsMC0xNiwwVjE1MkE4LDgsMCwwLDAsMTI4LDE2MFoiLz48L3N2Zz4=');}.icon-cloud-arrow-down{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yNDgsMTI4YTg3LjM0LDg3LjM0LDAsMCwxLTE3LjYsNTIuODEsOCw4LDAsMSwxLTEyLjgtOS42MkE3MS4zNCw3MS4zNCwwLDAsMCwyMzIsMTI4YTcyLDcyLDAsMCwwLTE0NCwwLDgsOCwwLDAsMS0xNiwwLDg4LDg4LDAsMCwxLDMuMjktMjMuODhDNzQuMiwxMDQsNzMuMSwxMDQsNzIsMTA0YTQ4LDQ4LDAsMCwwLDAsOTZIOTZhOCw4LDAsMCwxLDAsMTZINzJBNjQsNjQsMCwxLDEsODEuMjksODguNjgsODgsODgsMCwwLDEsMjQ4LDEyOFptLTY5LjY2LDQyLjM0TDE2MCwxODguNjlWMTI4YTgsOCwwLDAsMC0xNiwwdjYwLjY5bC0xOC4zNC0xOC4zNWE4LDgsMCwwLDAtMTEuMzIsMTEuMzJsMzIsMzJhOCw4LDAsMCwwLDExLjMyLDBsMzItMzJhOCw4LDAsMCwwLTExLjMyLTExLjMyWiIvPjwvc3ZnPg==');}details summary::after,.icon-caret-down{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTMuNjYsMTAxLjY2bC04MCw4MGE4LDgsMCwwLDEtMTEuMzIsMGwtODAtODBBOCw4LDAsMCwxLDUzLjY2LDkwLjM0TDEyOCwxNjQuNjlsNzQuMzQtNzQuMzVhOCw4LDAsMCwxLDExLjMyLDExLjMyWiIvPjwvc3ZnPg==');}.icon-cloud-arrow-up{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNzguMzQsMTY1LjY2LDE2MCwxNDcuMzFWMjA4YTgsOCwwLDAsMS0xNiwwVjE0Ny4zMWwtMTguMzQsMTguMzVhOCw4LDAsMCwxLTExLjMyLTExLjMybDMyLTMyYTgsOCwwLDAsMSwxMS4zMiwwbDMyLDMyYTgsOCwwLDAsMS0xMS4zMiwxMS4zMlpNMTYwLDQwQTg4LjA4LDg4LjA4LDAsMCwwLDgxLjI5LDg4LjY4LDY0LDY0LDAsMSwwLDcyLDIxNmg0MGE4LDgsMCwwLDAsMC0xNkg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTJBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDEwMC44LDY2LDgsOCwwLDAsMCwzLjIsMTUuMzQsNy45LDcuOSwwLDAsMCwzLjItLjY4QTg4LDg4LDAsMCwwLDE2MCw0MFoiLz48L3N2Zz4=');}.icon-cloud-check{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDBBODguMDksODguMDksMCwwLDAsODEuMjksODguNjcsNjQsNjQsMCwxLDAsNzIsMjE2aDg4YTg4LDg4LDAsMCwwLDAtMTc2Wm0wLDE2MEg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTFBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDcyLDcyWm0zNy42Ni05My42NmE4LDgsMCwwLDEsMCwxMS4zMmwtNDgsNDhhOCw4LDAsMCwxLTExLjMyLDBsLTI0LTI0YTgsOCwwLDAsMSwxMS4zMi0xMS4zMkwxNDQsMTQ4LjY5bDQyLjM0LTQyLjM1QTgsOCwwLDAsMSwxOTcuNjYsMTA2LjM0WiIvPjwvc3ZnPg==');}.icon-cloud-warning{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDBBODguMDksODguMDksMCwwLDAsODEuMjksODguNjcsNjQsNjQsMCwxLDAsNzIsMjE2aDg4YTg4LDg4LDAsMCwwLDAtMTc2Wm0wLDE2MEg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTFBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDcyLDcyWm0tOC03MlY4OGE4LDgsMCwwLDEsMTYsMHY0MGE4LDgsMCwwLDEtMTYsMFptMjAsMzZhMTIsMTIsMCwxLDEtMTItMTJBMTIsMTIsMCwwLDEsMTcyLDE2NFoiLz48L3N2Zz4=');}.icon-syncing{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0iY3VycmVudENvbG9yIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PHBhdGggaWQ9InJlZnJlc2giIGQ9Ik0xNjAuMDQ3IDEyMi44NzVhMzAuNzg0IDMwLjc4NCAwIDAgMC0yMS43NSA4Ljc5N2MtMi44NDIgMy4wMDMtLjQ2NyA0Ljk3MSAxLjMxMiAzLjE1NiAxMS4wNDMtMTAuNzg2IDI4LjcxLTEwLjY4IDM5LjYyNS4yMzRsNy4yMDMgNy4yMDRoLTEyLjg3NWMtMy4zNDcuMDA4LTMuMTY1IDMuODc1IDAgMy44NzVoMTYuMTFjMi4wNjIgMCAyLjU0LTEuNDE4IDIuNTYyLTQuOTdsLjA5NC0xNC45MjFjLjAyLTMuMjktMy40MzctMy4xNjUtMy40MzcgMHYxMi44NmwtNy4yMDMtNy4xODhhMzAuNzY4IDMwLjc2OCAwIDAgMC0yMS42NDEtOS4wNDd6bS0yOS41OTQgMzkuNzk3Yy0yLjA2MiAwLTIuNTI0IDEuNDAyLTIuNTQ3IDQuOTUzbC0uMDk0IDE0LjkyMmMtLjAyIDMuMjkgMy40MjIgMy4xNjQgMy40MjIgMHYtMTIuODZsNy4yMDMgNy4yMDRjMTEuOTU2IDExLjk1NSAzMS4zMTIgMTIuMDY0IDQzLjQwNy4yNSAyLjg0Mi0zLjAwMy40NTEtNC45ODgtMS4zMjgtMy4xNzItMTEuMDQzIDEwLjc4Ni0yOC43MSAxMC42OC0zOS42MjUtLjIzNWwtNy4xODgtNy4yMDNoMTIuODZjMy4zNDctLjAwOCAzLjE2NS0zLjg2IDAtMy44NmgtMTYuMTF6Ii8+PHBhdGggZD0iTTE2MCA0NGE4NC4xMSA4NC4xMSAwIDAgMC03Ni40MSA0OS4xMkE2MC43MSA2MC43MSAwIDAgMCA3MiA5MmE2MCA2MCAwIDAgMCAwIDEyMGg4OGE4NCA4NCAwIDAgMCAwLTE2OFptMCAxNjBINzJhNTIgNTIgMCAxIDEgOC41NS0xMDMuM0E4My42NiA4My42NiAwIDAgMCA3NiAxMjhhNCA0IDAgMCAwIDggMCA3NiA3NiAwIDEgMSA3NiA3NloiLz48L3N2Zz4=');}.icon-cloud-x{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDBBODguMDksODguMDksMCwwLDAsODEuMjksODguNjcsNjQsNjQsMCwxLDAsNzIsMjE2aDg4YTg4LDg4LDAsMCwwLDAtMTc2Wm0wLDE2MEg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTFBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDcyLDcyWm0yOS42Ni04Mi4zNEwxNzEuMzEsMTM2bDE4LjM1LDE4LjM0YTgsOCwwLDAsMS0xMS4zMiwxMS4zMkwxNjAsMTQ3LjMxbC0xOC4zNCwxOC4zNWE4LDgsMCwwLDEtMTEuMzItMTEuMzJMMTQ4LjY5LDEzNmwtMTguMzUtMTguMzRhOCw4LDAsMCwxLDExLjMyLTExLjMyTDE2MCwxMjQuNjlsMTguMzQtMTguMzVhOCw4LDAsMCwxLDExLjMyLDExLjMyWiIvPjwvc3ZnPg==');}.icon-arrows-clockwise{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjQsNDhWOTZhOCw4LDAsMCwxLTgsOEgxNjhhOCw4LDAsMCwxLDAtMTZoMjguNjlMMTgyLjA2LDczLjM3YTc5LjU2LDc5LjU2LDAsMCwwLTU2LjEzLTIzLjQzaC0uNDVBNzkuNTIsNzkuNTIsMCwwLDAsNjkuNTksNzIuNzEsOCw4LDAsMCwxLDU4LjQxLDYxLjI3YTk2LDk2LDAsMCwxLDEzNSwuNzlMMjA4LDc2LjY5VjQ4YTgsOCwwLDAsMSwxNiwwWk0xODYuNDEsMTgzLjI5YTgwLDgwLDAsMCwxLTExMi40Ny0uNjZMNTkuMzEsMTY4SDg4YTgsOCwwLDAsMCwwLTE2SDQwYTgsOCwwLDAsMC04LDh2NDhhOCw4LDAsMCwwLDE2LDBWMTc5LjMxbDE0LjYzLDE0LjYzQTk1LjQzLDk1LjQzLDAsMCwwLDEzMCwyMjIuMDZoLjUzYTk1LjM2LDk1LjM2LDAsMCwwLDY3LjA3LTI3LjMzLDgsOCwwLDAsMC0xMS4xOC0xMS40NFoiLz48L3N2Zz4=');}.icon-share-fat{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzcuNjYsMTA2LjM1bC04MC04MEE4LDgsMCwwLDAsMTQ0LDMyVjcyLjM1Yy0yNS45NCwyLjIyLTU0LjU5LDE0LjkyLTc4LjE2LDM0LjkxLTI4LjM4LDI0LjA4LTQ2LjA1LDU1LjExLTQ5Ljc2LDg3LjM3YTEyLDEyLDAsMCwwLDIwLjY4LDkuNThoMGMxMS0xMS43MSw1MC4xNC00OC43NCwxMDcuMjQtNTJWMTkyYTgsOCwwLDAsMCwxMy42Niw1LjY1bDgwLTgwQTgsOCwwLDAsMCwyMzcuNjYsMTA2LjM1Wk0xNjAsMTcyLjY5VjE0NGE4LDgsMCwwLDAtOC04Yy0yOC4wOCwwLTU1LjQzLDcuMzMtODEuMjksMjEuOGExOTYuMTcsMTk2LjE3LDAsMCwwLTM2LjU3LDI2LjUyYzUuOC0yMy44NCwyMC40Mi00Ni41MSw0Mi4wNS02NC44NkM5OS40MSw5OS43NywxMjcuNzUsODgsMTUyLDg4YTgsOCwwLDAsMCw4LThWNTEuMzJMMjIwLjY5LDExMloiLz48L3N2Zz4=');}.icon-trash{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTYsNDhIMTc2VjQwYTI0LDI0LDAsMCwwLTI0LTI0SDEwNEEyNCwyNCwwLDAsMCw4MCw0MHY4SDQwYTgsOCwwLDAsMCwwLDE2aDhWMjA4YTE2LDE2LDAsMCwwLDE2LDE2SDE5MmExNiwxNiwwLDAsMCwxNi0xNlY2NGg4YTgsOCwwLDAsMCwwLTE2Wk05Niw0MGE4LDgsMCwwLDEsOC04aDQ4YTgsOCwwLDAsMSw4LDh2OEg5NlptOTYsMTY4SDY0VjY0SDE5MlpNMTEyLDEwNHY2NGE4LDgsMCwwLDEtMTYsMFYxMDRhOCw4LDAsMCwxLDE2LDBabTQ4LDB2NjRhOCw4LDAsMCwxLTE2LDBWMTA0YTgsOCwwLDAsMSwxNiwwWiIvPjwvc3ZnPg==');}.icon-star{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzkuMTgsOTcuMjZBMTYuMzgsMTYuMzgsMCwwLDAsMjI0LjkyLDg2bC01OS00Ljc2TDE0My4xNCwyNi4xNWExNi4zNiwxNi4zNiwwLDAsMC0zMC4yNywwTDkwLjExLDgxLjIzLDMxLjA4LDg2YTE2LjQ2LDE2LjQ2LDAsMCwwLTkuMzcsMjguODZsNDUsMzguODNMNTMsMjExLjc1YTE2LjM4LDE2LjM4LDAsMCwwLDI0LjUsMTcuODJMMTI4LDE5OC40OWw1MC41MywzMS4wOEExNi40LDE2LjQsMCwwLDAsMjAzLDIxMS43NWwtMTMuNzYtNTguMDcsNDUtMzguODNBMTYuNDMsMTYuNDMsMCwwLDAsMjM5LjE4LDk3LjI2Wm0tMTUuMzQsNS40Ny00OC43LDQyYTgsOCwwLDAsMC0yLjU2LDcuOTFsMTQuODgsNjIuOGEuMzcuMzcsMCwwLDEtLjE3LjQ4Yy0uMTguMTQtLjIzLjExLS4zOCwwbC01NC43Mi0zMy42NWE4LDgsMCwwLDAtOC4zOCwwTDY5LjA5LDIxNS45NGMtLjE1LjA5LS4xOS4xMi0uMzgsMGEuMzcuMzcsMCwwLDEtLjE3LS40OGwxNC44OC02Mi44YTgsOCwwLDAsMC0yLjU2LTcuOTFsLTQ4LjctNDJjLS4xMi0uMS0uMjMtLjE5LS4xMy0uNXMuMTgtLjI3LjMzLS4yOWw2My45Mi01LjE2QTgsOCwwLDAsMCwxMDMsOTEuODZsMjQuNjItNTkuNjFjLjA4LS4xNy4xMS0uMjUuMzUtLjI1cy4yNy4wOC4zNS4yNUwxNTMsOTEuODZhOCw4LDAsMCwwLDYuNzUsNC45Mmw2My45Miw1LjE2Yy4xNSwwLC4yNCwwLC4zMy4yOVMyMjQsMTAyLjYzLDIyMy44NCwxMDIuNzNaIi8+PC9zdmc+');}.icon-alphabetical{--icon:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTgzLjc4IDE4NC4wNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNTkuNTg2IDY5Ljc0MmMtMC44NTEzIDAtMS40NjEgMC4xOTY1Ni0xLjgzNjYgMC41OTcxOC0wLjM1MDU0IDAuMzc1NTgtMC41Mjk1OCAxLjAyMjktMC41Mjk1OCAxLjk0OTNzMC4xNzkwMyAxLjU5MzcgMC41Mjk1OCAxLjk5NDRjMC4zNzU1OCAwLjM3NTU4IDAuOTg1MjkgMC41NjMzOCAxLjgzNjYgMC41NjMzOGg3LjAxOTdsLTEyLjQyOCAzNC4zNjZoLTIuMTA3Yy0wLjg1MTMgMC0xLjQ2MSAwLjE5NjU2LTEuODM2NiAwLjU5NzE4LTAuMzUwNTQgMC4zNzU1OC0wLjUyOTU3IDEuMDM0MS0wLjUyOTU3IDEuOTYwNiAwIDAuOTI2NDQgMC4xNzkwMyAxLjU4MjUgMC41Mjk1NyAxLjk4MyAwLjM3NTU4IDAuMzc1NTkgMC45ODUyOSAwLjU2MzM4IDEuODM2NiAwLjU2MzM4aDEyLjU1MmMwLjg1MTMgMCAxLjQ1MjItMC4xODc3OSAxLjgwMjgtMC41NjMzOCAwLjM3NTU4LTAuNDAwNjIgMC41NjMzNy0xLjA1NjYgMC41NjMzNy0xLjk4MyAwLTAuOTI2NDUtMC4xODc3OS0xLjU4NS0wLjU2MzM3LTEuOTYwNi0wLjM1MDU0LTAuNDAwNjItMC45NTE0Ny0wLjU5NzE4LTEuODAyOC0wLjU5NzE4aC00LjU1MjFsMy4xMjExLTguOTM0OWgxOC4yMmwzLjA3NiA4LjkzNDloLTUuMDcwNGMtMC44NTEzIDAtMS40NjEgMC4xOTY1Ni0xLjgzNjYgMC41OTcxOC0wLjM1MDU0IDAuMzc1NTgtMC41Mjk1OCAxLjAzNDEtMC41Mjk1OCAxLjk2MDYgMCAwLjkyNjQ0IDAuMTc5MDMgMS41ODI1IDAuNTI5NTggMS45ODMgMC4zNzU1OCAwLjM3NTU5IDAuOTg1MjkgMC41NjMzOCAxLjgzNjYgMC41NjMzOGgxMy4yOTZjMC44NTEzIDAgMS40NTIyLTAuMTg3NzkgMS44MDI4LTAuNTYzMzggMC4zNzU1OC0wLjQwMDYyIDAuNTYzMzctMS4wNTY2IDAuNTYzMzctMS45ODMgMC0wLjkyNjQ1LTAuMTg3NzktMS41ODUtMC41NjMzNy0xLjk2MDYtMC4zNTA1NC0wLjQwMDYyLTAuOTUxNDctMC41OTcxOC0xLjgwMjgtMC41OTcxOGgtMi4yODczbC0xMy4yNjItMzcuMDM2Yy0wLjMwMDQ3LTAuODUxMy0wLjc1OTk0LTEuNDYxLTEuMzg1OS0xLjgzNjYtMC42MDA5My0wLjQwMDYyLTEuNDA5Ny0wLjU5NzE4LTIuNDExMy0wLjU5NzE4em00NC4xNDYgMGMtMC44NTEzIDAtMS40NzIzIDAuMTk2NTYtMS44NDc4IDAuNTk3MTgtMC4zNTA1NSAwLjM3NTU4LTAuNTE4MyAxLjAyMjktMC41MTgzIDEuOTQ5M3YxMS45MWMwIDAuODc2MzMgMC4yMDUzMiAxLjUwNjEgMC42MzA5OCAxLjg4MTcgMC40MjU2NiAwLjM3NTU4IDEuMTU5MyAwLjU2MzM3IDIuMTg1OSAwLjU2MzM3czEuNzQ5LTAuMTg3NzkgMi4xNzQ3LTAuNTYzMzdjMC40MjU2OS0wLjM3NTU4IDAuNjQyMjYtMS4wMDUzIDAuNjQyMjYtMS44ODE3di05LjM1MTdoMTguODUxbC0yNC43NTQgMzUuMzAxYy0wLjM1MDU0IDAuNTI1ODItMC41MTgzMSAxLjA3MTctMC41MTgzMSAxLjYyMjYgMCAwLjkyNjQ1IDAuMTY3NzcgMS41ODI1IDAuNTE4MzEgMS45ODMxIDAuMzc1NTggMC4zNzU1OCAwLjk5NjU0IDAuNTYzMzggMS44NDc4IDAuNTYzMzhoMjguNzY2YzAuODUxMyAwIDEuNDUyMi0wLjE4NzggMS44MDI4LTAuNTYzMzggMC4zNzU1OC0wLjQwMDYyIDAuNTYzMzgtMS4wNTY2IDAuNTYzMzgtMS45ODMxdi0xMi42NjVjMC0wLjg3NjMzLTAuMjE2NTgtMS40OTQ4LTAuNjQyMjUtMS44NzA0LTAuNDI1NjYtMC4zNzU1OC0xLjE0OC0wLjU2MzM4LTIuMTc0Ny0wLjU2MzM4LTEuMDI2NiAwLTEuNzQ5IDAuMTg3NzktMi4xNzQ3IDAuNTYzMzgtMC40MjU2NiAwLjM3NTU4LTAuNjQyMjQgMC45OTQwMi0wLjY0MjI0IDEuODcwNHYxMC4xMDdoLTE5Ljk3OGwyNC45MDEtMzUuNDU5YzAuMjUwMzktMC4zNTA1NCAwLjM3MTgzLTAuODM4ODMgMC4zNzE4My0xLjQ2NDggMC0wLjkyNjQ1LTAuMTg3OC0xLjU3MzctMC41NjMzOC0xLjk0OTMtMC4zNTA1NS0wLjQwMDYyLTAuOTUxNDctMC41OTcxOC0xLjgwMjgtMC41OTcxOHptLTMxLjc1MiA1LjEwNDJoMC43MDk4NWw2Ljk4NTkgMjAuMzE1aC0xNC43MTZ6bS0zNy43MjMtNDkuMTgzYy00LjczNDIgMC04LjYzMTMgMy44OTctOC42MzEzIDguNjMxM3YxMTUuNDdjMCA0LjczNDIgMy44OTcgOC42MzEzIDguNjMxMyA4LjYzMTNoMTE1LjI2YzQuNzM0MiAwIDguNjQyMS0zLjg5NyA4LjY0MjEtOC42MzEzdi0xMTUuNDdjMC00LjczNDItMy45MDgyLTguNjMxMy04LjY0MjEtOC42MzEzem0wIDUuNzI0aDExNS4yNmMxLjY1OCAwIDIuOTA3IDEuMjQ5MSAyLjkwNyAyLjkwNzF2MTE1LjQ3YzAgMS42NTgtMS4yNDkxIDIuOTA3LTIuOTA3IDIuOTA3aC0xMTUuMjZjLTEuNjU4IDAtMi44OTU4LTEuMjQ5MS0yLjg5NTgtMi45MDd2LTExNS40N2MwLTEuNjU4IDEuMjM3OC0yLjkwNzEgMi44OTU4LTIuOTA3MXoiIGZpbGw9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIuNzIxMTQiLz48L3N2Zz4=');}.icon-list{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjQsMTI4YTgsOCwwLDAsMS04LDhINDBhOCw4LDAsMCwxLDAtMTZIMjE2QTgsOCwwLDAsMSwyMjQsMTI4Wk00MCw3MkgyMTZhOCw4LDAsMCwwLDAtMTZINDBhOCw4LDAsMCwwLDAsMTZaTTIxNiwxODRINDBhOCw4LDAsMCwwLDAsMTZIMjE2YTgsOCwwLDAsMCwwLTE2WiIvPjwvc3ZnPg==');}.icon-x{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMDUuNjYsMTk0LjM0YTgsOCwwLDAsMS0xMS4zMiwxMS4zMkwxMjgsMTM5LjMxLDYxLjY2LDIwNS42NmE4LDgsMCwwLDEtMTEuMzItMTEuMzJMMTE2LjY5LDEyOCw1MC4zNCw2MS42NkE4LDgsMCwwLDEsNjEuNjYsNTAuMzRMMTI4LDExNi42OWw2Ni4zNC02Ni4zNWE4LDgsMCwwLDEsMTEuMzIsMTEuMzJMMTM5LjMxLDEyOFoiLz48L3N2Zz4=');}.icon-arrow-circle-right{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xMjgsMjRBMTA0LDEwNCwwLDEsMCwyMzIsMTI4LDEwNC4xMSwxMDQuMTEsMCwwLDAsMTI4LDI0Wm0wLDE5MmE4OCw4OCwwLDEsMSw4OC04OEE4OC4xLDg4LjEsMCwwLDEsMTI4LDIxNlptNDUuNjYtOTMuNjZhOCw4LDAsMCwxLDAsMTEuMzJsLTMyLDMyYTgsOCwwLDAsMS0xMS4zMi0xMS4zMkwxNDguNjksMTM2SDg4YTgsOCwwLDAsMSwwLTE2aDYwLjY5bC0xOC4zNS0xOC4zNGE4LDgsMCwwLDEsMTEuMzItMTEuMzJaIi8+PC9zdmc+');}.icon-house{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTkuMzEsMTA4LjY4bC04MC04MGExNiwxNiwwLDAsMC0yMi42MiwwbC04MCw4MEExNS44NywxNS44NywwLDAsMCwzMiwxMjB2OTZhOCw4LDAsMCwwLDgsOGg2NGE4LDgsMCwwLDAsOC04VjE2MGgzMnY1NmE4LDgsMCwwLDAsOCw4aDY0YTgsOCwwLDAsMCw4LThWMTIwQTE1Ljg3LDE1Ljg3LDAsMCwwLDIxOS4zMSwxMDguNjhaTTIwOCwyMDhIMTYwVjE1MmE4LDgsMCwwLDAtOC04SDEwNGE4LDgsMCwwLDAtOCw4djU2SDQ4VjEyMGw4MC04MCw4MCw4MFoiLz48L3N2Zz4=');}.icon-star-half-fi{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzkuMTgsOTcuMjZBMTYuMzgsMTYuMzgsMCwwLDAsMjI0LjkyLDg2bC01OS00Ljc2TDE0My4xNCwyNi4xNWExNi4zNiwxNi4zNiwwLDAsMC0zMC4yNywwTDkwLjExLDgxLjIzLDMxLjA4LDg2YTE2LjQ2LDE2LjQ2LDAsMCwwLTkuMzcsMjguODZsNDUsMzguODNMNTMsMjExLjc1YTE2LjQsMTYuNCwwLDAsMCwyNC41LDE3LjgyTDEyOCwxOTguNDlsNTAuNTMsMzEuMDhBMTYuNCwxNi40LDAsMCwwLDIwMywyMTEuNzVsLTEzLjc2LTU4LjA3LDQ1LTM4LjgzQTE2LjQzLDE2LjQzLDAsMCwwLDIzOS4xOCw5Ny4yNlptLTE1LjM0LDUuNDctNDguNyw0MmE4LDgsMCwwLDAtMi41Niw3LjkxbDE0Ljg4LDYyLjhhLjM3LjM3LDAsMCwxLS4xNy40OGMtLjE4LjE0LS4yMy4xMS0uMzgsMGwtNTQuNzItMzMuNjVBOCw4LDAsMCwwLDEyOCwxODEuMVYzMmMuMjQsMCwuMjcuMDguMzUuMjZMMTUzLDkxLjg2YTgsOCwwLDAsMCw2Ljc1LDQuOTJsNjMuOTEsNS4xNmMuMTYsMCwuMjUsMCwuMzQuMjlTMjI0LDEwMi42MywyMjMuODQsMTAyLjczWiIvPjwvc3ZnPg==');}.icon-star-fi{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzQuMjksMTE0Ljg1bC00NSwzOC44M0wyMDMsMjExLjc1YTE2LjQsMTYuNCwwLDAsMS0yNC41LDE3LjgyTDEyOCwxOTguNDksNzcuNDcsMjI5LjU3QTE2LjQsMTYuNCwwLDAsMSw1MywyMTEuNzVsMTMuNzYtNTguMDctNDUtMzguODNBMTYuNDYsMTYuNDYsMCwwLDEsMzEuMDgsODZsNTktNC43NiwyMi43Ni01NS4wOGExNi4zNiwxNi4zNiwwLDAsMSwzMC4yNywwbDIyLjc1LDU1LjA4LDU5LDQuNzZhMTYuNDYsMTYuNDYsMCwwLDEsOS4zNywyOC44NloiLz48L3N2Zz4=');} inc/blocks/MenuBlock.php
@@ -147,11 +147,7 @@ protected function getSections():array { if (!$this->sections) { $sections = Options::get('menu_section_order'); if (!is_array($sections)) { $sections = []; } $this->sections = $sections; $this->sections = aneb_menu_sections(); } return $this->sections; @@ -215,13 +211,27 @@ $excerpt = get_the_excerpt($ID); $isVariable = !empty($fields['variations']??[]); $variations = $isVariable ? array_map('floatval', array_column($fields['variations'], 'price')) : []; $priceRange = $isVariable ? (jvbCurrency(min($variations)).' - '.jvbCurrency(max($variations))) : jvbCurrency($fields['_price']??''); $priceRange = $fields['price']; if ($isVariable) { $prices = array_map('floatval', array_column($fields['variations'], 'price')); $min = min($prices); $max = max($prices); $priceRange = $min===$max ? jvbCurrency($min) : jvbCurrency($min) .' - '.jvbCurrency($max); } // if (!$isVariable) { // jvbDump($fields, 'Fields'); // } // jvbDump(get_post_thumbnail_id($ID), 'thumbnail'); // $product = wc_get_product($ID); // jvbDump($product->get_image_id(), 'product image'); // jvbDump($product->get_gallery_image_ids(), 'Gallery images'); if ($fields['post_title'] === 'Breakfast Poutine') { jvbDump($ID); jvbDump($fields); } $out = sprintf( '<div id="%s" class="menu-item%s" data-section="%s" data-label="%s" data-id="%s"> inc/meta/Group.php
@@ -16,7 +16,7 @@ { $this->meta = $meta; $this->name = $name; $this->config = $meta->config($name) ?? []; $this->config = $meta->getFieldConfig($name) ?? []; $data = $meta->get($name); $this->data = is_array($data) ? $data : []; @@ -44,12 +44,11 @@ return $this->getPath($nested, $fieldConfig['fields'] ?? [], $parts); } public function set(string $path, mixed $value, bool $autosave = true): self public function set(string $path, mixed $value, bool $autosave = true): bool { $parts = explode('|', $path); $parts = explode('|', $path, 2); $this->setPath($this->data, $this->config['fields'] ?? [], $parts, $value); $this->sync($autosave); return $this; return $this->sync($autosave); } protected function setPath(array &$data, array $fieldsConfig, array $parts, mixed $value): void @@ -97,8 +96,8 @@ return isset($this->config['fields'][$field]); } protected function sync(bool $autosave = true): void protected function sync(bool $autosave = true): bool { $this->meta->set($this->name, $this->data, $autosave); return $this->meta->set($this->name, $this->data); } } inc/meta/Meta.php
@@ -1,593 +1,518 @@ <?php namespace JVBase\meta; use JVBase\base\Options; use JVBase\managers\Cache; use JVBase\registrar\Registrar; use WP_Post; use WP_Term; use WP_User; if (!defined('ABSPATH')) { exit; } class Meta { class Meta { /** * @var string post, term, user, or options */ protected string $type; protected string $objectType; /** * @var ?string the full slug, with BASE * @var string|null post type, user role, or taxonomy */ protected ?string $slug; protected string $contentType; protected Item $item; protected Storage $storage; protected Validator $validator; protected Sanitizer $sanitizer; protected array $fields; protected WP_Post|WP_Term|WP_User|false|null $wpObject; protected ?string $content; /** * @var int|string <int>post, term, or user ID OR {string} option key base */ protected int|string $ID; protected MetaTypeManager $typeManager; protected static array $instances = ['post' => [],'term' => [], 'user'=>[],'option'=>[]]; protected array $defaults = ['post_thumbnail']; /** * Create Meta instance for a post * @var array of Registrar's defined fields, if any */ public static function forPost(int $id): self { if (array_key_exists($id, self::$instances['post'])) { return self::$instances['post'][$id]; } $new = new self($id, 'post'); self::$instances['post'][$id] = $new; return $new; } protected static array $registrars = []; /** * Create Meta instance for a term * @var array Field values, stored in memory */ public static function forTerm(int $id): self protected array $fields; protected ?\WC_Product $product = null; protected static Validator $validator; protected static Sanitizer $sanitizer; protected static array $coreFields = [ 'post' => ['post_title', 'post_content', 'post_excerpt', 'post_status', 'post_date', 'post_modified', 'post_thumbnail', 'post_name'], 'term' => ['name', 'description', 'parent'], 'user' => ['first_name', 'last_name', 'user_email', 'display_name', 'user_login'], 'options' => [] ]; protected static array $instances = [ 'post' => [], 'term' => [], 'user' => [], 'options'=> [], ]; private function __construct(string $objectType, int|string $ID) { $this->objectType = $objectType; $this->ID = $ID; $this->content = $this->resolveBasedType(); if ($this->content && !array_key_exists($this->content, self::$registrars)) { $registrar = Registrar::getInstance($this->content); if ($registrar) { self::$registrars[$this->content] = $registrar->getFields(); } } if ($this->objectType === 'post' && function_exists('wc_get_product') && in_array($this->content, ['product', 'product_variation'], true)) { $this->product = wc_get_product($this->ID) ?: null; } $this->buildData(); if (!isset(self::$sanitizer)) { self::$sanitizer = new Sanitizer(); } if (!isset(self::$validator)) { self::$validator = new Validator(); } } protected function buildData():void { if (array_key_exists($id, self::$instances['term'])) { return self::$instances['term'][$id]; } $new = new self($id, 'term'); self::$instances['term'][$id] = $new; return $new; } /** * Create Meta instance for a user */ public static function forUser(int $id): self { if (array_key_exists($id, self::$instances['user'])) { return self::$instances['user'][$id]; } $new = new self($id, 'user'); self::$instances['user'][$id] = $new; return $new; } /** * Create Meta instance for options */ public static function forOptions(?string $baseKey = BASE): self { if (array_key_exists($baseKey, self::$instances['option'])) { return self::$instances['option'][$baseKey]; } $new = new self($baseKey, 'option'); self::$instances['option'][$baseKey] = $new; return $new; } /*************************************************************** * Constructor ***************************************************************/ public function __construct(int|string|null $id, string $type, array $fields = []) { $this->validator = new Validator(); $this->sanitizer = new Sanitizer(); $this->typeManager = new MetaTypeManager(); $this->type = $type; $this->ID = $id; $this->buildData($id, $type, $fields); } protected function buildData(int|string|null $id, string $type, array $fields):void { $this->wpObject = match($type) { 'post' => get_post($id), 'term' => get_term($id), 'user', 'integrations' => get_userdata($id), default => false }; $this->slug = match($type) { 'post' => $this->wpObject->post_type, 'term' => $this->wpObject->taxonomy, 'user' => jvbUserRole($id), default => null }; $registrar = !is_null($this->slug) ? Registrar::getInstance($this->slug) : false; $fields = $registrar ? $registrar->getFields() : $fields; if ($this->type == 'option') { $options = Options::getInstance(); $fields = $options->getFields(); } $meta = match($type) { 'post' => get_post_meta($id), 'term' => get_term_meta($id), 'user' => get_user_meta($id), //Get all Item meta $this->fields = match ($this->objectType) { 'post' => get_post_meta($this->ID), 'term' => get_term_meta($this->ID), 'user' => get_user_meta($this->ID), default => [] }; if (!$meta) { $meta = []; } $meta = array_map(fn($value) => maybe_unserialize($value[0]), $meta); $this->fields = array_combine( array_map('jvbNoBase', array_keys($this->fields)), $this->fields ); $this->fields = []; if (class_exists('WooCommerce') && $this->type === 'post' && $this->wpObject->post_type??'' === 'product') { $fields['post_title'] = [ 'type' => 'text', 'label' => 'Title', ]; $fields['post_content'] = [ 'type' => 'text', 'label' => 'Content', ]; $fields['post_excerpt'] = [ 'type' => 'text', 'label' => 'Short Description', ]; $fields['ID'] = [ 'type' => 'text', 'label' => 'ID', ]; $fields['_stock'] = [ 'type' => 'number', 'label' => 'Stock', 'default' => null ]; $fields['_stock_status'] = [ 'type' => 'select', 'label' => 'Stock Status', 'default' => 'instock', 'options' => [ 'instock' => 'In Stock', 'outofstock' => 'Out of Stock', 'onbackorder' => 'On Backorder' ] ]; $fields['_price'] = [ 'type' => 'text', 'subtype' => 'number', 'label' => 'Price' ]; $product = wc_get_product($id); $fields['sku'] = [ 'type' => 'text', 'label' => 'SKU', ]; $out = []; if ($product && $product->is_type('variable')) { $var = $product->get_children(); $var = array_filter(array_map(function($v) { return wc_get_product($v); }, $var)); $title= $product->get_title(); foreach ($var as $variation) { $out[] = [ 'id' => $variation->get_id(), 'sku' => $variation->get_sku(), 'title' => str_replace($title.' - ', '', $variation->get_name()), 'price' => $variation->get_price(), 'description'=> $variation->get_short_description(), 'sale' => $variation->get_sale_price(), 'img' => $variation->get_image_id(), 'stock' => $variation->get_stock_quantity(), 'stock_status' => $variation->get_stock_status() ]; } } if (!empty($out)) { $this->fields['variations'] = new Field('variations', $out, [ 'type' => 'repeater', 'label' => 'Variations', 'fields' => [ 'id' => [ 'type' => 'text', 'subtype' => 'number', 'label' => 'Variation ID', ], 'sku' => [ 'type' => 'text', 'label' => 'Stock Keeping Unit (SKU)' ], 'title' => [ 'type' => 'text', 'label' => 'Variation Title' ], 'price' => [ 'type' => 'text', 'subtype' => 'number', 'label' => 'Variation Price' ], 'sale' => [ 'type' => 'text', 'subtype' => 'number', 'label' => 'Sale Price', ], 'image' => [ 'type' => 'text', 'subtype' => 'number', 'label' => 'Variation Image ID' ], 'stock' => [ 'type' => 'text', 'label' => 'Stock Quantity' ], 'stock_status' => [ 'type' => 'text', 'label' => 'Stock Status' ] ] ]); } } foreach ($fields as $fieldName => $config) { $fieldName = jvbNoBase($fieldName); $value = ''; if ($this->type === 'option') { $value = get_option(BASE . $fieldName, $config['default'] ?? ''); } else if ($this->wpObject && $this->wpObject->post_type === 'product') { if (property_exists($this->wpObject, $fieldName)) { $value = $this->wpObject->$fieldName; } else { $product = wc_get_product($this->wpObject->ID); $value = match ($fieldName) { 'sku' => $product->get_sku(), '_price' => $product->get_price(), 'description'=> $product->get_short_description(), 'sale' => $product->get_sale_price(), 'img' => $product->get_image_id(), '_stock' => $product->get_stock_quantity(), '_stock_status' => $product->get_stock_status(), default => '', }; } } else if ( ($this->wpObject && property_exists($this->wpObject, $fieldName)) || ($type === 'user' && $this->wpObject && property_exists($this->wpObject->data, $fieldName))) { $config['wp'] = true; $value = $this->wpObject->$fieldName; } else if (in_array($fieldName, $this->defaults)) { $config['wp'] = true; switch ($fieldName) { case 'post_thumbnail': $value = get_post_thumbnail_id($this->ID); break; } } else { $value = array_key_exists(BASE.$fieldName, $meta) ? $meta[BASE.$fieldName] : $config['default']??''; switch ($config['type']) { case 'taxonomy': if (!$config['isReference']??true) { $config['wp'] = true; $value = implode(',', wp_get_post_terms($this->ID, jvbCheckBase($config['taxonomy']), ['fields' => 'ids'])); } break; case 'selector': if (array_key_exists('subtype', $config) && $config['subtype'] === 'taxonomy' && !$config['isReference']??true) { $config['wp'] = true; $value = implode(',',wp_get_post_terms($this->ID, jvbCheckBase($config['taxonomy']), ['fields' => 'ids'])); } break; } } $this->fields[$fieldName] = new Field($fieldName, $value, $config); } } // ───────────────────────────────────────────────────────────── // Magic Methods for Fluent Access // ───────────────────────────────────────────────────────────── public function __get(string $name): mixed { return $this->get($name); } public function __set(string $name, mixed $value): void { $this->set($name, $value); } public function __isset(string $name): bool { return $this->item->hasField($name); } // ───────────────────────────────────────────────────────────── // Core API // ───────────────────────────────────────────────────────────── /** * Get a field value */ public function get(string $name): mixed { // Handle repeater subfield path if (str_contains($name, ':')) { return $this->getByPath($name); } // Group shortcut path if (str_contains($name, '|')) { [$parent, $child] = explode('|', $name, 2); return $this->group($parent)->get($child); } if (!array_key_exists($name, $this->fields)) { error_log('[Meta]::get Attempted to get unregistered field: '.$name); return ''; } if (is_null($this->fields[$name])) { error_log('[Meta]::get Field does not seem to be setup yet: '.$name); return ''; } return $this->fields[$name]->get()??''; } /** * Set a field value (validates & sanitizes by default) */ public function set(string $name, mixed $value, $autosave = true): self { // Handle repeater subfield path (e.g., "services:2:image") if (str_contains($name, ':')) { return $this->setByPath($name, $value); } // Group shortcut if (str_contains($name, '|')) { return $this->setGroup($name, $value, $autosave); } $field = $this->fields[$name]??false; if (!$field) { error_log('No config found for field: '.$name); return $this; } // Validate if (!$this->validator->validate($value, $field->config)) { $field->addError("Validation failed for {$name}"); return $this; } // Sanitize $value = $this->sanitizer->sanitize($value, $field->config); $field->set($value); if ($autosave && $field->isDirty) { $this->save(); } return $this; } /** * Get multiple fields */ public function getAll(array $fields = []): array { if (empty($fields) || $fields === ['all']) { $fields = array_keys($this->fields); } $fields = array_filter($this->fields, function ($field) use ($fields) { return in_array($field, $fields); }, ARRAY_FILTER_USE_KEY); return array_map(function ($field) { return $field->value; }, $fields); } /** * Set multiple fields */ public function setAll(array $data):bool { foreach ($data as $name => $value) { $this->set($name, $value, false); } return $this->save(); } /** * Save all dirty fields to database */ public function save(): bool { $dirtyFields = array_filter($this->fields, function ($field) { return $field->isDirty; }); $defaults = array_filter($dirtyFields, function ($field) { return $field->isDefault; }); $custom = array_filter($dirtyFields, function($field) { return !$field->isDefault; }); $success = true; if (!empty($defaults)) { $result = false; switch ($this->type) { case 'post': //Deal with field exceptions, first, that cannot be set via wp_update_post foreach ($defaults as $fieldName => $field) { switch ($fieldName) { case 'post_thumbnail': $result = set_post_thumbnail($this->ID, $field->value); unset($defaults[$fieldName]); break; } //If it's a taxonomy, we use wp_set_object_terms if ( ($field->config['type'] === 'selector' && $field->config['subtype'] === 'taxonomy') || $field->config['type'] === 'taxonomy') { $result = wp_set_object_terms($this->ID, array_map('absint', explode(',', $field->value)), jvbCheckBase($field->config['taxonomy'])); unset($defaults[$fieldName]); } } if (!empty($defaults)) { error_log('Remaining fields: '.print_r($defaults, true)); $defaults = array_map(function ($field) { return $field->value; }, $defaults); error_log('Remaining values to save: '.print_r($defaults, true)); $data = array_merge([ 'post_type' => $this->slug, 'ID' => $this->ID ], $defaults); error_log('Updating post: '.print_r($data, true)); $result = wp_update_post($data); } break; case 'term': $termDefaults = array_map(fn($field) => $field->value, $defaults); $result = wp_update_term($this->ID, $this->slug, $termDefaults); break; case 'user': $userDefaults = array_map(fn($field) => $field->value, $defaults); $data = array_merge(['ID' => $this->ID], $userDefaults); $result = wp_update_user($data); break; } if (!$result || is_wp_error($result)) { $success = false; } } if (!empty($custom)) { $function = match ($this->type) { 'post' => 'update_post_meta', 'term' => 'update_term_meta', 'user' => 'update_user_meta', 'option' => 'update_option', default => false, }; if (!$function) { error_log('[Meta]::save() Invalid type, cannot save: '.$this->type); return false; } foreach ($custom as $field) { if ($this->type === 'option') { $result = update_option(BASE.$field->name, $field->value); } else { $result = $function($this->ID, BASE.$field->name, $field->value); } if (!$result) { error_log('Problem saving field: '.$field->name.' with value: '.print_r($field->value, true)); } } //Now handled directly from Registrar // if ($this->type === 'term' && Registrar::getInstance($this->slug)->hasFeature('is_content')) { // update_term_meta($this->ID, BASE.'date_modified', date('Y-m-d H:i:s')); // } } return $success; } /** * Delete a field value */ public function delete(string $name): bool { $field = $this->fields[$name]??false; if (!$field) { error_log('[Meta]::delete Could not delete field '.$name.': not registered'); return true; } if ($field->isTaxonomy()) { wp_set_object_terms($this->ID, [], $this->slug); return true; } return match ($this->type) { 'post' => delete_post_meta($this->ID, BASE.$name), 'term' => delete_term_meta($this->ID, BASE.$name), 'user', 'integrations' => delete_user_meta($this->ID, BASE.$name), 'option' => delete_option(BASE.$name), //Add in core fields $obj = match ($this->objectType) { 'post' => get_post($this->ID), 'term' => get_term($this->ID, $this->content), 'user' => get_userdata($this->ID), default => false }; if ($obj) { foreach (self::$coreFields[$this->objectType] as $name) { $value = match ($name) { 'post_thumbnail' => get_post_thumbnail_id($this->ID), default => $obj->$name??false }; // if (!$value) { // $this->log('Could not find value for '.$name.', of '.$this->content); // } $this->fields[$name] = $value??''; } } } public static function forPost(int $ID):self { if (array_key_exists($ID, self::$instances['post'])) { return self::$instances['post'][$ID]; } self::$instances['post'][$ID] = new self('post', $ID); return self::$instances['post'][$ID]; } public static function forTerm(int $ID):self { if (array_key_exists($ID, self::$instances['term'])) { return self::$instances['term'][$ID]; } self::$instances['term'][$ID] = new self('term', $ID); return self::$instances['term'][$ID]; } public static function forUser(int $ID):self { if (array_key_exists($ID, self::$instances['user'])) { return self::$instances['user'][$ID]; } self::$instances['user'][$ID] = new self('user', $ID); return self::$instances['user'][$ID]; } public static function forOptions(string $base = 'default'):self { if (array_key_exists($base, self::$instances['options'])) { return self::$instances['options'][$base]; } self::$instances['options'][$base] = new self('options', $base); return self::$instances['options'][$base]; } protected function resolveBasedType(): ?string { return match ($this->objectType) { 'post' => get_post_type($this->ID) ?: null, 'term' => ($term = get_term($this->ID)) && !is_wp_error($term) ? $term->taxonomy : null, 'user' => jvbUserRole($this->ID), default => null }; } /** * Delete multiple field values */ public function deleteAll(array $names): array { $results = []; /*************************************************************************** * GETTING FIELDS ***************************************************************************/ public function get(string $name): mixed { if (array_key_exists($name, $this->fields)) { return $this->fields[$name]; } if ($this->product && $this->isProductField($name)) { $value = $this->getProductField($name); } else if (in_array($name, static::$coreFields[$this->objectType], true)) { $value = $this->getCoreField($name); } else { $value = $this->getMetaField($name); } $this->fields[$name] = $value; return $value; } public function getAll(?array $names = null): array { $names = $names ?? $this->getAllFieldNames(); $out = []; foreach ($names as $name) { $results[$name] = $this->delete($name); $out[$name] = $this->get($name); } return $out; } protected function getCoreField(string $name): mixed { return match ($this->objectType) { 'post' => $name === 'post_thumbnail' ? get_post_thumbnail_id($this->ID) : (($p = get_post($this->ID)) ? ($p->$name ?? null) : null), 'term' => ($t = get_term($this->ID, $this->content)) && !is_wp_error($t) ? ($t->$name ?? null) : null, 'user' => ($u = get_userdata($this->ID)) ? ($u->$name ?? null) : null, }; } protected function getMetaField(string $name): mixed { $key = $this->metaKey($name); return match ($this->objectType) { 'post' => get_post_meta($this->ID, $key, true), 'term' => get_term_meta($this->ID, $key, true), 'user' => get_user_meta($this->ID, $key, true), 'options' => get_option($key) }; } /*************************************************************************** * SETTING FIELDS ***************************************************************************/ public function set(string $name, mixed $value): bool { $field = $this->getFieldConfig($name); if ($field) { if ($field['type']??'' === 'repeater' && str_contains($name, ':')) { return $this->setByPath($name, $value); } elseif ($field['type']??'' === 'group' && str_contains($name, '|')) { return $this->setGroup($name, $value); } if (!self::$validator->validate($value, $field)) { return false; } $value = self::$sanitizer->sanitize($value, $field); } if ($this->product && $this->isProductField($name)) { return $this->setProductField($name, $value); } if (in_array($name, static::$coreFields[$this->objectType], true)) { return $this->setCoreField($name, $value); } return $this->setMetaField($name, $value); } public function setAll(array $data): array { $core = array_filter($data, function ($name) { return in_array($name, self::$coreFields[$this->objectType]); }, ARRAY_FILTER_USE_KEY); $results = []; if (!empty($core)) { $data = array_filter($data, function ($name) { return !in_array($name, self::$coreFields[$this->objectType]); }, ARRAY_FILTER_USE_KEY); $results = $this->setAllCoreFields($core); } foreach ($data as $name => $value) { $results[$name] = $this->set($name, $value); } return $results; } /***************************************************************** * Repeater Access *****************************************************************/ protected function setAllCoreFields(array $data):array { $results = []; /** * Get repeater accessor for fluent repeater operations */ $core = array_filter($data, function ($name) { return in_array($name, self::$coreFields[$this->objectType]); }, ARRAY_FILTER_USE_KEY); if (count($core) !== count($data)) { $notCore = array_filter($data, function ($name) { return !in_array($name, self::$coreFields[$this->objectType]); }, ARRAY_FILTER_USE_KEY); $results = $this->setAll($notCore); } if (array_key_exists('post_thumbnail', $core)) { $results['post_thumbnail'] = $this->setCoreField('post_thumbnail', $core['post_thumbnail']); unset ($core['post_thumbnail']); } $return = match ($this->objectType) { 'post' => !is_wp_error(wp_update_post(array_merge(['ID' => $this->ID], $core))), 'term' => !is_wp_error(wp_update_term($this->ID, $this->content, $core)), 'user' => !is_wp_error(wp_update_user(array_merge(['ID' => $this->ID], $core))), }; foreach ($core as $name => $value) { $results[$name] = $return; } return $results; } protected function setCoreField(string $name, mixed $value): bool { $success = match ($this->objectType) { 'post' => $name === 'post_thumbnail' ? (!empty($value) ? set_post_thumbnail($this->ID, (int)$value) : delete_post_thumbnail($this->ID)) : !is_wp_error(wp_update_post(['ID' => $this->ID, $name => $value])), 'term' => !is_wp_error(wp_update_term($this->ID, $this->content, [$name => $value])), 'user' => !is_wp_error(wp_update_user(['ID' => $this->ID, $name => $value])), default => false }; if ($success) { $this->clearCache(); $this->fields[$name] = $value; } return $success; } protected function setMetaField(string $name, mixed $value): bool { $key = $this->metaKey($name); $result = match ($this->objectType) { 'post' => update_post_meta($this->ID, $key, $value), 'term' => update_term_meta($this->ID, $key, $value), 'user' => update_user_meta($this->ID, $key, $value), 'options' => update_option($key, $value) }; if ($result) { $this->fields[$name] = $value; $this->clearCache(); } return $result !== false; } /******************************************************************************** * DELETING FIELDS ********************************************************************************/ public function delete(string $name):bool { if (in_array($name, static::$coreFields[$this->objectType])) { return $this->deleteCoreField($name); } $field = $this->getFieldConfig($name); if ($field) { if ($this->isTaxonomy($field)) { return !is_wp_error(wp_set_object_terms($this->ID, [], $this->content)); } } $key = $this->metaKey($name); return match ($this->objectType) { 'post' => delete_post_meta($this->ID, $key), 'term' => delete_term_meta($this->ID, $key), 'user' => delete_user_meta($this->ID, $key), 'option'=> delete_option($key) }; } public function deleteAll(array $fieldNames):array { $core = array_filter($fieldNames, function ($name) { return in_array($name, self::$coreFields[$this->objectType]); }, ARRAY_FILTER_USE_KEY); $results = []; if (!empty($core)) { $fieldNames = array_filter($fieldNames, function ($name) { return !in_array($name, self::$coreFields[$this->objectType]); }); $results = $this->deleteAllCoreFields($core); } foreach ($fieldNames as $name) { $results[$name] = $this->delete($name); } return $results; } protected function deleteAllCoreFields(array $fieldNames):array { $results = []; $core = array_filter($fieldNames, function ($name) { return in_array($name, self::$coreFields[$this->objectType]); }, ARRAY_FILTER_USE_KEY); if (count($core) !== count($fieldNames)) { $notCore = array_filter($fieldNames, function ($name) { return !in_array($name, self::$coreFields[$this->objectType]); }, ARRAY_FILTER_USE_KEY); $results = $this->deleteAll($notCore); } if (array_key_exists('post_thumbnail', $fieldNames)) { $results['post_thumbnail'] = delete_post_thumbnail($this->ID); unset($fieldNames['post_thumbnail']); unset($core['post_thumbnail']); } $updated = []; foreach ($core as $name) { $updated[$name] = $this->getCoreDefault($name); } $success = match ($this->objectType) { 'post' => !is_wp_error(wp_update_post(array_merge(['ID' => $this->ID], $updated))), 'term' => !is_wp_error(wp_update_term($this->ID, $this->content, $updated)), 'user' => !is_wp_error(wp_update_user(array_merge(['ID' => $this->ID], $updated))) }; foreach ($core as $name) { $results[$name] = $success; } return $results; } protected function deleteCoreField(string $name):bool { if ($name === 'post_thumbnail') { return delete_post_thumbnail($this->ID); } $default = $this->getCoreDefault($name); return $this->setCoreField($name, $default); } /************************************************************** * WOOCOMMERCE PRODUCT SPECIFIC **************************************************************/ protected function isProductField(string $name): bool { return method_exists($this->product, 'get_'.$name) || method_exists($this->product, 'set_'.$name); } protected function getProductField(string $name): mixed { $method = 'get_'.$name; if ($name === 'variations') { return $this->getVariations(); } return method_exists($this->product, $method) ? $this->product->$method() : null; } protected function getVariations():array { $variations = $this->product->get_children(); $out = []; foreach ($variations as $v) { $out[] = self::forPost($v)->getAll(); } return $out; } protected function setProductField(string $name, mixed $value): bool { $method = 'set_'.$name; if (!method_exists($this->product, $method)) return false; $this->product->$method($value); $this->product->save(); $this->fields[$name] = $value; $this->clearCache(); return true; } protected function getAllProductFieldNames(): array { return [ 'price', 'sku', 'sale', 'short_description', 'image_id', 'gallery_image_ids', 'stock', 'stock_quantity', 'stock_status', 'variations' ]; } /*** Sanitization & Validation ***/ // protected function sanitize(mixed $value, array $field): mixed { // return match ($field['type']) { // 'text', 'tel' => sanitize_text_field($value), // 'email' => sanitize_email($value), // 'url' => esc_url_raw($value), // 'textarea' => !empty($field['quill']) ? wp_kses_post($value) : sanitize_textarea_field($value), // 'number' => is_numeric($value) ? $value + 0 : 0, // 'true_false' => (bool)$value, // 'datetime' => sanitize_text_field($value), // 'upload', 'image', 'gallery' => !empty($field['multiple']) ? array_map('intval', (array)$value) : (int)$value, // 'checkbox', 'set' => array_map('sanitize_text_field', (array)$value), // 'radio', 'select' => sanitize_text_field($value), // 'selector', 'taxonomy', 'user', 'post' => array_map('intval', (array)$value), // 'repeater', 'tagList' => $this->sanitizeRepeater($value, $field), // 'group' => $this->sanitizeGroup($value, $field), // default => is_array($value) ? array_map('sanitize_text_field', $value) : sanitize_text_field($value), // }; // } // protected function validate(string $name, mixed $value, array $field): bool|string { // if (!empty($field['required']) && ($value === '' || $value === null)) { // return $name.' is required'; // } // if (isset($field['maxLength']) && is_string($value) && strlen($value) > $field['maxLength']) { // return $name.' exceeds max length of '.$field['maxLength']; // } // if (isset($field['min']) && is_numeric($value) && $value < $field['min']) { // return $name.' is below minimum of '.$field['min']; // } // if (isset($field['max']) && is_numeric($value) && $value > $field['max']) { // return $name.' exceeds maximum of '.$field['max']; // } // if (in_array($field['type'], ['checkbox', 'set', 'radio', 'select'], true) && !empty($field['options'])) { // foreach ((array)$value as $v) { // if (!array_key_exists($v, $field['options'])) { // return $name.': invalid option '.$v; // } // } // } // return true; // } /************************************************************************ * REPEATER Helpers ************************************************************************/ public function repeater(string $name): Repeater { return new Repeater($this, $name); } protected function setByPath(string $path, mixed $value): self protected function setByPath(string $path, mixed $value):bool { $parts = explode(':', $path, 3); if (count($parts) !== 3) { return $this; $this->log('Could not set value by path'); return false; } [$repeaterName, $rowIndex, $subField] = $parts; $this->repeater($repeaterName)->setField((int) $rowIndex, $subField, $value); return $this; $this->clearCache(); return $this->repeater($repeaterName)->setField((int) $rowIndex, $subField, $value); } protected function getByPath(string $path): mixed @@ -600,123 +525,85 @@ [$repeaterName, $rowIndex, $subField] = $parts; return $this->repeater($repeaterName)->field((int) $rowIndex, $subField); } /**************************************************************************************** * GROUP helpers ****************************************************************************************/ public function group(string $name): Group { return new Group($this, $name); } protected function setGroup(string $name, mixed $value, bool $autosave = true): self protected function setGroup(string $name, mixed $value):bool { $parent = strtok($name, '|'); $child = str_replace($parent . '|', '', $name); $this->group($parent)->set($child, $value, $autosave); return $this; [$parent, $child] = explode('|', $name, 2); $this->clearCache(); return $this->group($parent)->set($child, $value); } // ───────────────────────────────────────────────────────────── // Utility Methods // ───────────────────────────────────────────────────────────── /** * Discard all unsaved changes */ public function reset(): self /*************************************************************************************** * UTILITY ***************************************************************************************/ protected function clearCache():void { $this->item->resetAll(); return $this; match ($this->objectType) { 'post' => Cache::clearPost($this->ID), 'term' => Cache::clearTerm($this->ID), 'user' => Cache::clearUser($this->ID), }; } /** * Get field configuration */ public function config(string $name): ?array protected function isTaxonomy(array $field):bool { return $this->fields[$name]->config??null; $type = $field['type']??''; return $type === 'taxonomy' || ($type === 'selector' &&$field['subtype']??'' === 'taxonomy'); } protected function getAllFieldNames(): array { $names = static::$coreFields[$this->objectType]; if ($this->hasRegistrar()) { $names = array_merge($names, array_keys(self::$registrars[$this->content])); } if ($this->product) { $names = array_merge($names, $this->getAllProductFieldNames()); } return array_unique($names); } /** * Get item ID */ public function id(): int|string|null protected function hasRegistrar():bool { return $this->ID; return array_key_exists($this->content, self::$registrars); } /** * Get object type (post, term, user, options) */ public function objectType(): string public function getFieldConfig(string $name): ?array { if (!$this->hasRegistrar()) return null; return self::$registrars[$this->content][$name] ?? null; } protected function getCoreDefault(string $name):string { return $this->type; } /** * Get content type (tattoo, artist, etc) */ public function contentType(): ?string { return $this->contentType; } public function item():Item { return $this->item; } // ───────────────────────────────────────────────────────────── // Protected Helpers // ───────────────────────────────────────────────────────────── /** * Check for field update overrides */ public function checkOverrides(Field $field): bool { $name = $field->name; $type = $field->type(); $value = $field->value; do_action('jvb_meta_update', $name, $value, $this->item->objectType); $overrides = [ BASE . 'update_' . $name, BASE . 'update_' . $type, 'jvb_update_' . $name, 'jvb_update_' . $type, ]; foreach ($overrides as $override) { if (function_exists($override)) { $override($this->item->id, $value); return true; } } return false; } /** * Get default value for a field type */ protected function getDefaultValue(string $name): string|int|bool|array { $config = $this->item->getFieldConfig($name); $type = $config['type'] ?? 'text'; return match ($this->typeManager->getMetaType($type)) { 'object', 'array' => [], 'boolean' => false, 'integer' => 0, default => '', return match($name) { 'post_status' => 'draft', 'post_date','post_modified' => date('mysql'), 'post_name' => sanitize_title($this->get('post_title')), default => '' }; } protected function metaKey(string $name): string { if ($this->product) { if (in_array($name, $this->getAllProductFieldNames())) { return $name; } } return jvbCheckBase($name); } protected function log(string $message, array $context = []):void { $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'] ?? ''; error_log('[Meta] '.$caller.' '.$message); if (!empty($context)) { error_log(' CONTEXT: '.print_r($context, true)); } } } inc/meta/MetaOld.php
New file @@ -0,0 +1,744 @@ <?php namespace JVBase\meta; use JVBase\base\Options; use JVBase\registrar\Registrar; use WP_Post; use WP_Term; use WP_User; if (!defined('ABSPATH')) { exit; } class MetaOld { /** * @var string post, term, user, or options */ protected string $type; /** * @var ?string the full slug, with BASE */ protected ?string $slug; protected string $contentType; protected Item $item; protected Storage $storage; protected Validator $validator; protected Sanitizer $sanitizer; protected array $fields; protected WP_Post|WP_Term|WP_User|false|null $wpObject; protected int|string $ID; protected MetaTypeManager $typeManager; protected static array $instances = ['post' => [],'term' => [], 'user'=>[],'option'=>[]]; protected array $defaults = ['post_thumbnail']; /** * Create Meta instance for a post */ public static function forPost(int $id): self { if (array_key_exists($id, self::$instances['post'])) { return self::$instances['post'][$id]; } $new = new self($id, 'post'); self::$instances['post'][$id] = $new; return $new; } /** * Create Meta instance for a term */ public static function forTerm(int $id): self { if (array_key_exists($id, self::$instances['term'])) { return self::$instances['term'][$id]; } $new = new self($id, 'term'); self::$instances['term'][$id] = $new; return $new; } /** * Create Meta instance for a user */ public static function forUser(int $id): self { if (array_key_exists($id, self::$instances['user'])) { return self::$instances['user'][$id]; } $new = new self($id, 'user'); self::$instances['user'][$id] = $new; return $new; } /** * Create Meta instance for options */ public static function forOptions(?string $baseKey = BASE): self { if (array_key_exists($baseKey, self::$instances['option'])) { return self::$instances['option'][$baseKey]; } $new = new self($baseKey, 'option'); self::$instances['option'][$baseKey] = $new; return $new; } /*************************************************************** * Constructor ***************************************************************/ public function __construct(int|string|null $id, string $type, array $fields = []) { $this->validator = new Validator(); $this->sanitizer = new Sanitizer(); $this->typeManager = new MetaTypeManager(); $this->type = $type; $this->ID = $id; $this->buildData($id, $type, $fields); } protected function buildData(int|string|null $id, string $type, array $fields):void { $this->wpObject = match($type) { 'post' => get_post($id), 'term' => get_term($id), 'user', 'integrations' => get_userdata($id), default => false }; $this->slug = match($type) { 'post' => $this->wpObject->post_type, 'term' => $this->wpObject->taxonomy, 'user' => jvbUserRole($id), default => null }; $registrar = !is_null($this->slug) ? Registrar::getInstance($this->slug) : false; $fields = $registrar ? $registrar->getFields() : $fields; if ($this->type === 'post' && $this->wpObject && $this->wpObject->post_type === 'product') { $fields = $this->getProductFields(); } if ($this->type == 'option') { $options = Options::getInstance(); $fields = $options->getFields(); } $meta = match($type) { 'post' => get_post_meta($id), 'term' => get_term_meta($id), 'user' => get_user_meta($id), default => [] }; if (!$meta) { $meta = []; } $meta = array_map(fn($value) => maybe_unserialize($value[0]), $meta); $this->fields = []; foreach ($fields as $fieldName => $config) { $fieldName = jvbNoBase($fieldName); $value = ''; if ($this->type === 'option') { $value = get_option(BASE . $fieldName, $config['default'] ?? ''); } else if ($this->wpObject && $this->wpObject->post_type === 'product') { if (property_exists($this->wpObject, $fieldName)) { if ($fieldName === 'post_thumbnail') { $value = get_post_thumbnail_id($this->ID); } else { $value = $this->wpObject->$fieldName; } } else { $product = wc_get_product($this->wpObject->ID); $value = match ($fieldName) { 'sku' => $product->get_sku(), '_price' => $product->get_price(), 'description'=> $product->get_short_description(), 'sale' => $product->get_sale_price(), 'post_thumbnail' => $product->get_image_id(), '_product_images' => $product->get_gallery_image_ids(), '_stock' => $product->get_stock_quantity(), '_stock_status' => $product->get_stock_status(), default => '', }; } } else if ( ($this->wpObject && property_exists($this->wpObject, $fieldName)) || ($type === 'user' && $this->wpObject && property_exists($this->wpObject->data, $fieldName))) { $config['wp'] = true; $value = $this->wpObject->$fieldName; } else if (in_array($fieldName, $this->defaults)) { $config['wp'] = true; switch ($fieldName) { case 'post_thumbnail': $value = get_post_thumbnail_id($this->ID); break; } } else { $value = array_key_exists(BASE.$fieldName, $meta) ? $meta[BASE.$fieldName] : $config['default']??''; switch ($config['type']) { case 'taxonomy': if (!$config['isReference']??true) { $config['wp'] = true; $value = implode(',', wp_get_post_terms($this->ID, jvbCheckBase($config['taxonomy']), ['fields' => 'ids'])); } break; case 'selector': if (array_key_exists('subtype', $config) && $config['subtype'] === 'taxonomy' && !$config['isReference']??true) { $config['wp'] = true; $value = implode(',',wp_get_post_terms($this->ID, jvbCheckBase($config['taxonomy']), ['fields' => 'ids'])); } break; } } $this->fields[$fieldName] = new Field($fieldName, $value, $config); } } protected function getProductFields():array { $fields = []; if (class_exists('WooCommerce') && $this->type === 'post' && $this->wpObject->post_type??'' === 'product') { $fields['post_title'] = [ 'type' => 'text', 'label' => 'Title', ]; $fields['post_content'] = [ 'type' => 'text', 'label' => 'Content', ]; $fields['post_excerpt'] = [ 'type' => 'text', 'label' => 'Short Description', ]; $fields['post_thumbnail'] = [ 'type' => 'upload', 'multiple' => false, 'label' => 'Image', ]; $fields['_product_images'] = [ 'type' => 'upload', 'label' => 'Gallery' ]; $fields['ID'] = [ 'type' => 'text', 'label' => 'ID', ]; $fields['_stock'] = [ 'type' => 'number', 'label' => 'Stock', 'default' => null ]; $fields['_stock_status'] = [ 'type' => 'select', 'label' => 'Stock Status', 'default' => 'instock', 'options' => [ 'instock' => 'In Stock', 'outofstock' => 'Out of Stock', 'onbackorder' => 'On Backorder' ] ]; $fields['_price'] = [ 'type' => 'text', 'subtype' => 'number', 'label' => 'Price' ]; $product = wc_get_product($this->ID); $fields['sku'] = [ 'type' => 'text', 'label' => 'SKU', ]; $out = []; if ($product && $product->is_type('variable')) { $var = $product->get_children(); $var = array_filter(array_map(function($v) { return wc_get_product($v); }, $var)); $title= $product->get_title(); foreach ($var as $variation) { $out[] = [ 'id' => $variation->get_id(), 'sku' => $variation->get_sku(), 'title' => str_replace($title.' - ', '', $variation->get_name()), 'price' => $variation->get_price(), 'description'=> $variation->get_short_description(), 'sale' => $variation->get_sale_price(), 'image' => $variation->get_image_id(), 'stock' => $variation->get_stock_quantity(), 'stock_status' => $variation->get_stock_status() ]; } } if (!empty($out)) { $this->fields['variations'] = new Field('variations', $out, [ 'type' => 'repeater', 'label' => 'Variations', 'fields' => [ 'id' => [ 'type' => 'text', 'subtype' => 'number', 'label' => 'Variation ID', ], 'sku' => [ 'type' => 'text', 'label' => 'Stock Keeping Unit (SKU)' ], 'title' => [ 'type' => 'text', 'label' => 'Variation Title' ], 'price' => [ 'type' => 'text', 'subtype' => 'number', 'label' => 'Variation Price' ], 'sale' => [ 'type' => 'text', 'subtype' => 'number', 'label' => 'Sale Price', ], 'image' => [ 'type' => 'text', 'subtype' => 'number', 'label' => 'Variation Image ID' ], 'stock' => [ 'type' => 'text', 'label' => 'Stock Quantity' ], 'stock_status' => [ 'type' => 'text', 'label' => 'Stock Status' ] ] ]); } } return $fields; } // ───────────────────────────────────────────────────────────── // Magic Methods for Fluent Access // ───────────────────────────────────────────────────────────── public function __get(string $name): mixed { return $this->get($name); } public function __set(string $name, mixed $value): void { $this->set($name, $value); } public function __isset(string $name): bool { return $this->item->hasField($name); } // ───────────────────────────────────────────────────────────── // Core API // ───────────────────────────────────────────────────────────── /** * Get a field value */ public function get(string $name): mixed { // Handle repeater subfield path if (str_contains($name, ':')) { return $this->getByPath($name); } // Group shortcut path if (str_contains($name, '|')) { [$parent, $child] = explode('|', $name, 2); return $this->group($parent)->get($child); } if (!array_key_exists($name, $this->fields)) { error_log('[Meta]::get Attempted to get unregistered field: '.$name); return ''; } if (is_null($this->fields[$name])) { error_log('[Meta]::get Field does not seem to be setup yet: '.$name); return ''; } return $this->fields[$name]->get()??''; } /** * Set a field value (validates & sanitizes by default) */ public function set(string $name, mixed $value, $autosave = true): self { // Handle repeater subfield path (e.g., "services:2:image") if (str_contains($name, ':')) { return $this->setByPath($name, $value); } // Group shortcut if (str_contains($name, '|')) { return $this->setGroup($name, $value, $autosave); } $field = $this->fields[$name]??false; if (!$field) { error_log('No config found for field: '.$name); return $this; } // Validate if (!$this->validator->validate($value, $field->config)) { $field->addError("Validation failed for {$name}"); return $this; } // Sanitize $value = $this->sanitizer->sanitize($value, $field->config); $field->set($value); if ($autosave && $field->isDirty) { $this->save(); } return $this; } /** * Get multiple fields */ public function getAll(array $fields = []): array { if (empty($fields) || $fields === ['all']) { $fields = array_keys($this->fields); } $fields = array_filter($this->fields, function ($field) use ($fields) { return in_array($field, $fields); }, ARRAY_FILTER_USE_KEY); return array_map(function ($field) { return $field->value; }, $fields); } /** * Set multiple fields */ public function setAll(array $data):bool { foreach ($data as $name => $value) { $this->set($name, $value, false); } return $this->save(); } /** * Save all dirty fields to database */ public function save(): bool { $dirtyFields = array_filter($this->fields, function ($field) { return $field->isDirty; }); $defaults = array_filter($dirtyFields, function ($field) { return $field->isDefault; }); $custom = array_filter($dirtyFields, function($field) { return !$field->isDefault; }); $success = true; if (!empty($defaults)) { $result = false; switch ($this->type) { case 'post': //Deal with field exceptions, first, that cannot be set via wp_update_post foreach ($defaults as $fieldName => $field) { switch ($fieldName) { case 'post_thumbnail': $result = set_post_thumbnail($this->ID, $field->value); unset($defaults[$fieldName]); break; } //If it's a taxonomy, we use wp_set_object_terms if ( ($field->config['type'] === 'selector' && $field->config['subtype'] === 'taxonomy') || $field->config['type'] === 'taxonomy') { $result = wp_set_object_terms($this->ID, array_map('absint', explode(',', $field->value)), jvbCheckBase($field->config['taxonomy'])); unset($defaults[$fieldName]); } } if (!empty($defaults)) { error_log('Remaining fields: '.print_r($defaults, true)); $defaults = array_map(function ($field) { return $field->value; }, $defaults); error_log('Remaining values to save: '.print_r($defaults, true)); $data = array_merge([ 'post_type' => $this->slug, 'ID' => $this->ID ], $defaults); error_log('Updating post: '.print_r($data, true)); $result = wp_update_post($data); } break; case 'term': $termDefaults = array_map(fn($field) => $field->value, $defaults); $result = wp_update_term($this->ID, $this->slug, $termDefaults); break; case 'user': $userDefaults = array_map(fn($field) => $field->value, $defaults); $data = array_merge(['ID' => $this->ID], $userDefaults); $result = wp_update_user($data); break; } if (!$result || is_wp_error($result)) { $success = false; } } if (!empty($custom)) { $function = match ($this->type) { 'post' => 'update_post_meta', 'term' => 'update_term_meta', 'user' => 'update_user_meta', 'option' => 'update_option', default => false, }; if (!$function) { error_log('[Meta]::save() Invalid type, cannot save: '.$this->type); return false; } foreach ($custom as $field) { if ($this->type === 'option') { $result = update_option(BASE.$field->name, $field->value); } else { $result = $function($this->ID, BASE.$field->name, $field->value); } if (!$result) { error_log('Problem saving field: '.$field->name.' with value: '.print_r($field->value, true)); } } //Now handled directly from Registrar // if ($this->type === 'term' && Registrar::getInstance($this->slug)->hasFeature('is_content')) { // update_term_meta($this->ID, BASE.'date_modified', date('Y-m-d H:i:s')); // } } return $success; } /** * Delete a field value */ public function delete(string $name): bool { $field = $this->fields[$name]??false; if (!$field) { error_log('[Meta]::delete Could not delete field '.$name.': not registered'); return true; } if ($field->isTaxonomy()) { wp_set_object_terms($this->ID, [], $this->slug); return true; } return match ($this->type) { 'post' => delete_post_meta($this->ID, BASE.$name), 'term' => delete_term_meta($this->ID, BASE.$name), 'user', 'integrations' => delete_user_meta($this->ID, BASE.$name), 'option' => delete_option(BASE.$name), default => false }; } /** * Delete multiple field values */ public function deleteAll(array $names): array { $results = []; foreach ($names as $name) { $results[$name] = $this->delete($name); } return $results; } /***************************************************************** * Repeater Access *****************************************************************/ /** * Get repeater accessor for fluent repeater operations */ public function repeater(string $name): Repeater { return new Repeater($this, $name); } protected function setByPath(string $path, mixed $value): self { $parts = explode(':', $path, 3); if (count($parts) !== 3) { return $this; } [$repeaterName, $rowIndex, $subField] = $parts; $this->repeater($repeaterName)->setField((int) $rowIndex, $subField, $value); return $this; } protected function getByPath(string $path): mixed { $parts = explode(':', $path, 3); if (count($parts) !== 3) { return null; } [$repeaterName, $rowIndex, $subField] = $parts; return $this->repeater($repeaterName)->field((int) $rowIndex, $subField); } public function group(string $name): Group { return new Group($this, $name); } protected function setGroup(string $name, mixed $value, bool $autosave = true): self { $parent = strtok($name, '|'); $child = str_replace($parent . '|', '', $name); $this->group($parent)->set($child, $value, $autosave); return $this; } // ───────────────────────────────────────────────────────────── // Utility Methods // ───────────────────────────────────────────────────────────── /** * Discard all unsaved changes */ public function reset(): self { $this->item->resetAll(); return $this; } /** * Get field configuration */ public function config(string $name): ?array { return $this->fields[$name]->config??null; } /** * Get item ID */ public function id(): int|string|null { return $this->ID; } /** * Get object type (post, term, user, options) */ public function objectType(): string { return $this->type; } /** * Get content type (tattoo, artist, etc) */ public function contentType(): ?string { return $this->contentType; } public function item():Item { return $this->item; } /** * Check for field update overrides */ public function checkOverrides(Field $field): bool { $name = $field->name; $type = $field->type(); $value = $field->value; do_action('jvb_meta_update', $name, $value, $this->item->objectType); $overrides = [ BASE . 'update_' . $name, BASE . 'update_' . $type, 'jvb_update_' . $name, 'jvb_update_' . $type, ]; foreach ($overrides as $override) { if (function_exists($override)) { $override($this->item->id, $value); return true; } } return false; } /** * Get default value for a field type */ protected function getDefaultValue(string $name): string|int|bool|array { $config = $this->item->getFieldConfig($name); $type = $config['type'] ?? 'text'; return match ($this->typeManager->getMetaType($type)) { 'object', 'array' => [], 'boolean' => false, 'integer' => 0, default => '', }; } } inc/meta/Repeater.php
@@ -25,7 +25,7 @@ $this->meta = $meta; $this->name = $name; $this->data = $meta->get($name) ?: []; $this->config = $meta->config($name) ?? []; $this->config = $meta->getFieldConfig($name) ?? []; // Ensure data is array if (!is_array($this->data)) { @@ -111,16 +111,14 @@ /** * Set field value in specific row */ public function setField(int $index, string $field, mixed $value): self public function setField(int $index, string $field, mixed $value):bool { if (!isset($this->data[$index])) { $this->data[$index] = []; } $this->data[$index][$field] = $value; $this->sync(); return $this; return $this->sync(); } /** @@ -374,9 +372,9 @@ /** * Sync data back to Meta instance */ protected function sync(): void protected function sync(): bool { $this->meta->set($this->name, $this->data); return $this->meta->set($this->name, $this->data); } /**