diff --git a/.wordpress-org/blueprints/blueprint.json b/.wordpress-org/blueprints/blueprint.json index 0d2c64e4..75651f33 100644 --- a/.wordpress-org/blueprints/blueprint.json +++ b/.wordpress-org/blueprints/blueprint.json @@ -15,7 +15,7 @@ "step": "installPlugin", "pluginZipFile": { "resource": "url", - "url": "https:\/\/downloads.wordpress.org\/plugin\/ultimate-member.2.10.2.zip" + "url": "https:\/\/downloads.wordpress.org\/plugin\/ultimate-member.2.10.3.zip" }, "options": { "activate": true diff --git a/README.md b/README.md index 9412b326..45927cc6 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ GNU Version 2 or Any Later Version ### IMPORTANT: PLEASE UPDATE THE PLUGIN TO AT LEAST VERSION 2.6.7 IMMEDIATELY. VERSION 2.6.7 PATCHES SECURITY PRIVILEGE ESCALATION VULNERABILITY. PLEASE SEE [THIS ARTICLE](https://docs.ultimatemember.com/article/1866-security-incident-update-and-recommended-actions) FOR MORE INFORMATION -[Official Release Version: 2.10.2](https://github.com/ultimatemember/ultimatemember/releases/tag/2.10.2). +[Official Release Version: 2.10.3](https://github.com/ultimatemember/ultimatemember/releases/tag/2.10.3). ## Changelog diff --git a/assets/css/admin/builder.css b/assets/css/admin/builder.css index fe28aca1..83959bf3 100644 --- a/assets/css/admin/builder.css +++ b/assets/css/admin/builder.css @@ -495,6 +495,11 @@ line-height: 18px; margin: 0 0 10px 0; } +.um-admin-drag-col.cols-3 .um-admin-drag-fld-banned { + height: auto; + line-height: 18px; + margin: 0 0 10px 0; +} .um-admin-drag-col.cols-3 .um-admin-drag-fld-icons { float: none; position: absolute; @@ -600,6 +605,16 @@ color: #fff; } +.um-admin-drag-fld-banned { + float: left; + font-size: 13px; + height: 30px; + line-height: 30px; + margin: 0 0 0 20px; + font-weight: 400; + color: var(--um-blocks-error-color, #d92d20); +} + .um-admin-drag-fld-icons { float: right; } diff --git a/assets/css/admin/builder.min.css b/assets/css/admin/builder.min.css index 184ab039..83cc04a0 100644 --- a/assets/css/admin/builder.min.css +++ b/assets/css/admin/builder.min.css @@ -1 +1 @@ -@-webkit-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.um-admin-row-loading{position:absolute;width:100%;height:calc(100% + 30px);top:-30px;left:0;background:rgba(241,241,241,.6);z-index:999;display:none}.um-admin-row-loading>span{display:block;-webkit-transition:.1s opacity;-moz-transition:.1s opacity;-ms-transition:.1s opacity;-o-transition:.1s opacity;transition:.1s opacity;color:#c6c6c6!important;-webkit-animation:um-ajax-spinning 1.1s infinite linear;animation:um-ajax-spinning 1.1s infinite linear;border-top:.2em solid #000;border-right:.2em solid #000;border-bottom:.2em solid #000;border-left:.2em solid #c6c6c6;font-size:1.75em;-ms-transform:translateZ(0);transform:translateZ(0);border-radius:50%;width:40px;height:40px;margin:-20px 0 0 -20px;outline:0;padding:0;vertical-align:baseline;position:absolute;left:50%;top:50%}.um-admin-boxed-links{width:100%;text-align:center}.um-admin-boxed-links a{width:33.3%;float:left;border-left:1px solid #fff;border-right:1px solid #fff;padding:30px 25px!important;background:#f8f8f8;text-align:center;display:inline-block!important;font-size:14px;transition:all .2s linear;text-decoration:none;color:#666;margin:0!important;box-sizing:border-box!important;border-radius:2px;font-weight:600}.um-admin-boxed-links a:hover{background:#eee}.um-admin-boxed-links a.um-admin-activebg{background:#0085ba;color:#fff}.um-admin-boxed-links a.um-admin-activebg:hover{background:#0085ba;color:#fff}.um-admin-boxed-links i{font-size:28px;vertical-align:middle;margin:0 10px 0 0}.um-admin-half{float:left;width:48%}.um-admin-half select{box-sizing:border-box}.um-admin-half.um-admin-right{float:right}.um-admin-half p:first-child{margin-top:0}.um-admin-half .um{opacity:1;margin-bottom:0!important}.um-admin-tri{float:left;width:33%;position:relative}.um-admin-tri[data-select2-id]{position:relative}._heading_text[data-select2-id]{position:relative}.um-admin-error-block,.um-admin-success-block{display:none;width:100%;background:#c74a4a;border-radius:3px;color:#fff;box-sizing:border-box;position:relative;padding:12px;font-size:13px;line-height:1em!important;margin:0 0 16px 0}.um-admin-success-block{background:#7acf58}.um-admin-cur-condition-template{display:none}.dynamic-mce-content{display:none}.um-admin-btns{line-height:1.5em;margin:0 0 5px 0;display:flex;flex-direction:row;justify-content:flex-start;align-items:baseline;flex-wrap:wrap}.um-admin-btns .um-no-custom-fields{margin:0}.um-admin-btns a{margin:0 3px 8px 0!important;font-size:12px!important;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;flex-wrap:nowrap;display:flex!important}.um-admin-btns a.with-icon span{color:#aaa;font-size:15px!important;padding-left:5px;height:auto;width:auto}.um-admin-btns a.with-icon:hover span{color:#cb3838}.um-admin-btn-toggle{padding:10px 0}.um-admin-btn-toggle p{margin:0 5px 0 0!important}.um-admin-btn-toggle p.um-admin-reset-conditions a{margin-bottom:5px}.um-admin-btn-toggle a{text-decoration:none;color:#999}.um-admin-btn-toggle a.um-admin-new-condition{margin-bottom:5px}.um-admin-btn-toggle a.active{color:#0085ba}.um-admin-btn-toggle a.active:hover{color:#0085ba}.um-admin-btn-toggle i{margin:0 5px 0 0!important;height:100%;color:#666;position:relative;top:1px}.um-admin-btn-content{display:none;padding:5px 0 0 0}.um-admin-btn-content p{float:left;margin-right:10px!important;padding:0!important}.um-admin-btn-content p.um-admin-conditions-notice{width:100%;margin:0 0 9px 0!important}.um-admin-btn-content .um-admin-cur-condition:not(:last-child){margin:0 0 5px 0}.um-admin-builder i{font-size:15px!important;height:100%!important;line-height:100%!important;margin:0 5px 0 0!important}.um-admin-builder .um-admin-drag{margin-top:40px}.um-admin-drag-add-field{display:block;width:100%;height:30px;line-height:30px;text-align:center;color:#aaa;text-decoration:none}.um-admin-drag-add-field:hover{color:#0085ba}.um-admin-drag-add-field i{font-size:23px!important;margin:0!important}.um-admin-drag-ctrls{position:absolute;top:-30px;left:0}.um-admin-drag-ctrls.um-admin-drag-ctrls-demo{position:absolute;right:7px;top:-30px;left:auto}.um-admin-drag-ctrls a{float:left;margin:0 5px 0 0;height:30px;line-height:30px;background:#eee;color:#888;padding:0 8px;text-decoration:none;border-radius:2px 2px 0 0}.um-admin-drag-ctrls a:hover{background-color:#e5e5e5}.um-admin-drag-ctrls a.active,.um-admin-drag-ctrls a.active:hover{background-color:#0085ba;color:#fff}.um-admin-drag-ctrls.columns a{width:30px!important;background-image:url(../../img/builder/1-column.gif);background-repeat:no-repeat;background-position:center}.um-admin-drag-ctrls.columns a:nth-child(2){background-image:url(../../img/builder/2-columns.gif)}.um-admin-drag-ctrls.columns a:last-child{background-image:url(../../img/builder/3-columns.gif)}.um-admin-drag-ctrls.columns a.active{background-image:url(../../img/builder/1-column-active.gif)}.um-admin-drag-ctrls.columns a.active:nth-child(2){background-image:url(../../img/builder/2-columns-active.gif)}.um-admin-drag-ctrls.columns a.active:last-child{background-image:url(../../img/builder/3-columns-active.gif)}.um-admin-drag{width:100%;background:#f1f1f1;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;position:relative;padding:60px 20px 20px 20px}.um-admin-drag-row{width:100%;background:#fefefe;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;position:relative;padding:60px 20px 20px 20px;margin:0 0 60px 0}.um-admin-drag-row:last-child{margin-bottom:0}.um-admin-drag-row-icons{position:absolute;right:0;top:-30px;height:30px}.um-admin-drag-row-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-row-icons a:hover{background:#008ec2}.um-admin-drag-row-icons span{cursor:move!important;text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-row-icons span:hover{background:#008ec2}.um-admin-drag-row-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-rowsub{position:relative;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;background:#f1f1f1;padding:20px;margin:0 0 60px 0}.um-admin-drag-rowsub:last-child{margin-bottom:0}.um-admin-drag-rowsub-icons{position:absolute;right:0;top:-30px;height:30px}.um-admin-drag-rowsub-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-rowsub-icons a:hover{background:#008ec2}.um-admin-drag-rowsub-icons span{cursor:move!important;text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-rowsub-icons span:hover{background:#008ec2}.um-admin-drag-rowsub-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-col{float:left;width:100%;background:#fff;border:1px dashed #bbb;box-sizing:border-box;padding:20px}.um-admin-drag-col.cols-3{width:32%}.um-admin-drag-col.cols-3.cols-last{width:32%}.um-admin-drag-col.cols-3.cols-middle{width:32%;margin:0 2%}.um-admin-drag-col.cols-3 .um-admin-drag-fld-title{height:auto;line-height:18px;margin:10px;margin-bottom:0}.um-admin-drag-col.cols-3 .um-admin-drag-fld-type{height:auto;line-height:18px;margin:0 0 10px 0}.um-admin-drag-col.cols-3 .um-admin-drag-fld-icons{float:none;position:absolute;bottom:0;right:0;height:30px;display:none}.um-admin-drag-col.cols-3 .um-admin-drag-fld-icons.um-field-type-group{float:left;position:relative;bottom:auto;right:auto;height:30px;display:block}.um-admin-drag-col.cols-3 .um-admin-drag-fld:hover .um-admin-drag-fld-icons{display:block}.um-admin-drag-col.cols-1{width:100%}.um-admin-drag-col.cols-1.cols-last{width:100%}.um-admin-drag-col.cols-2{width:49%;margin:0 1% 0 0}.um-admin-drag-col.cols-2.cols-last{width:49%;margin:0 0 0 1%}.um-admin-drag-col>.um-admin-drag-fld:last-of-type{margin-bottom:20px}.um-admin-drag-addrow{text-align:center;font-size:14px;color:#888;cursor:pointer;border:1px dashed #bbb;padding:10px 0;margin:20px 0 0 0}.um-admin-drag-addrow i{font-size:23px!important;margin:0!important}.um-admin-drag-addrow:hover{color:#0085ba}.um-admin-drag-fld{display:block;position:relative;border:1px solid #ddd;margin:0 0 15px 0;background:#f5f5f5;cursor:move!important}.um-admin-drag-fld.um-field-type-group{background:#555;border:0}.um-admin-drag-fld-title{float:left;font-weight:700;font-size:13px;color:#666;height:30px;line-height:30px;margin:0 0 0 20px}.um-admin-drag-fld-title.um-field-type-group{color:#fff}.um-admin-drag-fld-title i{width:24px;display:inline-block;text-align:center}.um-admin-drag-group{background:#fcfcfc;cursor:default;padding:20px;border:1px dashed #bbb}.um-admin-drag-fld-type{float:left;font-size:13px;height:30px;line-height:30px;margin:0 0 0 20px;font-weight:400;color:#999}.um-admin-drag-fld-type.um-field-type-group{color:#fff}.um-admin-drag-fld-icons{float:right}.um-admin-drag-fld-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-fld-icons a:hover{background:#008ec2}.um-admin-drag-fld-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-fld-icons.um-field-type-group a.um_admin_duplicate_field{display:none!important}.um-row-placeholder{width:100%;border:2px dashed #aaa;box-sizing:border-box}.um-rowsub-placeholder{width:100%;border:2px dashed #ccc;box-sizing:border-box;display:block}.um-fld-placeholder{display:block;border:1px dashed #ddd;background:#fff;box-sizing:border-box;width:100%}.um-admin-modal #UM_preview_form .um-admin-modal-body{position:relative}.um-admin-modal #UM_preview_form .um-admin-modal-body .um-admin-preview-overlay{position:absolute;top:0;bottom:0;left:0;right:0;background-color:rgba(255,255,255,0);z-index:100} \ No newline at end of file +@-webkit-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.um-admin-row-loading{position:absolute;width:100%;height:calc(100% + 30px);top:-30px;left:0;background:rgba(241,241,241,.6);z-index:999;display:none}.um-admin-row-loading>span{display:block;-webkit-transition:.1s opacity;-moz-transition:.1s opacity;-ms-transition:.1s opacity;-o-transition:.1s opacity;transition:.1s opacity;color:#c6c6c6!important;-webkit-animation:um-ajax-spinning 1.1s infinite linear;animation:um-ajax-spinning 1.1s infinite linear;border-top:.2em solid #000;border-right:.2em solid #000;border-bottom:.2em solid #000;border-left:.2em solid #c6c6c6;font-size:1.75em;-ms-transform:translateZ(0);transform:translateZ(0);border-radius:50%;width:40px;height:40px;margin:-20px 0 0 -20px;outline:0;padding:0;vertical-align:baseline;position:absolute;left:50%;top:50%}.um-admin-boxed-links{width:100%;text-align:center}.um-admin-boxed-links a{width:33.3%;float:left;border-left:1px solid #fff;border-right:1px solid #fff;padding:30px 25px!important;background:#f8f8f8;text-align:center;display:inline-block!important;font-size:14px;transition:all .2s linear;text-decoration:none;color:#666;margin:0!important;box-sizing:border-box!important;border-radius:2px;font-weight:600}.um-admin-boxed-links a:hover{background:#eee}.um-admin-boxed-links a.um-admin-activebg{background:#0085ba;color:#fff}.um-admin-boxed-links a.um-admin-activebg:hover{background:#0085ba;color:#fff}.um-admin-boxed-links i{font-size:28px;vertical-align:middle;margin:0 10px 0 0}.um-admin-half{float:left;width:48%}.um-admin-half select{box-sizing:border-box}.um-admin-half.um-admin-right{float:right}.um-admin-half p:first-child{margin-top:0}.um-admin-half .um{opacity:1;margin-bottom:0!important}.um-admin-tri{float:left;width:33%;position:relative}.um-admin-tri[data-select2-id]{position:relative}._heading_text[data-select2-id]{position:relative}.um-admin-error-block,.um-admin-success-block{display:none;width:100%;background:#c74a4a;border-radius:3px;color:#fff;box-sizing:border-box;position:relative;padding:12px;font-size:13px;line-height:1em!important;margin:0 0 16px 0}.um-admin-success-block{background:#7acf58}.um-admin-cur-condition-template{display:none}.dynamic-mce-content{display:none}.um-admin-btns{line-height:1.5em;margin:0 0 5px 0;display:flex;flex-direction:row;justify-content:flex-start;align-items:baseline;flex-wrap:wrap}.um-admin-btns .um-no-custom-fields{margin:0}.um-admin-btns a{margin:0 3px 8px 0!important;font-size:12px!important;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;flex-wrap:nowrap;display:flex!important}.um-admin-btns a.with-icon span{color:#aaa;font-size:15px!important;padding-left:5px;height:auto;width:auto}.um-admin-btns a.with-icon:hover span{color:#cb3838}.um-admin-btn-toggle{padding:10px 0}.um-admin-btn-toggle p{margin:0 5px 0 0!important}.um-admin-btn-toggle p.um-admin-reset-conditions a{margin-bottom:5px}.um-admin-btn-toggle a{text-decoration:none;color:#999}.um-admin-btn-toggle a.um-admin-new-condition{margin-bottom:5px}.um-admin-btn-toggle a.active{color:#0085ba}.um-admin-btn-toggle a.active:hover{color:#0085ba}.um-admin-btn-toggle i{margin:0 5px 0 0!important;height:100%;color:#666;position:relative;top:1px}.um-admin-btn-content{display:none;padding:5px 0 0 0}.um-admin-btn-content p{float:left;margin-right:10px!important;padding:0!important}.um-admin-btn-content p.um-admin-conditions-notice{width:100%;margin:0 0 9px 0!important}.um-admin-btn-content .um-admin-cur-condition:not(:last-child){margin:0 0 5px 0}.um-admin-builder i{font-size:15px!important;height:100%!important;line-height:100%!important;margin:0 5px 0 0!important}.um-admin-builder .um-admin-drag{margin-top:40px}.um-admin-drag-add-field{display:block;width:100%;height:30px;line-height:30px;text-align:center;color:#aaa;text-decoration:none}.um-admin-drag-add-field:hover{color:#0085ba}.um-admin-drag-add-field i{font-size:23px!important;margin:0!important}.um-admin-drag-ctrls{position:absolute;top:-30px;left:0}.um-admin-drag-ctrls.um-admin-drag-ctrls-demo{position:absolute;right:7px;top:-30px;left:auto}.um-admin-drag-ctrls a{float:left;margin:0 5px 0 0;height:30px;line-height:30px;background:#eee;color:#888;padding:0 8px;text-decoration:none;border-radius:2px 2px 0 0}.um-admin-drag-ctrls a:hover{background-color:#e5e5e5}.um-admin-drag-ctrls a.active,.um-admin-drag-ctrls a.active:hover{background-color:#0085ba;color:#fff}.um-admin-drag-ctrls.columns a{width:30px!important;background-image:url(../../img/builder/1-column.gif);background-repeat:no-repeat;background-position:center}.um-admin-drag-ctrls.columns a:nth-child(2){background-image:url(../../img/builder/2-columns.gif)}.um-admin-drag-ctrls.columns a:last-child{background-image:url(../../img/builder/3-columns.gif)}.um-admin-drag-ctrls.columns a.active{background-image:url(../../img/builder/1-column-active.gif)}.um-admin-drag-ctrls.columns a.active:nth-child(2){background-image:url(../../img/builder/2-columns-active.gif)}.um-admin-drag-ctrls.columns a.active:last-child{background-image:url(../../img/builder/3-columns-active.gif)}.um-admin-drag{width:100%;background:#f1f1f1;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;position:relative;padding:60px 20px 20px 20px}.um-admin-drag-row{width:100%;background:#fefefe;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;position:relative;padding:60px 20px 20px 20px;margin:0 0 60px 0}.um-admin-drag-row:last-child{margin-bottom:0}.um-admin-drag-row-icons{position:absolute;right:0;top:-30px;height:30px}.um-admin-drag-row-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-row-icons a:hover{background:#008ec2}.um-admin-drag-row-icons span{cursor:move!important;text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-row-icons span:hover{background:#008ec2}.um-admin-drag-row-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-rowsub{position:relative;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;background:#f1f1f1;padding:20px;margin:0 0 60px 0}.um-admin-drag-rowsub:last-child{margin-bottom:0}.um-admin-drag-rowsub-icons{position:absolute;right:0;top:-30px;height:30px}.um-admin-drag-rowsub-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-rowsub-icons a:hover{background:#008ec2}.um-admin-drag-rowsub-icons span{cursor:move!important;text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-rowsub-icons span:hover{background:#008ec2}.um-admin-drag-rowsub-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-col{float:left;width:100%;background:#fff;border:1px dashed #bbb;box-sizing:border-box;padding:20px}.um-admin-drag-col.cols-3{width:32%}.um-admin-drag-col.cols-3.cols-last{width:32%}.um-admin-drag-col.cols-3.cols-middle{width:32%;margin:0 2%}.um-admin-drag-col.cols-3 .um-admin-drag-fld-title{height:auto;line-height:18px;margin:10px;margin-bottom:0}.um-admin-drag-col.cols-3 .um-admin-drag-fld-type{height:auto;line-height:18px;margin:0 0 10px 0}.um-admin-drag-col.cols-3 .um-admin-drag-fld-banned{height:auto;line-height:18px;margin:0 0 10px 0}.um-admin-drag-col.cols-3 .um-admin-drag-fld-icons{float:none;position:absolute;bottom:0;right:0;height:30px;display:none}.um-admin-drag-col.cols-3 .um-admin-drag-fld-icons.um-field-type-group{float:left;position:relative;bottom:auto;right:auto;height:30px;display:block}.um-admin-drag-col.cols-3 .um-admin-drag-fld:hover .um-admin-drag-fld-icons{display:block}.um-admin-drag-col.cols-1{width:100%}.um-admin-drag-col.cols-1.cols-last{width:100%}.um-admin-drag-col.cols-2{width:49%;margin:0 1% 0 0}.um-admin-drag-col.cols-2.cols-last{width:49%;margin:0 0 0 1%}.um-admin-drag-col>.um-admin-drag-fld:last-of-type{margin-bottom:20px}.um-admin-drag-addrow{text-align:center;font-size:14px;color:#888;cursor:pointer;border:1px dashed #bbb;padding:10px 0;margin:20px 0 0 0}.um-admin-drag-addrow i{font-size:23px!important;margin:0!important}.um-admin-drag-addrow:hover{color:#0085ba}.um-admin-drag-fld{display:block;position:relative;border:1px solid #ddd;margin:0 0 15px 0;background:#f5f5f5;cursor:move!important}.um-admin-drag-fld.um-field-type-group{background:#555;border:0}.um-admin-drag-fld-title{float:left;font-weight:700;font-size:13px;color:#666;height:30px;line-height:30px;margin:0 0 0 20px}.um-admin-drag-fld-title.um-field-type-group{color:#fff}.um-admin-drag-fld-title i{width:24px;display:inline-block;text-align:center}.um-admin-drag-group{background:#fcfcfc;cursor:default;padding:20px;border:1px dashed #bbb}.um-admin-drag-fld-type{float:left;font-size:13px;height:30px;line-height:30px;margin:0 0 0 20px;font-weight:400;color:#999}.um-admin-drag-fld-type.um-field-type-group{color:#fff}.um-admin-drag-fld-banned{float:left;font-size:13px;height:30px;line-height:30px;margin:0 0 0 20px;font-weight:400;color:var(--um-blocks-error-color,#d92d20)}.um-admin-drag-fld-icons{float:right}.um-admin-drag-fld-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-fld-icons a:hover{background:#008ec2}.um-admin-drag-fld-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-fld-icons.um-field-type-group a.um_admin_duplicate_field{display:none!important}.um-row-placeholder{width:100%;border:2px dashed #aaa;box-sizing:border-box}.um-rowsub-placeholder{width:100%;border:2px dashed #ccc;box-sizing:border-box;display:block}.um-fld-placeholder{display:block;border:1px dashed #ddd;background:#fff;box-sizing:border-box;width:100%}.um-admin-modal #UM_preview_form .um-admin-modal-body{position:relative}.um-admin-modal #UM_preview_form .um-admin-modal-body .um-admin-preview-overlay{position:absolute;top:0;bottom:0;left:0;right:0;background-color:rgba(255,255,255,0);z-index:100} \ No newline at end of file diff --git a/assets/css/admin/builder.sass b/assets/css/admin/builder.sass index f1356be4..18621495 100644 --- a/assets/css/admin/builder.sass +++ b/assets/css/admin/builder.sass @@ -400,6 +400,10 @@ height: auto line-height: 18px margin: 0 0 10px 0 + .#{$prefix}admin-drag-fld-banned + height: auto + line-height: 18px + margin: 0 0 10px 0 .#{$prefix}admin-drag-fld-icons float: none position: absolute @@ -492,6 +496,14 @@ &.um-field-type-group color: #fff +.#{$prefix}admin-drag-fld-banned + float: left + font-size: 13px + height: 30px + line-height: 30px + margin: 0 0 0 20px + font-weight: 400 + color: $error-text .#{$prefix}admin-drag-fld-icons float: right diff --git a/assets/css/um-members.css b/assets/css/um-members.css index edecd6cc..076cf73b 100644 --- a/assets/css/um-members.css +++ b/assets/css/um-members.css @@ -582,6 +582,7 @@ body #content .um-directory a:hover, body #content .um-directory a:focus { background-color: #eee; box-sizing: border-box; position: relative; + min-height: 50px; } .um-directory .um-members-wrapper .um-members.um-members-grid .um-member .um-member-cover[data-ratio] { overflow-y: hidden; @@ -1248,4 +1249,4 @@ body #content .um-directory a:hover, body #content .um-directory a:focus { .select2-container .select2-results li.select2-results__option[aria-disabled=true] { display: none; -} \ No newline at end of file +} diff --git a/assets/sass/um-members.sass b/assets/sass/um-members.sass index b7a5c602..01301d33 100644 --- a/assets/sass/um-members.sass +++ b/assets/sass/um-members.sass @@ -435,6 +435,7 @@ body background-color: #eee box-sizing: border-box position: relative + min-height: 50px &[data-ratio] overflow-y: hidden .um-member-cover-e @@ -1005,4 +1006,4 @@ body .select2-container .select2-results li.select2-results__option[aria-disabled=true] - display: none \ No newline at end of file + display: none diff --git a/blocks-src/um-member-directories/src/index.js b/blocks-src/um-member-directories/src/index.js index c5415fff..d7fd056d 100644 --- a/blocks-src/um-member-directories/src/index.js +++ b/blocks-src/um-member-directories/src/index.js @@ -89,7 +89,9 @@ jQuery(window).on( 'load', function($) { jQuery(mutation.addedNodes).find('.um-member').each(function() { let wrapper = document.querySelector('.um-member'); let directory = jQuery(this).parent(); - UM_Member_Grid(directory); + if (directory.hasClass('um-members-grid')) { + UM_Member_Grid(directory); + } if (wrapper) { wrapper.addEventListener('click', (event) => { diff --git a/changelog.txt b/changelog.txt index de589987..020e8c31 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,23 @@ == Changelog == += 2.10.3 April 24, 2025 = + +* Enhancements: + + - Added: The `Ignore the "User Role > Registration Options"` setting. It provides an ability to auto-approve users if they were created via wp-admin > Users screen. + - Tweak: Avoid email notifications to Administrator about user registration via wp-admin > Users screen. + - Tweak: Updated the Action Scheduler implementation to improve flexibility and clarity. Refactor Action Scheduler for not only email handling. + +* Bugfixes: + + - Fixed: Member Directory styles when it's rendered on the Gutenberg builder page. + - Fixed: Member Directory filtering query when the custom users metatable is used. + - Fixed: PHP Warning that occurs when using the `getimagesize` function with an image from an external source. + - Fixed: Reset Password email notification's the {password_reset_link}` placeholder. + - Fixed: Changed "Turkey" to the current official term "Türkiye". + +* Cached and optimized/minified assets(JS/CSS) must be flushed/re-generated after upgrade * + = 2.10.2 April 02, 2025 = * Enhancements: diff --git a/includes/action-scheduler/class-init.php b/includes/action-scheduler/class-init.php index 16b96bc3..27974be6 100644 --- a/includes/action-scheduler/class-init.php +++ b/includes/action-scheduler/class-init.php @@ -43,13 +43,13 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { public function __construct() { if ( ! $this->can_be_active() ) { add_action( 'init', array( $this, 'add_notice' ) ); + add_filter( 'um_site_health_extend', array( $this, 'change_site_health' ) ); } else { add_filter( 'um_settings_structure', array( $this, 'add_setting' ) ); - - if ( UM()->options()->get( 'enable_action_scheduler' ) ) { - $this->enabled = true; - $this->load_library( true ); - } + // Since 2.10.3 we load library as soon as there are required library files. + // Options above are used for enable/disable some Action Scheduler hooks e.g. um_dispatch_email. + $this->enabled = true; + $this->load_library( true ); } } @@ -68,6 +68,18 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { ); } + /** + * Adds the Action Scheduler setting to Ultimate Member feature settings + * + * @param array $settings + * + * @return array + */ + public function change_site_health( $site_health ) { + $site_health['ultimate-member']['fields']['enable_as_email_sending']['value'] = __( 'No', 'ultimate-member' ); + return $site_health; + } + /** * Adds the Action Scheduler setting to Ultimate Member feature settings * @@ -77,11 +89,11 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { */ public function add_setting( $settings ) { $settings['advanced']['sections']['features']['form_sections']['features']['fields'][] = array( - 'id' => 'enable_action_scheduler', + 'id' => 'enable_as_email_sending', 'type' => 'checkbox', - 'label' => __( 'Action Scheduler', 'ultimate-member' ), - 'checkbox_label' => __( 'Enable Action Scheduler', 'ultimate-member' ), - 'description' => __( 'Check this box if you want to use the Ultimate Member action scheduler. By enabling it, certain tasks like sending system emails will be scheduled to run at optimal times, which can help reduce the load on your server', 'ultimate-member' ), + 'label' => __( 'Email sending by Action Scheduler', 'ultimate-member' ), + 'checkbox_label' => __( 'Enable Action Scheduler for Ultimate Member emails sending', 'ultimate-member' ), + 'description' => __( 'Check this box if you want to use the Action Scheduler for Ultimate Member emails sending. By enabling it, sending system emails will be scheduled to run at optimal times, which can help reduce the load on your server', 'ultimate-member' ), ); return $settings; @@ -131,6 +143,10 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { return $this->verify_wc_action_scheduler() || $this->load_library(); } + public function is_hook_enabled( $hook ) { + return apply_filters( 'um_action_scheduler_is_hook_enabled', $this->is_enabled(), $hook ); + } + /** * Enqueue an action to run one time, as soon as possible. * If Action Scheduler is disabled then do_action_ref_array is called to run action right away. @@ -144,11 +160,12 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { * @return int Еhe action’s ID. Zero if there was an error scheduling the action. The error will be sent to error_log */ public function enqueue_async_action( $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { - if ( $this->enabled ) { + if ( $this->is_hook_enabled( $hook ) ) { $group = $this->set_group( $group ); return as_enqueue_async_action( $hook, $args, $group, $unique, $priority ); } + // Make then standard action without Action Scheduler. do_action_ref_array( $hook, $args ); return 0; } @@ -166,11 +183,12 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { * @return int The action’s ID. Zero if there was an error scheduling the action. The error will be sent to error_log. */ public function schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { - if ( $this->enabled ) { + if ( $this->is_hook_enabled( $hook ) ) { $group = $this->set_group( $group ); return as_schedule_single_action( $timestamp, $hook, $args, $group, $unique, $priority ); } + // Make then standard action without Action Scheduler. do_action_ref_array( $hook, $args ); return 0; } diff --git a/includes/admin/class-enqueue.php b/includes/admin/class-enqueue.php index 05c43246..51a53193 100644 --- a/includes/admin/class-enqueue.php +++ b/includes/admin/class-enqueue.php @@ -198,6 +198,7 @@ final class Enqueue extends \um\common\Enqueue { wp_register_style( 'um_responsive', $css_url . 'um-responsive' . $suffix . '.css', array( 'um_profile' ), UM_VERSION ); wp_register_style( 'um_account', $css_url . 'um-account' . $suffix . '.css', array(), UM_VERSION ); wp_register_style( 'um_default_css', $css_url . 'um-old-default' . $suffix . '.css', array(), UM_VERSION ); + wp_register_style( 'um_misc', $css_url . 'um-misc' . $suffix . '.css', array( 'um_styles' ), UM_VERSION ); wp_register_style( 'um_datetime', $libs_url . 'pickadate/default' . $suffix . '.css', array(), '3.6.2' ); wp_register_style( 'um_datetime_date', $libs_url . 'pickadate/default.date' . $suffix . '.css', array( 'um_datetime' ), '3.6.2' ); @@ -209,6 +210,7 @@ final class Enqueue extends \um\common\Enqueue { wp_enqueue_style( 'um_profile' ); wp_enqueue_style( 'um_responsive' ); wp_enqueue_style( 'um_account' ); + wp_enqueue_style( 'um_misc' ); wp_enqueue_style( 'um_datetime_date' ); wp_enqueue_style( 'um_datetime_time' ); diff --git a/includes/admin/class-site-health.php b/includes/admin/class-site-health.php index bc1a3558..45ceec2d 100644 --- a/includes/admin/class-site-health.php +++ b/includes/admin/class-site-health.php @@ -1,6 +1,8 @@ esc_html__( 'Are the banned custom fields?', 'ultimate-member' ), + 'test' => array( $this, 'banned_fields_test' ), + ); + break; + } + } + } + return $tests; } @@ -210,6 +226,132 @@ class Site_Health { return $result; } + private static function field_is_banned( $metakey ) { + return UM()->user()->is_metakey_banned( $metakey ) || in_array( strtolower( $metakey ), UM()->builtin()->blacklist_fields, true ); + } + + public function get_banned_fields() { + $result = array( + 'description' => '', + 'actions' => '', + ); + + $forms = get_posts( + array( + 'post_type' => 'um_form', + 'posts_per_page' => -1, + 'fields' => 'ids', + ) + ); + + $forms_count = 0; + $break_forms = array(); + if ( ! empty( $forms ) ) { + foreach ( $forms as $form_id ) { + $fields = UM()->query()->get_attr( 'custom_fields', $form_id ); + if ( empty( $fields ) ) { + continue; + } + foreach ( $fields as $field ) { + if ( empty( $field['metakey'] ) ) { + continue; + } + + if ( self::field_is_banned( $field['metakey'] ) ) { + if ( ! array_key_exists( $form_id, $break_forms ) ) { + $break_forms[ $form_id ] = array( + 'title' => get_the_title( $form_id ), + 'link' => get_edit_post_link( $form_id ), + 'fields' => array( + $field['metakey'] => isset( $field['title'] ) ? $field['title'] : __( 'Unknown title', 'ultimate-member' ), + ), + ); + + ++$forms_count; + } else { + $break_forms[ $form_id ]['fields'][ $field['metakey'] ] = isset( $field['title'] ) ? $field['title'] : __( 'Unknown title', 'ultimate-member' ); + } + } + } + } + } + + if ( 0 < $forms_count ) { + $result['description'] .= sprintf( + '

%s

', + __( 'Please note that some fields in your Ultimate Member Forms are currently on a restricted list that disallows their use. This is particularly related to the Ultimate Member Forms and their fields below.', 'ultimate-member' ) + ); + + if ( ! empty( $break_forms ) ) { + $forms_description = array(); + foreach ( $break_forms as $form_id => $form_data ) { + $fields = array(); + foreach ( $form_data['fields'] as $metakey => $field_title ) { + $fields[] = sprintf( __( '%s (%s)', 'ultimate-member' ), $field_title, $metakey ); + } + + $forms_description[] = '

' . sprintf( + __( 'Fields in %s (#ID: %s):', 'ultimate-member' ), + esc_url( $form_data['link'] ), + esc_html( $form_data['title'] ), + esc_html( $form_id ) + ) . '

'; + } + + $result['description'] .= implode( ' ', $forms_description ); + } + + $result['actions'] .= sprintf( + '

%s

', + admin_url( 'edit.php?post_type=um_form' ), + esc_html__( 'Edit form fields and update', 'ultimate-member' ) + ); + } + + $result = apply_filters( 'um_get_banned_fields_result', $result ); + + if ( ! empty( $result['description'] ) ) { + $result['description'] .= sprintf( + '

%s

', + __( 'The using meta keys from restricted list in Ultimate Member Forms may break the website\'s functionality and is unsecure.', 'ultimate-member' ) + ); + } + + if ( ! empty( $result['description'] ) && ! empty( $result['actions'] ) ) { + return $result; + } + + return false; + } + + public function banned_fields_test() { + $result = array( + 'label' => __( 'You have correct Ultimate Member fields', 'ultimate-member' ), + 'status' => 'good', + 'badge' => array( + 'label' => UM_PLUGIN_NAME, + 'color' => self::BADGE_COLOR, + ), + 'description' => sprintf( + '

%s

', + __( 'Your all custom Ultimate Member fields are correct.', 'ultimate-member' ) + ), + 'actions' => '', + 'test' => 'um_banned_fields', + ); + + $banned_fields = $this->get_banned_fields(); + if ( false !== $banned_fields ) { + $result['label'] = __( 'Some field from Ultimate Member forms has banned meta key', 'ultimate-member' ); + $result['status'] = 'critical'; + $result['badge']['color'] = 'red'; + $result['description'] = $banned_fields['description']; + $result['actions'] = $banned_fields['actions']; + } + + return $result; + } + private function get_roles() { return UM()->roles()->get_roles(); } @@ -240,7 +382,7 @@ class Site_Health { } private function get_field_data( $info, $key, $field_key, $field ) { - $row = isset( $field['metakey'] ) ? false : true; + $row = array_key_exists( 'type', $field ) && 'row' === $field['type']; $title = $row ? __( 'Row: ', 'ultimate-member' ) . $field['id'] : __( 'Field: ', 'ultimate-member' ) . $field['metakey']; $field = array_map( array( &$this, 'array_map' ), $field ); $field_info = array( @@ -254,7 +396,7 @@ class Site_Health { } private function get_member_directories() { - $query = new \WP_Query(); + $query = new WP_Query(); $member_directories = $query->query( array( 'post_type' => 'um_directory', @@ -372,6 +514,10 @@ class Site_Health { 'label' => __( 'Use Gravatars?', 'ultimate-member' ), 'value' => UM()->options()->get( 'use_gravatars' ) ? $labels['yes'] : $labels['no'], ), + 'um-admin_ignore_user_status' => array( + 'label' => __( 'Ignore the "User Role > Registration Options" if this user is added from the wp-admin dashboard', 'ultimate-member' ), + 'value' => UM()->options()->get( 'admin_ignore_user_status' ) ? $labels['yes'] : $labels['no'], + ), 'um-delete_comments' => array( 'label' => __( 'Deleting user comments after deleting a user', 'ultimate-member' ), 'value' => UM()->options()->get( 'delete_comments' ) ? $labels['yes'] : $labels['no'], @@ -1080,6 +1226,10 @@ class Site_Health { 'label' => __( 'Enable Gutenberg Blocks', 'ultimate-member' ), 'value' => UM()->options()->get( 'enable_blocks' ) ? $labels['yes'] : $labels['no'], ), + 'um-enable_as_email_sending' => array( + 'label' => __( 'Email sending by Action Scheduler', 'ultimate-member' ), + 'value' => UM()->options()->get( 'enable_as_email_sending' ) ? $labels['yes'] : $labels['no'], + ), 'um-rest_api_version' => array( 'label' => __( 'REST API version', 'ultimate-member' ), 'value' => UM()->options()->get( 'rest_api_version' ), @@ -1627,10 +1777,8 @@ class Site_Health { ), ) ); - } - if ( array_key_exists( '_um_login_redirect_url', $rolemeta ) && 'redirect_url' === $rolemeta['_um_login_redirect_url'] ) { - if ( array_key_exists( '_um_pending_url', $rolemeta ) ) { + if ( 'redirect_url' === $rolemeta['_um_after_login'] && array_key_exists( '_um_login_redirect_url', $rolemeta ) ) { $info[ 'ultimate-member-' . $key ]['fields'] = array_merge( $info[ 'ultimate-member-' . $key ]['fields'], array( @@ -1660,10 +1808,8 @@ class Site_Health { ), ) ); - } - if ( 'redirect_url' === $rolemeta['_um_after_logout'] ) { - if ( array_key_exists( '_um_logout_redirect_url', $rolemeta ) ) { + if ( 'redirect_url' === $rolemeta['_um_after_logout'] && array_key_exists( '_um_logout_redirect_url', $rolemeta ) ) { $info[ 'ultimate-member-' . $key ]['fields'] = array_merge( $info[ 'ultimate-member-' . $key ]['fields'], array( @@ -1689,10 +1835,8 @@ class Site_Health { ), ) ); - } - if ( 'redirect_url' === $rolemeta['_um_after_delete'] ) { - if ( array_key_exists( '_um_delete_redirect_url', $rolemeta ) ) { + if ( 'redirect_url' === $rolemeta['_um_after_delete'] && array_key_exists( '_um_delete_redirect_url', $rolemeta ) ) { $info[ 'ultimate-member-' . $key ]['fields'] = array_merge( $info[ 'ultimate-member-' . $key ]['fields'], array( @@ -1706,17 +1850,15 @@ class Site_Health { } if ( ! empty( $rolemeta['wp_capabilities'] ) ) { - if ( array_key_exists( 'wp_capabilities', $rolemeta ) ) { - $info[ 'ultimate-member-' . $key ]['fields'] = array_merge( - $info[ 'ultimate-member-' . $key ]['fields'], - array( - 'um-wp_capabilities' => array( - 'label' => __( 'WP Capabilities', 'ultimate-member' ), - 'value' => $rolemeta['wp_capabilities'], - ), - ) - ); - } + $info[ 'ultimate-member-' . $key ]['fields'] = array_merge( + $info[ 'ultimate-member-' . $key ]['fields'], + array( + 'um-wp_capabilities' => array( + 'label' => __( 'WP Capabilities', 'ultimate-member' ), + 'value' => $rolemeta['wp_capabilities'], + ), + ) + ); } $info = apply_filters( 'um_debug_information_user_role', $info, $key ); @@ -2160,11 +2302,7 @@ class Site_Health { ) ); } else { - if ( isset( $options[ $field ] ) ) { - $sortby_label = $options[ $field ]; - } else { - $sortby_label = $field; - } + $sortby_label = isset( $options[ $field ] ) ? $options[ $field ] : $field; $info[ 'ultimate-member-directory-' . $key ]['fields'] = array_merge( $info[ 'ultimate-member-directory-' . $key ]['fields'], array( @@ -2419,6 +2557,17 @@ class Site_Health { } } - return $info; + /** + * Filters the site health information. + * + * @hook um_site_health_extend + * + * @since 2.10.3 + * + * @param {array} $info The site health info to be filtered. + * + * @return {array} The filtered site health info. + */ + return apply_filters( 'um_site_health_extend', $info ); } } diff --git a/includes/admin/class-users-columns.php b/includes/admin/class-users-columns.php index 81165829..5ad62275 100644 --- a/includes/admin/class-users-columns.php +++ b/includes/admin/class-users-columns.php @@ -350,9 +350,6 @@ if ( ! class_exists( 'um\admin\Users_Columns' ) ) { return; } - // Set default statuses if not already done. - UM()->setup()->set_default_user_status(); - $id = 'um_user_status'; // need to add there additional nonce field because WordPress native _wpnonce field isn't visible on the users.php screen then custom actions diff --git a/includes/admin/core/class-admin-builder.php b/includes/admin/core/class-admin-builder.php index e0b49050..2a433c9f 100644 --- a/includes/admin/core/class-admin-builder.php +++ b/includes/admin/core/class-admin-builder.php @@ -546,6 +546,9 @@ if ( ! class_exists( 'um\admin\core\Admin_Builder' ) ) {
+ user()->is_metakey_banned( $key ) || in_array( strtolower( $key ), UM()->builtin()->blacklist_fields, true ) ) { ?> +
+
diff --git a/includes/admin/core/class-admin-notices.php b/includes/admin/core/class-admin-notices.php index 0fbc12ab..be73e0f4 100644 --- a/includes/admin/core/class-admin-notices.php +++ b/includes/admin/core/class-admin-notices.php @@ -47,6 +47,8 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { $this->lock_registration(); + $this->empty_status_users(); + $this->extensions_page(); $this->child_theme_required(); @@ -204,17 +206,17 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { ob_start(); ?> -
- +
+
- array( + 'href' => array(), + ), + 'strong' => array(), + ); + + $this->add_notice( + 'empty_status_users', + array( + 'class' => 'info', + // translators: %1$d: Background update for users is complete; %2$d: Total users for background update. + 'message' => '

' . wp_kses( sprintf( __( 'Background process is running: Setting user statuses %1$d/%2$d.', 'ultimate-member' ), $empty_status_users[0], $empty_status_users[1] ), $allowed_html ) . '

', + 'dismissible' => false, + ) + ); + } + /** * Checking if the "Membership - Anyone can register" WordPress general setting is active */ @@ -256,11 +289,11 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { ?>

- All Access Pass – Get access to all Ultimate Member extensions at a significant discount with our All Access Pass.', 'ultimate-member' ) ?> + All Access Pass – Get access to all Ultimate Member extensions at a significant discount with our All Access Pass.', 'ultimate-member' ); ?>

- +

@@ -290,7 +323,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { $path = str_replace( '//', '/', $path ); if ( ! file_exists( $path ) ) { - $old = umask(0); + $old = umask( 0 ); @mkdir( $path, 0777, true ); umask( $old ); } @@ -326,9 +359,12 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { 'woocommerce', ); - $slugs = array_map( function( $item ) { - return 'um-' . $item . '/um-' . $item . '.php'; - }, $old_extensions ); + $slugs = array_map( + function ( $item ) { + return 'um-' . $item . '/um-' . $item . '.php'; + }, + $old_extensions + ); $active_plugins = UM()->dependencies()->get_active_plugins(); foreach ( $slugs as $slug ) { @@ -622,7 +658,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { $arr_inactive_license_keys[] = $license->item_name; } - $invalid_license++; + ++$invalid_license; } if ( ! empty( $arr_inactive_license_keys ) ) { @@ -676,34 +712,41 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {

- +  

- add_notice( 'upgrade', array( - 'class' => 'error', - 'message' => $message, - ), 4 ); - } else { - if ( isset( $_GET['msg'] ) && 'updated' === sanitize_key( $_GET['msg'] ) ) { - if ( isset( $_GET['page'] ) && 'um_options' === sanitize_key( $_GET['page'] ) ) { - $this->add_notice( 'settings_upgrade', array( - 'class' => 'updated', - 'message' => '

' . __( 'Settings successfully upgraded', 'ultimate-member' ) . '

', - ), 4 ); - } else { - $this->add_notice( - 'upgrade', - array( - 'class' => 'updated', - // translators: %1$s is a plugin name title; %2$s is a plugin version. - 'message' => '

' . sprintf( __( '%1$s %2$s Successfully Upgraded', 'ultimate-member' ), UM_PLUGIN_NAME, UM_VERSION ) . '

', - ), - 4 - ); - } + $this->add_notice( + 'upgrade', + array( + 'class' => 'error', + 'message' => $message, + ), + 4 + ); + } elseif ( isset( $_GET['msg'] ) && 'updated' === sanitize_key( $_GET['msg'] ) ) { + if ( isset( $_GET['page'] ) && 'um_options' === sanitize_key( $_GET['page'] ) ) { + $this->add_notice( + 'settings_upgrade', + array( + 'class' => 'updated', + 'message' => '

' . __( 'Settings successfully upgraded', 'ultimate-member' ) . '

', + ), + 4 + ); + } else { + $this->add_notice( + 'upgrade', + array( + 'class' => 'updated', + // translators: %1$s is a plugin name title; %2$s is a plugin version. + 'message' => '

' . sprintf( __( '%1$s %2$s Successfully Upgraded', 'ultimate-member' ), UM_PLUGIN_NAME, UM_VERSION ) . '

', + ), + 4 + ); } } } @@ -734,18 +777,18 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { ?>

-  |  -  |  - +  |  +  |  +

- +

- +

@@ -754,7 +797,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {

- +

@@ -763,17 +806,22 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {

- +

- add_notice( 'reviews_notice', array( - 'class' => 'updated', - 'message' => $message, - 'dismissible' => true - ), 1 ); + $this->add_notice( + 'reviews_notice', + array( + 'class' => 'updated', + 'message' => $message, + 'dismissible' => true, + ), + 1 + ); } @@ -782,7 +830,8 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { */ function future_changed() { - ob_start(); ?> + ob_start(); + ?>

- add_notice( 'future_changes', array( - 'class' => 'updated', - 'message' => $message, - ), 2 ); + $this->add_notice( + 'future_changes', + array( + 'class' => 'updated', + 'message' => $message, + ), + 2 + ); } /** diff --git a/includes/admin/core/class-admin-settings.php b/includes/admin/core/class-admin-settings.php index deb4f727..bdef709a 100644 --- a/includes/admin/core/class-admin-settings.php +++ b/includes/admin/core/class-admin-settings.php @@ -100,19 +100,20 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { $sites = get_sites( array( 'fields' => 'ids' ) ); foreach ( $sites as $blog_id ) { $metakeys[] = $wpdb->get_blog_prefix( $blog_id ) . 'capabilities'; - $metakeys[] = 'wc_money_spent_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); - $metakeys[] = 'wc_order_count_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); + $metakeys[] = 'wc_money_spent_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); // Is used since Woocommerce 9.1.0 + $metakeys[] = 'wc_order_count_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); // Is used since Woocommerce 9.1.0 TODO remove as soon as used 'um_wc_order_count_' } } else { $blog_id = get_current_blog_id(); $metakeys[] = $wpdb->get_blog_prefix( $blog_id ) . 'capabilities'; - $metakeys[] = 'wc_money_spent_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); - $metakeys[] = 'wc_order_count_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); + $metakeys[] = 'wc_money_spent_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); // Is used since Woocommerce 9.1.0 + $metakeys[] = 'wc_order_count_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); // Is used since Woocommerce 9.1.0 TODO remove as soon as used 'um_wc_order_count_' } - //member directory data + // Member directory data $metakeys[] = 'um_member_directory_data'; $metakeys[] = '_um_verified'; + $metakeys[] = '_money_spent'; // Legacy since Woocommerce 9.1.0. TODO remove as soon as stop support Woo below 9.1.0 version $metakeys[] = '_completed'; $metakeys[] = '_reviews_avg'; @@ -752,6 +753,9 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { 'use_um_gravatar_default_image' => array( 'sanitize' => 'bool', ), + 'admin_ignore_user_status' => array( + 'sanitize' => 'bool', + ), 'delete_comments' => array( 'sanitize' => 'bool', ), @@ -1010,7 +1014,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { 'enable_blocks' => array( 'sanitize' => 'bool', ), - 'enable_action_scheduler' => array( + 'enable_as_email_sending' => array( 'sanitize' => 'bool', ), 'rest_api_version' => array( @@ -1181,6 +1185,13 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { 'description' => __( 'Do you want to use the plugin default avatar instead of the gravatar default photo (If the user did not upload a custom profile photo/avatar).', 'ultimate-member' ), 'conditional' => array( 'use_um_gravatar_default_builtin_image', '=', 'default' ), ), + array( + 'id' => 'admin_ignore_user_status', + 'type' => 'checkbox', + 'label' => __( 'Ignore the "User Role > Registration Options"', 'ultimate-member' ), + 'checkbox_label' => __( 'Automatically approve users from the wp-admin dashboard', 'ultimate-member' ), + 'description' => __( 'Ignore registration settings and automatically approve the user if this user is added from the wp-admin dashboard.', 'ultimate-member' ), + ), array( 'id' => 'delete_comments', 'type' => 'checkbox', diff --git a/includes/blocks/um-member-directories/src/index.asset.php b/includes/blocks/um-member-directories/src/index.asset.php index 7ac488fc..3b988c76 100644 --- a/includes/blocks/um-member-directories/src/index.asset.php +++ b/includes/blocks/um-member-directories/src/index.asset.php @@ -1 +1 @@ - array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-server-side-render'), 'version' => 'cced871823424b7be338'); + array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-server-side-render'), 'version' => 'b29cf253331968aa1d87'); diff --git a/includes/blocks/um-member-directories/src/index.js b/includes/blocks/um-member-directories/src/index.js index 38fffc0c..4b7cc8fd 100644 --- a/includes/blocks/um-member-directories/src/index.js +++ b/includes/blocks/um-member-directories/src/index.js @@ -1 +1 @@ -(()=>{"use strict";var e={n:t=>{var r=t&&t.__esModule?()=>t.default:()=>t;return e.d(r,{a:r}),r},d:(t,r)=>{for(var i in r)e.o(r,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:r[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.wp.data,r=window.wp.components,i=window.wp.blockEditor,n=window.wp.serverSideRender;var o=e.n(n);const l=window.wp.blocks,a=window.wp.element,c=window.ReactJSXRuntime;(0,l.registerBlockType)("um-block/um-member-directories",{edit:function(e){const n=(0,i.useBlockProps)(),{attributes:l,setAttributes:d}=e,{member_id:u}=l,m=(0,t.useSelect)((e=>e("core").getEntityRecords("postType","um_directory",{per_page:-1,_fields:["id","title"]})),[]),s=(0,a.useMemo)((()=>m?0===m.length?[{label:wp.i18n.__("No posts found.","ultimate-member"),value:""}]:[{label:wp.i18n.__("Select Directory","ultimate-member"),value:""}].concat(m.map((e=>({label:e.title.rendered,value:e.id})))):[{label:wp.i18n.__("Loading...","ultimate-member"),value:""}]),[m]);return m?(0,c.jsxs)("div",{...n,children:[(0,c.jsx)(o(),{block:"um-block/um-member-directories",attributes:l}),(0,c.jsx)(i.InspectorControls,{children:(0,c.jsx)(r.PanelBody,{title:wp.i18n.__("Select Directories","ultimate-member"),children:(0,c.jsx)(r.SelectControl,{label:wp.i18n.__("Select Directories","ultimate-member"),className:"um_select_directory",value:u,options:s,onChange:e=>d({member_id:e})})})})]}):(0,c.jsxs)("p",{children:[(0,c.jsx)(r.Spinner,{}),wp.i18n.__("Loading...","ultimate-member")]})},save:()=>null}),jQuery(window).on("load",(function(e){new MutationObserver((function(e){e.forEach((function(e){jQuery(e.addedNodes).find(".um.um-directory").each((function(){let e=document.querySelector(".um-directory"),t=jQuery(this);um_ajax_get_members(t),um_slider_filter_init(t),um_datepicker_filter_init(t),um_timepicker_filter_init(t),e&&e.addEventListener("click",(t=>{t.target!==e&&(t.preventDefault(),t.stopPropagation())}))})),jQuery(e.addedNodes).find(".um-member").each((function(){let e=document.querySelector(".um-member"),t=jQuery(this).parent();UM_Member_Grid(t),e&&e.addEventListener("click",(t=>{t.target!==e&&(t.preventDefault(),t.stopPropagation())}))}))}))})).observe(document,{attributes:!1,childList:!0,characterData:!1,subtree:!0})}))})(); \ No newline at end of file +(()=>{"use strict";var e={n:t=>{var r=t&&t.__esModule?()=>t.default:()=>t;return e.d(r,{a:r}),r},d:(t,r)=>{for(var i in r)e.o(r,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:r[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.wp.data,r=window.wp.components,i=window.wp.blockEditor,n=window.wp.serverSideRender;var o=e.n(n);const l=window.wp.blocks,a=window.wp.element,c=window.ReactJSXRuntime;(0,l.registerBlockType)("um-block/um-member-directories",{edit:function(e){const n=(0,i.useBlockProps)(),{attributes:l,setAttributes:d}=e,{member_id:s}=l,u=(0,t.useSelect)((e=>e("core").getEntityRecords("postType","um_directory",{per_page:-1,_fields:["id","title"]})),[]),m=(0,a.useMemo)((()=>u?0===u.length?[{label:wp.i18n.__("No posts found.","ultimate-member"),value:""}]:[{label:wp.i18n.__("Select Directory","ultimate-member"),value:""}].concat(u.map((e=>({label:e.title.rendered,value:e.id})))):[{label:wp.i18n.__("Loading...","ultimate-member"),value:""}]),[u]);return u?(0,c.jsxs)("div",{...n,children:[(0,c.jsx)(o(),{block:"um-block/um-member-directories",attributes:l}),(0,c.jsx)(i.InspectorControls,{children:(0,c.jsx)(r.PanelBody,{title:wp.i18n.__("Select Directories","ultimate-member"),children:(0,c.jsx)(r.SelectControl,{label:wp.i18n.__("Select Directories","ultimate-member"),className:"um_select_directory",value:s,options:m,onChange:e=>d({member_id:e})})})})]}):(0,c.jsxs)("p",{children:[(0,c.jsx)(r.Spinner,{}),wp.i18n.__("Loading...","ultimate-member")]})},save:()=>null}),jQuery(window).on("load",(function(e){new MutationObserver((function(e){e.forEach((function(e){jQuery(e.addedNodes).find(".um.um-directory").each((function(){let e=document.querySelector(".um-directory"),t=jQuery(this);um_ajax_get_members(t),um_slider_filter_init(t),um_datepicker_filter_init(t),um_timepicker_filter_init(t),e&&e.addEventListener("click",(t=>{t.target!==e&&(t.preventDefault(),t.stopPropagation())}))})),jQuery(e.addedNodes).find(".um-member").each((function(){let e=document.querySelector(".um-member"),t=jQuery(this).parent();t.hasClass("um-members-grid")&&UM_Member_Grid(t),e&&e.addEventListener("click",(t=>{t.target!==e&&(t.preventDefault(),t.stopPropagation())}))}))}))})).observe(document,{attributes:!1,childList:!0,characterData:!1,subtree:!0})}))})(); \ No newline at end of file diff --git a/includes/class-config.php b/includes/class-config.php index 0a3e9fbb..f77a229f 100644 --- a/includes/class-config.php +++ b/includes/class-config.php @@ -658,6 +658,7 @@ if ( ! class_exists( 'um\Config' ) ) { 'use_gravatars' => false, 'use_um_gravatar_default_builtin_image' => 'default', 'use_um_gravatar_default_image' => false, + 'admin_ignore_user_status' => false, 'toggle_password' => false, 'require_strongpass' => false, 'password_min_chars' => 8, @@ -733,7 +734,7 @@ if ( ! class_exists( 'um\Config' ) ) { 'secure_notify_admins_banned_accounts__interval' => 'instant', 'secure_allowed_redirect_hosts' => '', 'delete_comments' => false, - 'enable_action_scheduler' => false, + 'enable_as_email_sending' => UM()->options()->get( 'enable_action_scheduler' ), // Use legacy option value by default. It helps during update to set the same value. The last version when we used 'enable_action_scheduler' is 2.10.2 ); add_filter( 'um_get_tabs_from_config', '__return_true' ); diff --git a/includes/class-dependencies.php b/includes/class-dependencies.php index 51e142a9..cc02622c 100644 --- a/includes/class-dependencies.php +++ b/includes/class-dependencies.php @@ -180,27 +180,41 @@ if ( ! class_exists( 'um\Dependencies' ) ) { /** * Compare UM core and extension versions * - * @param string $um_required_ver + * @param string $um_required_ver UM core required version. * @param string $ext_ver * @param string $ext_key * @param string $ext_title + * @param bool $raw False by default. Then return message. Use `true` in conditions inside `plugins_loaded` hook * @return bool */ - public function compare_versions( $um_required_ver, $ext_ver, $ext_key, $ext_title ) { - - if ( version_compare( UM_VERSION, $um_required_ver, '<' ) - || empty( $this->ext_required_version[$ext_key] ) - || version_compare( $this->ext_required_version[$ext_key], $ext_ver, '>' ) ) { + public function compare_versions( $um_required_ver, $ext_ver, $ext_key, $ext_title, $raw = false ) { + if ( empty( $this->ext_required_version[ $ext_key ] ) || + version_compare( UM_VERSION, $um_required_ver, '<' ) || + version_compare( $this->ext_required_version[ $ext_key ], $ext_ver, '>' ) ) { $message = ''; if ( version_compare( UM_VERSION, $um_required_ver, '<' ) ) { - // translators: %1$s is a extension name; %2$s is a plugin name; %3$s is a required version. - $message = sprintf( __( 'This version of "%1$s" requires the core %2$s plugin to be %3$s or higher.', 'ultimate-member' ), $ext_title, UM_PLUGIN_NAME, $um_required_ver ) . - '
' . - // translators: %s: plugin name. - sprintf( __( 'Please update %s to the latest version.', 'ultimate-member' ), UM_PLUGIN_NAME ); + if ( $raw ) { + return false; + } + if ( UM()->is_new_ui() ) { + // translators: %1$s is an extension name; %2$s is a plugin name; %3$s is a required version. + $message = sprintf( __( 'When new UI is enabled this version of "%1$s" requires the core %2$s plugin to be %3$s or higher.', 'ultimate-member' ), $ext_title, UM_PLUGIN_NAME, $um_required_ver ) . + '
' . + // translators: %s: plugin name. + sprintf( __( 'Please update %s to the latest version or disable new UI.', 'ultimate-member' ), UM_PLUGIN_NAME ); + } else { + // translators: %1$s is an extension name; %2$s is a plugin name; %3$s is a required version. + $message = sprintf( __( 'This version of "%1$s" requires the core %2$s plugin to be %3$s or higher.', 'ultimate-member' ), $ext_title, UM_PLUGIN_NAME, $um_required_ver ) . + '
' . + // translators: %s: plugin name. + sprintf( __( 'Please update %s to the latest version.', 'ultimate-member' ), UM_PLUGIN_NAME ); + } } elseif ( empty( $this->ext_required_version[ $ext_key ] ) || version_compare( $this->ext_required_version[ $ext_key ], $ext_ver, '>' ) ) { - // translators: %1$s is a plugin name; %2$s is a extension name; %3$s is a extension version. + if ( $raw ) { + return false; + } + // translators: %1$s is a plugin name; %2$s is an extension name; %3$s is an extension version. $message = sprintf( __( 'Sorry, but this version of %1$s does not work with extension "%2$s" %3$s version.', 'ultimate-member' ), UM_PLUGIN_NAME, $ext_title, $ext_ver ) . '
' . // translators: %s: extension name. @@ -208,19 +222,24 @@ if ( ! class_exists( 'um\Dependencies' ) ) { } return $message; - } else { - //check correct folder name for extensions - if ( ! self::$active_plugins ) self::init(); + } - if ( ! in_array( "um-{$ext_key}/um-{$ext_key}.php", self::$active_plugins ) && ! array_key_exists( "um-{$ext_key}/um-{$ext_key}.php", self::$active_plugins ) ) { - // translators: %1$s is a extension name; %2$s is a extension version. - $message = sprintf( __( 'Please check "%1$s" %2$s extension\'s folder name.', 'ultimate-member' ), $ext_title, $ext_ver ) . - '
' . - // translators: %s: extension name. - sprintf( __( 'Correct folder name is "%s"', 'ultimate-member' ), "um-{$ext_key}" ); + // Check correct folder name for extensions. + if ( ! self::$active_plugins ) { + self::init(); + } - return $message; + if ( ! array_key_exists( "um-{$ext_key}/um-{$ext_key}.php", self::$active_plugins ) && + ! in_array( "um-{$ext_key}/um-{$ext_key}.php", self::$active_plugins, true ) ) { + if ( $raw ) { + return false; } + + // translators: %1$s is an extension name; %2$s is an extension version. + return sprintf( __( 'Please check "%1$s" %2$s extension\'s folder name.', 'ultimate-member' ), $ext_title, $ext_ver ) . + '
' . + // translators: %s: extension name. + sprintf( __( 'Correct folder name is "%s"', 'ultimate-member' ), "um-{$ext_key}" ); } return true; diff --git a/includes/common/actions/class-emails.php b/includes/common/actions/class-emails.php index 57991450..f4c485be 100644 --- a/includes/common/actions/class-emails.php +++ b/includes/common/actions/class-emails.php @@ -17,10 +17,18 @@ if ( ! class_exists( 'um\common\actions\Emails' ) ) { class Emails { public function __construct() { + add_filter( 'um_action_scheduler_is_hook_enabled', array( $this, 'is_enabled' ), 10, 2 ); add_action( 'um_dispatch_email', array( $this, 'send' ), 10, 3 ); add_action( 'um_before_email_notification_sending', array( $this, 'before_email_send' ), 10, 2 ); } + public function is_enabled( $is_enabled, $hook ) { + if ( 'um_dispatch_email' === $hook ) { + $is_enabled = UM()->options()->get( 'enable_as_email_sending' ); + } + return $is_enabled; + } + /** * Send an email * @@ -36,7 +44,7 @@ if ( ! class_exists( 'um\common\actions\Emails' ) ) { // @todo Workaround for now. Maybe we need to put base $user_id everytime if ( array_key_exists( 'fetch_user_id', $args ) ) { // When Action Scheduler is enabled, email sending script is located out of basic functionality, so we need to fetch the user for replace placeholders. - if ( UM()->maybe_action_scheduler()->is_enabled() ) { + if ( UM()->maybe_action_scheduler()->is_hook_enabled( 'um_dispatch_email' ) ) { um_fetch_user( $args['fetch_user_id'] ); } unset( $args['fetch_user_id'] ); @@ -56,7 +64,7 @@ if ( ! class_exists( 'um\common\actions\Emails' ) ) { * @return void */ public function before_email_send( $email, $template ) { - if ( ! UM()->maybe_action_scheduler()->is_enabled() ) { + if ( ! UM()->maybe_action_scheduler()->is_hook_enabled( 'um_dispatch_email' ) ) { return; } diff --git a/includes/common/actions/class-users.php b/includes/common/actions/class-users.php new file mode 100644 index 00000000..e3309b6a --- /dev/null +++ b/includes/common/actions/class-users.php @@ -0,0 +1,142 @@ +maybe_action_scheduler()->next_scheduled_action( self::SCHEDULE_ACTION ) ) { + return; + } + + UM()->maybe_action_scheduler()->schedule_recurring_action( + time() + 60, + self::INTERVAL, + self::SCHEDULE_ACTION + ); + } + + public function status_check() { + $total_users = UM()->common()->users()::get_empty_status_users(); + if ( empty( $total_users ) ) { + return; + } + + UM()->maybe_action_scheduler()->enqueue_async_action( + self::BATCH_ACTION, + array( + 'page' => 1, + 'total' => $total_users, + 'pages' => ceil( $total_users / self::BATCH_SIZE ), + ) + ); + } + + /** + * Perform batch checking for users based on specific conditions. + * Ignore users with `_um_registration_in_progress` that can be in the process of the registration. + * Get users with empty `account_status` meta. + * + * @param int $page The current page number. + * @param int $total The total number of users to process. + * @param int $pages The total number of pages to process. + */ + public function batch_check( $page, $total, $pages ) { + $users = new WP_User_Query( + array( + 'number' => self::BATCH_SIZE, + 'fields' => 'ids', + 'meta_query' => array( + 'relation' => 'AND', + array( + 'relation' => 'OR', + array( + 'key' => '_um_registration_in_progress', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => '_um_registration_in_progress', + 'value' => '1', + 'compare' => '!=', + ), + ), + array( + 'relation' => 'OR', + array( + 'key' => 'account_status', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => 'account_status', + 'value' => '', + 'compare' => '=', + ), + ), + ), + ) + ); + + $results = $users->get_results(); + + if ( ! empty( $results ) ) { + $um_empty_status_users = get_option( '_um_log_empty_status_users', array( 0, 0 ) ); + if ( ! is_array( $um_empty_status_users ) ) { + $um_empty_status_users = array( 0, count( $results ) ); + } + + foreach ( $results as $user_id ) { + $res = UM()->common()->users()->approve( $user_id, true, true ); + if ( $res ) { + ++$um_empty_status_users[0]; + } + } + + if ( $um_empty_status_users[0] < $um_empty_status_users[1] ) { + update_option( '_um_log_empty_status_users', $um_empty_status_users ); + } else { + delete_option( '_um_log_empty_status_users' ); + } + + $next_page = $page + 1; + if ( $next_page <= $pages ) { + UM()->maybe_action_scheduler()->enqueue_async_action( + self::BATCH_ACTION, + array( + 'page' => $next_page, + 'total' => $total, + 'pages' => $pages, + ) + ); + } + } + } + } +} diff --git a/includes/common/class-init.php b/includes/common/class-init.php index 73cf4805..2ceff24f 100644 --- a/includes/common/class-init.php +++ b/includes/common/class-init.php @@ -39,6 +39,10 @@ if ( ! class_exists( 'um\common\Init' ) ) { if ( empty( UM()->classes['um\common\actions\emails'] ) ) { UM()->classes['um\common\actions\emails'] = new actions\Emails(); } + + if ( empty( UM()->classes['um\common\actions\users'] ) ) { + UM()->classes['um\common\actions\users'] = new actions\Users(); + } // Other classes init here as soon as possible. } diff --git a/includes/common/class-users.php b/includes/common/class-users.php index e6fcaa5e..392c29d2 100644 --- a/includes/common/class-users.php +++ b/includes/common/class-users.php @@ -658,11 +658,12 @@ class Users { * * @param int $user_id User ID. * @param bool $force If true - ignore current user condition. + * @param bool $silent If true - don't send email notification. E.g. case when user already exists, but doesn't have a status. * * @return bool `true` if the user has been approved * `false` on failure or if the user already has approved status. */ - public function approve( $user_id, $force = false ) { + public function approve( $user_id, $force = false, $silent = false ) { if ( ! $this->can_be_approved( $user_id, $force ) ) { return false; } @@ -683,25 +684,27 @@ class Users { // It's `false` on failure or if the user already has approved status. if ( false !== $result ) { - $userdata = get_userdata( $user_id ); + if ( false === $silent ) { + $userdata = get_userdata( $user_id ); - $this->reset_activation_link( $user_id ); + $this->reset_activation_link( $user_id ); - $email_slug = 'welcome_email'; - if ( 'awaiting_admin_review' === $old_status ) { - $email_slug = 'approved_email'; - $this->maybe_generate_password_reset_key( $userdata ); + $email_slug = 'welcome_email'; + if ( 'awaiting_admin_review' === $old_status ) { + $email_slug = 'approved_email'; + $this->maybe_generate_password_reset_key( $userdata ); + } + + $current_user_id = get_current_user_id(); + um_fetch_user( $user_id ); + + add_filter( 'um_template_tags_patterns_hook', array( UM()->password(), 'add_placeholder' ) ); + add_filter( 'um_template_tags_replaces_hook', array( UM()->password(), 'add_replace_placeholder' ) ); + + UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, $email_slug, array( 'fetch_user_id' => $user_id ) ) ); + + um_fetch_user( $current_user_id ); } - - $current_user_id = get_current_user_id(); - um_fetch_user( $user_id ); - - add_filter( 'um_template_tags_patterns_hook', array( UM()->password(), 'add_placeholder' ) ); - add_filter( 'um_template_tags_replaces_hook', array( UM()->password(), 'add_replace_placeholder' ) ); - - UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, $email_slug, array( 'fetch_user_id' => $user_id ) ) ); - - um_fetch_user( $current_user_id ); /** * Fires after User has been approved. * @@ -827,4 +830,29 @@ class Users { $user = WP_Session_Tokens::get_instance( $user_id ); $user->destroy_all(); } + + /** + * Retrieve the number of users with empty `account_status` usermeta. + * + * @return int + */ + public static function get_empty_status_users() { + global $wpdb; + + $total_users = $wpdb->get_var( + "SELECT COUNT(u.ID) + FROM {$wpdb->users} u + LEFT JOIN {$wpdb->usermeta} um ON u.ID = um.user_id AND um.meta_key = 'account_status' + LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = '_um_registration_in_progress' + WHERE ( um.meta_value IS NULL OR um.meta_value = '' ) AND + um2.meta_value IS NULL OR um2.meta_value != '1'" + ); + + $total_users = absint( $total_users ); + // In WordPress, an underscore prefix before the option name (e.g., _my_option_name) is commonly used to indicate that the option is private. + // This option has a format: {updated_users}/{total_users_for_update}. + update_option( '_um_log_empty_status_users', array( 0, $total_users ) ); + + return $total_users; + } } diff --git a/includes/core/class-builtin.php b/includes/core/class-builtin.php index a0fd52c4..76585731 100644 --- a/includes/core/class-builtin.php +++ b/includes/core/class-builtin.php @@ -34,6 +34,9 @@ if ( ! class_exists( 'um\core\Builtin' ) ) { public $saved_fields = array(); /** + * Field keys that cannot be used as metakey when add new custom field to UM Form. + * Note: They are used both with function `UM()->user()->is_metakey_banned()` to avoid using unsecure metakeys. + * * @var array */ public $blacklist_fields = array(); @@ -183,7 +186,7 @@ if ( ! class_exists( 'um\core\Builtin' ) ) { * @return int|string Empty or error string. */ public function blacklist_field_err( $key ) { - if ( in_array( strtolower( $key ), $this->blacklist_fields, true ) ) { + if ( in_array( strtolower( $key ), $this->blacklist_fields, true ) || UM()->user()->is_metakey_banned( $key ) ) { return __( 'Your meta key can not be used', 'ultimate-member' ); } @@ -1426,6 +1429,9 @@ if ( ! class_exists( 'um\core\Builtin' ) ) { public function set_blacklist_fields() { $this->blacklist_fields = array( 'id', + 'role', + 'user_id', + 'user_pass', ); /** @@ -2056,7 +2062,7 @@ if ( ! class_exists( 'um\core\Builtin' ) ) { 'TO' => __('Tonga','ultimate-member'), 'TT' => __('Trinidad and Tobago','ultimate-member'), 'TN' => __('Tunisia','ultimate-member'), - 'TR' => __('Turkey','ultimate-member'), + 'TR' => __('Türkiye','ultimate-member'), 'TM' => __('Turkmenistan','ultimate-member'), 'TC' => __('Turks and Caicos Islands','ultimate-member'), 'TV' => __('Tuvalu','ultimate-member'), diff --git a/includes/core/class-member-directory-meta.php b/includes/core/class-member-directory-meta.php index 9ab8b16b..272fbe0d 100644 --- a/includes/core/class-member-directory-meta.php +++ b/includes/core/class-member-directory-meta.php @@ -340,11 +340,11 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $join_alias and $compare variables are pre-escaped. $values_array[] = $wpdb->prepare( "{$join_alias}.um_value LIKE %s", '%"' . $wpdb->esc_like( $single_val ) . '"%' ); - $values_array[] = $wpdb->prepare( "{$join_alias}.um_value LIKE %s", '%' . $wpdb->esc_like( maybe_serialize( (string) $single_val ) ) . '%' ); + $values_array[] = $wpdb->prepare( "{$join_alias}.um_value LIKE %s", '%' . $wpdb->esc_like( serialize( (string) $single_val ) ) . '%' ); $values_array[] = $wpdb->prepare( "{$join_alias}.um_value = %s", $single_val ); if ( is_numeric( $single_val ) ) { - $values_array[] = $wpdb->prepare( "{$join_alias}.um_value LIKE %s", '%' . $wpdb->esc_like( maybe_serialize( (int) $single_val ) ) . '%' ); + $values_array[] = $wpdb->prepare( "{$join_alias}.um_value LIKE %s", '%' . $wpdb->esc_like( serialize( (int) $single_val ) ) . '%' ); } // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $join_alias is pre-escaped. } diff --git a/includes/core/class-password.php b/includes/core/class-password.php index 5690e046..355f9183 100644 --- a/includes/core/class-password.php +++ b/includes/core/class-password.php @@ -36,18 +36,26 @@ if ( ! class_exists( 'um\core\Password' ) ) { /** * Get Reset URL * - * @return bool|string + * @param int|null $user_id + * + * @return string */ - public function reset_url() { + public function reset_url( $user_id = null ) { static $reset_key = null; - $user_id = um_user( 'ID' ); + if ( is_null( $user_id ) ) { + $user_id = um_user( 'ID' ); + } delete_option( "um_cache_userdata_{$user_id}" ); // New reset password key via WordPress native field. It maybe already exists here but generated twice to make sure that emailed with a proper and fresh hash. // But doing that only once in 1 request using static variable. Different email placeholders can use reset_url() and we have to use 1 time generated to avoid invalid keys. $user_data = get_userdata( $user_id ); + if ( empty( $user_data ) ) { + return ''; + } + if ( empty( $reset_key ) ) { $reset_key = UM()->user()->maybe_generate_password_reset_key( $user_data ); } @@ -466,7 +474,7 @@ if ( ! class_exists( 'um\core\Password' ) ) { um_fetch_user( $data->ID ); if ( false === UM()->options()->get( 'only_approved_user_reset_password' ) || UM()->common()->users()->has_status( $data->ID, 'approved' ) ) { - UM()->user()->password_reset(); + UM()->user()->password_reset( $data->ID ); } } diff --git a/includes/core/class-setup.php b/includes/core/class-setup.php index 27a342eb..eb6d37f4 100644 --- a/includes/core/class-setup.php +++ b/includes/core/class-setup.php @@ -310,37 +310,21 @@ KEY meta_value_indx (um_value(191)) * @since 2.4.2 */ public function set_default_user_status() { - $result = get_transient( 'um_count_users_unassigned' ); - if ( false === $result ) { - $args = array( - 'fields' => 'ids', - 'number' => 0, - 'meta_query' => array( - array( - 'key' => 'account_status', - 'compare' => 'NOT EXISTS', - ), - ), - 'um_custom_user_query' => true, - ); - - $users = new WP_User_Query( $args ); - if ( empty( $users ) || is_wp_error( $users ) ) { - $result = array(); - } else { - $result = $users->get_results(); - } - - set_transient( 'um_count_users_unassigned', $result, DAY_IN_SECONDS ); - } - - if ( empty( $result ) ) { + $total_users = UM()->common()->users()::get_empty_status_users(); + if ( empty( $total_users ) ) { return; } - foreach ( $result as $user_id ) { - update_user_meta( $user_id, 'account_status', 'approved' ); - } + // If there are some users without `account_status` then run the first async batch for update. + $batch_size = 50; // See the class constant value `\um\common\actions\Users::BATCH_ACTION`. + UM()->maybe_action_scheduler()->enqueue_async_action( + $batch_size, + array( + 'page' => 1, + 'total' => $total_users, + 'pages' => ceil( $total_users / $batch_size ), + ) + ); } /** diff --git a/includes/core/class-user.php b/includes/core/class-user.php index f8d70584..630bc515 100644 --- a/includes/core/class-user.php +++ b/includes/core/class-user.php @@ -640,7 +640,6 @@ if ( ! class_exists( 'um\core\User' ) ) { UM()->files()->remove_dir( UM()->files()->upload_temp ); UM()->files()->remove_dir( UM()->uploader()->get_upload_base_dir() . um_user( 'ID' ) . DIRECTORY_SEPARATOR ); - delete_transient( 'um_count_users_unassigned' ); delete_transient( 'um_count_users_pending_dot' ); } @@ -959,8 +958,6 @@ if ( ! class_exists( 'um\core\User' ) ) { /** This action is documented in ultimate-member/includes/common/um-actions-register.php */ do_action( 'um_user_register', $user_id, $_POST, null ); } - - delete_transient( 'um_count_users_unassigned' ); } @@ -1487,22 +1484,34 @@ if ( ! class_exists( 'um\core\User' ) ) { return get_password_reset_key( $userdata ); } - /** * Password reset email + * + * @param int|null $user_id + * + * @return void */ - function password_reset() { - $userdata = get_userdata( um_user( 'ID' ) ); + public function password_reset( $user_id = null ) { + if ( is_null( $user_id ) ) { + $user_id = um_user( 'ID' ); + } + $userdata = get_userdata( $user_id ); $this->maybe_generate_password_reset_key( $userdata ); - add_filter( 'um_template_tags_patterns_hook', array( UM()->password(), 'add_placeholder' ) ); - add_filter( 'um_template_tags_replaces_hook', array( UM()->password(), 'add_replace_placeholder' ) ); + $mail_args = array( + 'fetch_user_id' => $user_id, + 'tags' => array( + '{password_reset_link}', + ), + 'tags_replace' => array( + UM()->password()->reset_url( $user_id ), + ), + ); - UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, 'resetpw_email', array( 'fetch_user_id' => um_user( 'ID' ) ) ) ); + UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, 'resetpw_email', $mail_args ) ); } - /** * Password changed email * diff --git a/includes/core/um-actions-profile.php b/includes/core/um-actions-profile.php index 101f5a21..520c42c2 100644 --- a/includes/core/um-actions-profile.php +++ b/includes/core/um-actions-profile.php @@ -737,18 +737,18 @@ function um_profile_dynamic_meta_desc() { $image_info = array(); $image_width = $image_size; $image_height = $image_size; - if ( false === strpos( $image, 'gravatar.com' ) ) { - // Ignore Gravatar image here and handler a real image. + $upload_dir = wp_get_upload_dir(); + if ( false !== strpos( $image, $upload_dir['url'] ) ) { + // Get the image size of the real image. $image = current( explode( '?', $image ) ); // strip $_GET attributes from photo URL. $image_path = wp_normalize_path( ABSPATH . wp_parse_url( $image, PHP_URL_PATH ) ); - $image_info = wp_check_filetype( $image_path ); - $imagesizes = getimagesize( $image_path ); - if ( is_array( $imagesizes ) ) { - list( $image_width, $image_height ) = $imagesizes; + if ( file_exists( $image_path ) ) { + $image_info = wp_check_filetype( $image_path ); + $imagesizes = @getimagesize( $image_path ); + if ( is_array( $imagesizes ) ) { + list( $image_width, $image_height ) = $imagesizes; + } } - } else { - // Gravatar image. - $image_path = esc_url_raw( $image ); } $person = array( diff --git a/includes/core/um-actions-register.php b/includes/core/um-actions-register.php index 0516d902..007101e8 100644 --- a/includes/core/um-actions-register.php +++ b/includes/core/um-actions-register.php @@ -113,6 +113,11 @@ add_action( 'um_user_register', 'um_after_insert_user', 1, 3 ); * @param $user_id */ function um_send_registration_notification( $user_id ) { + if ( is_admin() ) { + // Don't send email notifications to administrators about new user registration, because the user was created from wp-admin. + return; + } + um_fetch_user( $user_id ); $registration_status = um_user( 'status' ); @@ -137,7 +142,12 @@ add_action( 'um_registration_complete', 'um_send_registration_notification' ); * @param null|array $form_data */ function um_check_user_status( $user_id, $args, $form_data = null ) { - $registration_status = um_user( 'status' ); + if ( ( is_null( $form_data ) || is_admin() ) && UM()->options()->get( 'admin_ignore_user_status' ) ) { + $registration_status = 'approved'; + } else { + $registration_status = um_user( 'status' ); + } + /** * Fires after complete UM user registration. * Where $status can be equal to 'approved', 'checkmail' or 'pending'. @@ -169,9 +179,12 @@ function um_check_user_status( $user_id, $args, $form_data = null ) { */ do_action( "um_post_registration_{$registration_status}_hook", $user_id, $args, $form_data ); + delete_user_meta( $user_id, '_um_registration_in_progress' ); // Status is set. We can delete this marker. + if ( is_null( $form_data ) || is_admin() ) { return; } + // Code below is running only for registration from the frontend forms. /** * Fires after complete UM user registration. Only for the frontend action which is run before autologin and redirects. @@ -502,6 +515,11 @@ function um_submit_form_register( $args, $form_data ) { 'user_pass' => $user_password, 'user_email' => $user_email, 'role' => $user_role, + 'meta_input' => array( + // It's used to ignore users who cannot finish the registration process in the scheduled tasks + // to set 'approved' status to the users without `account_status` meta. + '_um_registration_in_progress' => true, + ), ); $user_id = wp_insert_user( $userdata ); diff --git a/readme.txt b/readme.txt index ab90339a..53fa0b0c 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Contributors: ultimatemember, champsupertramp, nsinelnikov Tags: community, member, membership, user-profile, user-registration Requires PHP: 7.0 Requires at least: 6.2 -Tested up to: 6.7 +Tested up to: 6.8 Stable tag: 2.10.2 License: GPLv3 License URI: http://www.gnu.org/licenses/gpl-3.0.txt @@ -167,6 +167,24 @@ No specific extensions are needed. But we highly recommended keep active these P IMPORTANT: PLEASE UPDATE THE PLUGIN TO AT LEAST VERSION 2.6.7 IMMEDIATELY. VERSION 2.6.7 PATCHES SECURITY PRIVILEGE ESCALATION VULNERABILITY. PLEASE SEE [THIS ARTICLE](https://docs.ultimatemember.com/article/1866-security-incident-update-and-recommended-actions) FOR MORE INFORMATION += 2.10.3 2025-04-24 = + +**Enhancements** + +* Added: The `Ignore the "User Role > Registration Options"` setting. It provides an ability to auto-approve users if they were created via wp-admin > Users screen. +* Tweak: Avoid email notifications to Administrator about user registration via wp-admin > Users screen. +* Tweak: Updated the Action Scheduler implementation to improve flexibility and clarity. Refactor Action Scheduler for not only email handling. + +**Bugfixes** + +* Fixed: Member Directory styles when it's rendered on the Gutenberg builder page. +* Fixed: Member Directory filtering query when the custom users metatable is used. +* Fixed: PHP Warning that occurs when using the `getimagesize` function with an image from an external source. +* Fixed: Reset Password email notification's the {password_reset_link}` placeholder. +* Fixed: Changed "Turkey" to the current official term "Türkiye". + +**Cached and optimized/minified assets(JS/CSS) must be flushed/re-generated after upgrade** + = 2.10.2 2025-04-02 = **Enhancements** diff --git a/tests/generate-empty-status-users.php b/tests/generate-empty-status-users.php new file mode 100644 index 00000000..55e26ae2 --- /dev/null +++ b/tests/generate-empty-status-users.php @@ -0,0 +1,34 @@ + $random_user_name, + 'user_pass' => 'q1q2q1q2', + 'user_email' => $random_user_email, + 'first_name' => $random_first_name, + 'last_name' => $random_last_name, + 'role' => 'subscriber', + ); + + $user_id = wp_insert_user( $userdata ); + + if ( is_wp_error( $user_id ) ) { + // Something went wrong, handle the error + var_dump( 'User creation failed: ' . $user_id->get_error_message() ); + } else { + var_dump( 'User creation complete: ID:' . $user_id . ' Username:' . $userdata['user_login'] ); + } +} + +exit; diff --git a/ultimate-member.php b/ultimate-member.php index 960538ca..66b8d5dd 100644 --- a/ultimate-member.php +++ b/ultimate-member.php @@ -3,7 +3,7 @@ * Plugin Name: Ultimate Member * Plugin URI: http://ultimatemember.com/ * Description: The easiest way to create powerful online communities and beautiful user profiles with WordPress - * Version: 2.10.2 + * Version: 2.10.3 * Author: Ultimate Member * Author URI: http://ultimatemember.com/ * Text Domain: ultimate-member diff --git a/uninstall.php b/uninstall.php index 6cf0d554..5cf4e8ab 100644 --- a/uninstall.php +++ b/uninstall.php @@ -127,7 +127,7 @@ if ( ! empty( $delete_options ) ) { delete_transient( "um_count_users_{$status}" ); } delete_transient( 'um_count_users_pending_dot' ); - delete_transient( 'um_count_users_unassigned' ); + delete_transient( 'um_count_users_unassigned' ); // legacy but still need to delete while uninstall. //remove all users cache UM()->user()->remove_cache_all_users();