Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
#tab-dashboard-wrapper{margin-left:-20px}label{cursor:auto}#wpbody .thrive-ab-editor-link{color:#555}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
#tcb-modal-reset-stats{max-width:760px;border-radius:5px;left:30%}#tcb-modal-reset-stats .tcb-modal-title{margin-left:0}#tcb-modal-reset-stats .tcb-gray{padding:10px 20px 0;margin-bottom:100px}#tcb-modal-reset-stats .tcb-gray label span:after{left:.25em;top:49%}#tcb-modal-reset-stats .tcb-modal-footer{padding:20px 0}#tcb-modal-reset-stats .tcb-modal-footer .tcb-modal-cancel{padding:0}@media only screen and (max-width: 1400px){.tvd-modal{max-width:none !important;width:80%}}.tcb-icon.test-running use{--color-a: #29c1ec;--color-slash: #c0cad1;--color-b: #4bb35e}

View File

@@ -0,0 +1,74 @@
<svg style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<symbol id="icon-external-link" viewBox="0 0 32 32">
<title>external-link</title>
<path d="M25.143 16.571v5.714q0 2.125-1.509 3.634t-3.634 1.509h-14.857q-2.125 0-3.634-1.509t-1.509-3.634v-14.857q0-2.125 1.509-3.634t3.634-1.509h12.571q0.25 0 0.411 0.161t0.161 0.411v1.143q0 0.25-0.161 0.411t-0.411 0.161h-12.571q-1.179 0-2.018 0.839t-0.839 2.018v14.857q0 1.179 0.839 2.018t2.018 0.839h14.857q1.179 0 2.018-0.839t0.839-2.018v-5.714q0-0.25 0.161-0.411t0.411-0.161h1.143q0.25 0 0.411 0.161t0.161 0.411zM32 1.143v9.143q0 0.464-0.339 0.804t-0.804 0.339-0.804-0.339l-3.143-3.143-11.643 11.643q-0.179 0.179-0.411 0.179t-0.411-0.179l-2.036-2.036q-0.179-0.179-0.179-0.411t0.179-0.411l11.643-11.643-3.143-3.143q-0.339-0.339-0.339-0.804t0.339-0.804 0.804-0.339h9.143q0.464 0 0.804 0.339t0.339 0.804z"></path>
</symbol>
<symbol id="icon-pencil" viewBox="0 0 32 32">
<title>pencil</title>
<path d="M8.768 27.429l1.625-1.625-4.196-4.196-1.625 1.625v1.911h2.286v2.286h1.911zM18.107 10.857q0-0.393-0.393-0.393-0.179 0-0.304 0.125l-9.679 9.679q-0.125 0.125-0.125 0.304 0 0.393 0.393 0.393 0.179 0 0.304-0.125l9.679-9.679q0.125-0.125 0.125-0.304zM17.143 7.429l7.429 7.429-14.857 14.857h-7.429v-7.429zM29.339 9.143q0 0.946-0.661 1.607l-2.964 2.964-7.429-7.429 2.964-2.946q0.643-0.679 1.607-0.679 0.946 0 1.625 0.679l4.196 4.179q0.661 0.696 0.661 1.625z"></path>
</symbol>
<symbol id="icon-trash-o" viewBox="0 0 32 32">
<title>trash-o</title>
<path d="M12.571 13.143v10.286q0 0.25-0.161 0.411t-0.411 0.161h-1.143q-0.25 0-0.411-0.161t-0.161-0.411v-10.286q0-0.25 0.161-0.411t0.411-0.161h1.143q0.25 0 0.411 0.161t0.161 0.411zM17.143 13.143v10.286q0 0.25-0.161 0.411t-0.411 0.161h-1.143q-0.25 0-0.411-0.161t-0.161-0.411v-10.286q0-0.25 0.161-0.411t0.411-0.161h1.143q0.25 0 0.411 0.161t0.161 0.411zM21.714 13.143v10.286q0 0.25-0.161 0.411t-0.411 0.161h-1.143q-0.25 0-0.411-0.161t-0.161-0.411v-10.286q0-0.25 0.161-0.411t0.411-0.161h1.143q0.25 0 0.411 0.161t0.161 0.411zM24 26.071v-16.929h-16v16.929q0 0.393 0.125 0.723t0.259 0.482 0.188 0.152h14.857q0.054 0 0.188-0.152t0.259-0.482 0.125-0.723zM12 6.857h8l-0.857-2.089q-0.125-0.161-0.304-0.196h-5.661q-0.179 0.036-0.304 0.196zM28.571 7.429v1.143q0 0.25-0.161 0.411t-0.411 0.161h-1.714v16.929q0 1.482-0.839 2.563t-2.018 1.080h-14.857q-1.179 0-2.018-1.045t-0.839-2.527v-17h-1.714q-0.25 0-0.411-0.161t-0.161-0.411v-1.143q0-0.25 0.161-0.411t0.411-0.161h5.518l1.25-2.982q0.268-0.661 0.964-1.125t1.411-0.464h5.714q0.714 0 1.411 0.464t0.964 1.125l1.25 2.982h5.518q0.25 0 0.411 0.161t0.161 0.411z"></path>
</symbol>
<symbol id="icon-monetary-2" viewBox="0 0 65 32">
<title>monetary-2</title>
<path fill="#42b265" style="fill: var(--color1, #42b265)"
d="M55.792 31.744c-0.299-0.011-0.568-0.131-0.77-0.321l0.001 0.001-4.425-4.489c-0.179-0.205-0.288-0.474-0.288-0.77s0.109-0.565 0.289-0.771l-0.001 0.001c0.199-0.193 0.47-0.313 0.77-0.313s0.571 0.119 0.77 0.313l-0-0 3.655 3.655 7.054-8.208c0.193-0.229 0.481-0.374 0.802-0.374 0.578 0 1.047 0.469 1.047 1.047 0 0.257-0.093 0.493-0.247 0.675l0.001-0.002-7.888 9.234c-0.193 0.198-0.463 0.321-0.761 0.321-0.003 0-0.006 0-0.008-0h0z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M4.361 4.104v27.896h41.363c0.496 0 0.898-0.402 0.898-0.898v0 0c0-0.496-0.402-0.898-0.898-0.898v0h-30.525c-0.311-4.634-4.146-8.275-8.831-8.275-0.074 0-0.148 0.001-0.222 0.003l0.011-0v-7.952c0.046 0.001 0.101 0.001 0.156 0.001 4.618 0 8.411-3.535 8.82-8.047l0.002-0.034h31.679c0.41 4.543 4.2 8.077 8.816 8.077 0.186 0 0.37-0.006 0.553-0.017l-0.025 0.001v3.271c-0 0.006-0 0.013-0 0.019 0 0.496 0.402 0.898 0.898 0.898 0.007 0 0.014-0 0.020-0l-0.001 0c0.496 0 0.898-0.402 0.898-0.898v0-13.146zM6.413 23.792c3.624 0.009 6.597 2.782 6.924 6.321l0.002 0.027h-7.182v-6.349zM6.413 12.056h-0.192v-6.156h7.118c-0.457 3.466-3.376 6.119-6.922 6.156l-0.004 0zM55.663 12.056c-3.544-0.014-6.459-2.685-6.859-6.124l-0.003-0.032h7.439v6.092c-0.144 0.042-0.31 0.067-0.482 0.067-0.033 0-0.067-0.001-0.1-0.003l0.005 0z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M31.166 8.914c-0.010-0-0.022-0-0.034-0-2.344 0-4.465 0.956-5.993 2.5l-0.001 0.001c-1.546 1.553-2.501 3.694-2.501 6.058 0 0.012 0 0.024 0 0.036v-0.002c-0 0.010-0 0.022-0 0.034 0 2.344 0.956 4.465 2.5 5.993l0.001 0.001c1.543 1.542 3.674 2.497 6.027 2.501h0.001c4.728 0 8.561-3.833 8.561-8.561s-3.833-8.561-8.561-8.561v0zM37.836 17.443c0 0.006 0 0.013 0 0.019 0 3.657-2.952 6.625-6.603 6.65h-0.002c-3.648 0-6.605-2.957-6.605-6.605s2.957-6.605 6.605-6.605v0 0c0.031-0.001 0.067-0.001 0.103-0.001 3.591 0 6.503 2.911 6.503 6.503 0 0.014-0 0.028-0 0.041v-0.002z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M30.782 13.531c-0.68 0.035-1.298 0.27-1.804 0.648l0.009-0.006c-0.392 0.313-0.641 0.791-0.641 1.327 0 0.007 0 0.014 0 0.021v-0.001c0.001 0.487 0.17 0.934 0.452 1.287l-0.003-0.004c0.439 0.429 0.976 0.759 1.575 0.954l0.029 0.008 0.385 0.128v1.796c-0.461-0.011-0.901-0.081-1.319-0.201l0.037 0.009c-0.463-0.111-0.869-0.265-1.248-0.463l0.030 0.014v1.539c0.739 0.304 1.595 0.49 2.492 0.513l0.009 0v1.154h0.77v-1.154c0.699-0.048 1.336-0.281 1.872-0.65l-0.013 0.008c0.395-0.327 0.645-0.817 0.645-1.366 0-0.038-0.001-0.076-0.004-0.114l0 0.005c0-0.009 0-0.020 0-0.031 0-0.315-0.071-0.613-0.198-0.879l0.005 0.012c-0.169-0.297-0.409-0.536-0.696-0.701l-0.009-0.005c-0.443-0.258-0.971-0.517-1.517-0.738l-0.086-0.031v-1.731c0.685 0.064 1.316 0.223 1.903 0.465l-0.044-0.016 0.513-1.347c-0.697-0.322-1.513-0.51-2.372-0.513h-0.001v-0.834h-0.77zM32.192 18.661c0.11 0.094 0.182 0.23 0.192 0.383l0 0.002c0 0.385-0.257 0.577-0.77 0.705v-1.411c0.227 0.056 0.422 0.168 0.577 0.321l-0-0zM30.782 16.289c-0.256-0.128-0.449-0.256-0.577-0.321-0.118-0.107-0.193-0.262-0.193-0.433 0-0.005 0-0.011 0-0.016l-0 0.001q0-0.481 0.77-0.577z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M34.693 0h-34.693v24.048c0 0.496 0.402 0.898 0.898 0.898v0 0c0.496 0 0.898-0.402 0.898-0.898v0-22.253h32.898c0.496 0 0.898-0.402 0.898-0.898v0c0-0.496-0.402-0.898-0.898-0.898v0z"></path>
</symbol>
<symbol id="icon-subs" viewBox="0 0 29 32">
<title>subs</title>
<path fill="#333" style="fill: var(--color2, #333)"
d="M25.789 22.684h-3.015c-0.373 0-0.675-0.302-0.675-0.675v0 0c0-0.373 0.302-0.675 0.675-0.675v0h3.074c0.007 0 0.015 0 0.024 0 0.945 0 1.74-0.643 1.971-1.516l0.003-0.014c0.031-0.133 0.050-0.285 0.050-0.441 0-1.092-0.88-1.979-1.97-1.989h-22.505c-0.011-0-0.024-0-0.037-0-0.945 0-1.741 0.643-1.971 1.516l-0.003 0.014c-0.031 0.133-0.050 0.285-0.050 0.441 0 1.092 0.88 1.979 1.97 1.989h3.196c0.373 0 0.675 0.302 0.675 0.675v0 0c0 0.373-0.302 0.675-0.675 0.675v0h-3.060c-0.015 0-0.032 0-0.050 0-1.826 0-3.318-1.431-3.415-3.232l-0-0.009c-0.001-0.029-0.001-0.064-0.001-0.099 0-1.834 1.487-3.322 3.322-3.322 0.004 0 0.007 0 0.011 0h22.593c0.003 0 0.007-0 0.010-0 1.834 0 3.322 1.487 3.322 3.322 0 0.035-0.001 0.070-0.002 0.104l0-0.005c-0.098 1.81-1.59 3.241-3.416 3.241-0.017 0-0.035-0-0.052-0l0.003 0z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M28.444 14.492h-27.544c-0.497 0-0.9-0.403-0.9-0.9v0-4.726c0-0.497 0.403-0.9 0.9-0.9v0h27.544c0.497 0 0.9 0.403 0.9 0.9v0 4.726c0 0.497-0.403 0.9-0.9 0.9v0zM1.35 13.142h26.644v-3.826h-26.644z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M28.444 6.526h-27.544c-0.497 0-0.9-0.403-0.9-0.9v0-4.726c0-0.497 0.403-0.9 0.9-0.9v0h27.544c0.497 0 0.9 0.403 0.9 0.9v0 4.726c0 0.497-0.403 0.9-0.9 0.9v0zM1.35 5.176h26.644v-3.826h-26.644z"></path>
<path fill="#42b265" style="fill: var(--color1, #42b265)"
d="M14.852 22.369c0.369-0.010 0.665-0.306 0.675-0.674l0-0.001v-1.8c0-0.373-0.302-0.675-0.675-0.675s-0.675 0.302-0.675 0.675v0 1.8c0.010 0.369 0.306 0.665 0.674 0.675l0.001 0z"></path>
<path fill="#42b265" style="fill: var(--color1, #42b265)"
d="M20.748 24.664h-1.8c-0.373 0-0.675 0.302-0.675 0.675s0.302 0.675 0.675 0.675v0h1.8c0.369-0.010 0.665-0.306 0.675-0.674l0-0.001c0-0.004 0-0.009 0-0.014 0-0.365-0.296-0.662-0.662-0.662-0.005 0-0.010 0-0.014 0h0.001z"></path>
<path fill="#42b265" style="fill: var(--color1, #42b265)"
d="M10.712 24.664h-1.8c-0.373 0-0.675 0.302-0.675 0.675s0.302 0.675 0.675 0.675v0h1.8c0.369-0.010 0.665-0.306 0.675-0.674l0-0.001c0-0.004 0-0.009 0-0.014 0-0.365-0.296-0.662-0.662-0.662-0.005 0-0.010 0-0.014 0h0.001z"></path>
<path fill="#42b265" style="fill: var(--color1, #42b265)"
d="M12.332 22.954c0.102-0.12 0.165-0.277 0.165-0.448 0-0.195-0.080-0.371-0.21-0.497l-0-0-1.35-1.17c-0.122-0.127-0.294-0.206-0.484-0.206-0.37 0-0.671 0.3-0.671 0.671 0 0.212 0.099 0.402 0.253 0.524l0.001 0.001 1.35 1.17c0.122 0.101 0.278 0.167 0.447 0.18l0.003 0c0.002 0 0.004 0 0.006 0 0.195 0 0.37-0.087 0.488-0.224l0.001-0.001z"></path>
<path fill="#42b265" style="fill: var(--color1, #42b265)"
d="M19.533 21.063c-0.12-0.139-0.297-0.226-0.494-0.226-0.175 0-0.334 0.069-0.451 0.181l0-0-1.35 1.17c-0.139 0.12-0.226 0.297-0.226 0.494 0 0.175 0.069 0.334 0.181 0.451l-0-0c0.125 0.132 0.3 0.217 0.494 0.225l0.002 0c0.001 0 0.002 0 0.004 0 0.173 0 0.331-0.069 0.447-0.18l-0 0 1.35-1.17c0.14-0.12 0.228-0.296 0.228-0.494 0-0.176-0.070-0.335-0.183-0.452l0 0z"></path>
<path fill="#42b265" style="fill: var(--color1, #42b265)"
d="M15.257 24.439c-0.125-0.132-0.3-0.217-0.494-0.225l-0.002-0c-0.187 0.013-0.357 0.078-0.498 0.182l0.003-0.002-2.34 2.34c-0.125 0.119-0.203 0.287-0.203 0.473s0.078 0.353 0.202 0.472l0 0c0.133 0.112 0.306 0.18 0.495 0.18s0.362-0.068 0.496-0.181l-0.001 0.001 1.17-1.17v4.816c0 0.373 0.302 0.675 0.675 0.675s0.675-0.302 0.675-0.675v0-4.771l1.17 1.17c0.121 0.121 0.288 0.196 0.473 0.196 0.369 0 0.668-0.299 0.668-0.668 0-0.185-0.075-0.352-0.196-0.473v0z"></path>
</symbol>
<symbol id="icon-visit_gp" viewBox="0 0 46 32">
<title>visit_gp</title>
<path fill="#333" style="fill: var(--color2, #333)"
d="M42.935 4.734h-6.249v-1.562c0-1.752-1.42-3.172-3.172-3.172h-30.296c-0.011-0-0.025-0-0.038-0-1.754 0-3.176 1.419-3.181 3.171v20.924c0 1.752 1.42 3.172 3.172 3.172v0h6.296v1.515c0 1.752 1.42 3.172 3.172 3.172h30.296c1.752 0 3.172-1.42 3.172-3.172v0-20.876c0-0.017 0.001-0.037 0.001-0.057 0-1.72-1.395-3.115-3.115-3.115-0.020 0-0.040 0-0.060 0.001l0.003-0zM1.42 3.172c0.003-0.966 0.785-1.749 1.751-1.751h30.296c0.001 0 0.003 0 0.005 0 0.965 0 1.747 0.782 1.747 1.747 0 0.002 0 0.003 0 0.005v-0 2.982h-33.799zM44.734 28.828c0 0.001 0 0.003 0 0.005 0 0.965-0.782 1.747-1.747 1.747-0.002 0-0.003 0-0.005 0h-30.296c-0.001 0-0.003 0-0.005 0-0.965 0-1.747-0.782-1.747-1.747 0-0.002 0-0.003 0-0.005v0-1.515h17.799c0.392 0 0.71-0.318 0.71-0.71v0 0c0-0.392-0.318-0.71-0.71-0.71v0h-25.562c-0.968-0.039-1.739-0.827-1.751-1.798v-16.522h33.846v8c0 0.392 0.318 0.71 0.71 0.71v0 0c0.392 0 0.71-0.318 0.71-0.71v0-3.266h8.047zM44.734 10.888h-8.047v-4.734h6.296c0.001 0 0.003 0 0.005 0 0.965 0 1.747 0.782 1.747 1.747 0 0.002 0 0.003 0 0.005v-0z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M6.959 2.888c-0.002 0-0.004-0-0.006-0-0.531 0-0.965 0.419-0.988 0.945l-0 0.002c0 0.549 0.445 0.994 0.994 0.994s0.994-0.445 0.994-0.994v0c-0.023-0.528-0.457-0.947-0.988-0.947-0.002 0-0.004 0-0.006 0h0z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M4.923 3.834c0 0.523-0.445 0.947-0.994 0.947s-0.994-0.424-0.994-0.947c0-0.523 0.445-0.947 0.994-0.947s0.994 0.424 0.994 0.947z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M11.172 3.834c0 0.523-0.445 0.947-0.994 0.947s-0.994-0.424-0.994-0.947c0-0.523 0.445-0.947 0.994-0.947s0.994 0.424 0.994 0.947z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M22.959 11.361h-17.609c-0.392 0-0.71 0.318-0.71 0.71s0.318 0.71 0.71 0.71v0h17.609c0.392 0 0.71-0.318 0.71-0.71s-0.318-0.71-0.71-0.71v0z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M15.479 18.414h-10.13c-0.392 0-0.71 0.318-0.71 0.71s0.318 0.71 0.71 0.71v0h10.083c0.388-0.010 0.7-0.322 0.71-0.709l0-0.001c0-0.007 0-0.016 0-0.024 0-0.371-0.294-0.673-0.662-0.686l-0.001-0z"></path>
<path fill="#333" style="fill: var(--color2, #333)"
d="M22.959 14.911h-17.609c-0.392 0-0.71 0.318-0.71 0.71s0.318 0.71 0.71 0.71v0h17.609c0.392 0 0.71-0.318 0.71-0.71s-0.318-0.71-0.71-0.71v0z"></path>
<path fill="#42b265" style="fill: var(--color1, #42b265)"
d="M40.71 21.633l-3.55-2.746c-0.117-0.089-0.265-0.142-0.426-0.142-0.392 0-0.71 0.318-0.71 0.71 0 0.232 0.111 0.437 0.282 0.567l0.002 0.001 2.178 1.657h-8.521c-0.392 0-0.71 0.318-0.71 0.71s0.318 0.71 0.71 0.71v0h8.426l-1.751 1.751c-0.131 0.125-0.213 0.302-0.213 0.497s0.082 0.372 0.213 0.497l0 0c0.14 0.118 0.322 0.19 0.521 0.19s0.381-0.072 0.522-0.191l-0.001 0.001 3.172-3.172c0.123-0.112 0.2-0.273 0.2-0.452 0-0.041-0.004-0.081-0.012-0.12l0.001 0.004c-0.023-0.209-0.15-0.383-0.328-0.472l-0.003-0.002z"></path>
</symbol>
<symbol id="icon-winner" viewBox="0 0 32 32">
<title>winner</title>
<path fill="#4bb35d" style="fill: var(--color1, #4bb35d)"
d="M28.12 4.4h-0.92v-2.8c0-0.88-0.72-1.6-1.6-1.6h-19.2c-0.88 0-1.6 0.72-1.6 1.6v2.8h-0.92c-2.16 0-3.88 1.72-3.88 3.88v0.28c0 2.12 1.72 3.84 3.88 3.84h0.92v2c0 5.16 3.52 9.52 8.28 10.8-2.040 0.84-3.48 2.84-3.48 5.2 0 0.88 0.72 1.6 1.6 1.6h9.6c0.88 0 1.6-0.72 1.6-1.6 0-2.36-1.44-4.36-3.48-5.2 4.76-1.28 8.28-5.64 8.28-10.8v-2h0.92c2.12 0 3.88-1.72 3.88-3.88v-0.28c0-2.12-1.72-3.84-3.88-3.84zM3.88 11.6c-1.72 0-3.080-1.36-3.080-3.080v-0.28c0-1.68 1.36-3.040 3.080-3.040h0.92v6.4h-0.92zM16.8 25.6c2.64 0 4.8 2.16 4.8 4.8 0 0.44-0.36 0.8-0.8 0.8h-9.6c-0.44 0-0.8-0.36-0.8-0.8 0-2.64 2.16-4.8 4.8-4.8h1.6zM26.4 14.4c0 5.72-4.68 10.4-10.4 10.4s-10.4-4.68-10.4-10.4v-12.8c0-0.44 0.36-0.8 0.8-0.8h19.2c0.44 0 0.8 0.36 0.8 0.8v12.8zM31.2 8.52c0 1.68-1.36 3.080-3.080 3.080h-0.92v-6.4h0.92c1.68 0 3.080 1.36 3.080 3.080v0.24z"></path>
</symbol>
</defs>
</svg>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,20 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 1/16/2018
* Time: 10:55 AM
*/
/**
* Base Collection
*/
module.exports = Backbone.Collection.extend( {
/**
* helper function to get the last item of a collection
*
* @return Backbone.Model
*/
last: function () {
return this.at( this.size() - 1 );
}
} );

View File

@@ -0,0 +1,33 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 1/16/2018
* Time: 10:56 AM
*/
var base = require( './base' ),
BreadcrumbLink = require( '../models/breadcrumb-link' );
/**
* Breadcrumb links collection
*/
module.exports = base.extend( {
model: Backbone.Model.extend( {
defaults: {
hash: '',
label: ''
}
} ),
/**
* helper function allows adding items to the collection easier
*
* @param {string} route
* @param {string} label
*/
add_page: function ( route, label ) {
var _model = new BreadcrumbLink( {
hash: route,
label: label
} );
return this.add( _model );
}
} );

View File

@@ -0,0 +1,21 @@
(function ( $ ) {
//DOM ready
$( function () {
var ThriveAbAdmin = window.ThriveAbAdmin || {},
router = require( './router' );
Backbone.emulateHTTP = true;
ThriveAbAdmin.router = new router();
ThriveAbAdmin.router.init_breadcrumbs();
Backbone.history.stop();
Backbone.history.start();
if ( ! Backbone.history.fragment ) {
ThriveAbAdmin.router.navigate( '#dashboard', {trigger: true} );
}
} );
})( jQuery );

View File

@@ -0,0 +1,36 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 1/16/2018
* Time: 11:00 AM
*/
/**
* BreadcrumbLink model
*/
module.exports = Backbone.Model.extend( {
defaults: {
ID: '',
hash: '',
label: '',
full_link: false
},
/**
* we pass only hash and label, and build the ID based on the label
*
* @param {object} att
*/
initialize: function ( att ) {
if ( ! this.get( 'ID' ) ) {
this.set( 'ID', att.label.split( ' ' ).join( '' ).toLowerCase() );
}
this.set( 'full_link', att.hash.match( /^http/ ) );
},
/**
*
* @returns {String}
*/
get_url: function () {
return this.get( 'full_link' ) ? this.get( 'hash' ) : ('#' + this.get( 'hash' ));
}
} );

View File

@@ -0,0 +1,127 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 1/16/2018
* Time: 9:51 AM
*/
var BreadcrumbsCollection = require( './collections/breadcrumbs' ),
BreadcrumbsView = require( './views/breadcrumbs' ),
DashboardView = require( './views/dashboard' );
(function ( $ ) {
module.exports = Backbone.Router.extend( {
view: null,
$el: $( '#tab-admin-dashboard-wrapper' ),
routes: {
'dashboard': 'dashboard'
},
breadcrumbs: {
col: null,
view: null
},
/**
* init the breadcrumbs collection and view
*/
init_breadcrumbs: function () {
this.breadcrumbs.col = new BreadcrumbsCollection();
this.breadcrumbs.view = new BreadcrumbsView( {
collection: this.breadcrumbs.col
} )
},
/**
* set the current page - adds the structure to breadcrumbs and sets the new document title
*
* @param {string} section page hierarchy
* @param {string} label current page label
*
* @param {Array} [structure] optional the structure of the links that lead to the current page
*/
set_page: function ( section, label, structure ) {
this.breadcrumbs.col.reset();
structure = structure || {};
/* Thrive Dashboard is always the first element */
this.breadcrumbs.col.add_page( ThriveAbAdmin.dash_url, ThriveAbAdmin.t.Thrive_Dashboard, true );
_.each( structure, _.bind( function ( item ) {
this.breadcrumbs.col.add_page( item.route, item.label );
}, this ) );
/**
* last link - no need for route
*/
this.breadcrumbs.col.add_page( '', label );
/* update the page title */
var $title = $( 'head > title' );
if ( ! this.original_title ) {
this.original_title = $title.html();
}
$title.html( label + ' &lsaquo; ' + this.original_title );
},
/**
* dashboard route callback
*/
dashboard: function () {
this.set_page( 'dashboard', ThriveAbAdmin.t.Dashboard );
var self = this;
TVE_Dash.showLoader();
if ( this.view ) {
this.view.remove();
}
jQuery.ajax( {
cache: false,
url: ThriveAbAdmin.ajax.url,
method: 'POST',
dataType: 'json',
data: {
route: 'testsforadmin',
action: ThriveAbAdmin.ajax.action,
custom: ThriveAbAdmin.ajax.controller_action,
nonce: ThriveAbAdmin.ajax.nonce
}
} ).done( function ( response ) {
self.view = new DashboardView( {
running_tests: new Backbone.Collection( response.running_tests ),
completed_tests: new Backbone.Collection( response.completed_tests ),
dashboard_stats: response.dashboard_stats
} );
self.$el.html( self.view.render().$el );
if ( ThriveAbAdmin.license.gp && ThriveAbAdmin.license.show_lightbox) {
TVE_Dash.modal( TVE_Dash.views.LicenseModal, {
model: {
title: 'Thrive Optimize',
license_class: 'grace-period',
product_class: 'tab',
license_link: ThriveAbAdmin.license.link,
grace_time: ThriveAbAdmin.license.grace_time
},
className: 'tvd-modal tvd-license-modal tvd-modal-grace-period',
width: '950px',
'max-width': '950px',
} );
} else if ( ThriveAbAdmin.license.exp && ! ThriveAbAdmin.license.gp ) {
TVE_Dash.modal( TVE_Dash.views.LicenseModal, {
model: {
title: 'Thrive Optimize',
license_class: 'expired',
product_class: 'tab',
license_link: ThriveAbAdmin.license.link
},
className: 'tvd-modal tvd-license-modal tvd-modal-expired',
no_close: true,
width: '950px',
dismissible: false,
'max-width': '950px',
} );
$( '#tab-admin-dashboard-wrapper' ).replaceWith( $('#tab-admin-dashboard-wrapper').clone() );
}
TVE_Dash.hideLoader();
} );
}
} );
})( jQuery );

View File

@@ -0,0 +1,31 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 1/16/2018
* Time: 11:03 AM
*/
/**
* breadcrumbs view - renders breadcrumb links
*/
(function ( $ ) {
module.exports = Backbone.View.extend( {
el: $( '#tab-breadcrumbs-wrapper' )[0],
template: TVE_Dash.tpl( 'breadcrumbs' ),
/**
* setup collection listeners
*/
initialize: function () {
this.$title = $( 'head > title' );
this.original_title = this.$title.html();
this.listenTo( this.collection, 'change', this.render );
this.listenTo( this.collection, 'add', this.render );
},
/**
* render the html
*/
render: function () {
this.$el.empty().html( this.template( {links: this.collection} ) );
}
} );
})( jQuery );

View File

@@ -0,0 +1,91 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 1/16/2018
* Time: 2:17 PM
*/
var TestListView = require( './test-list' ),
TestPagination = require( './test-pagination' );
module.exports = Backbone.View.extend( {
template: TVE_Dash.tpl( 'dashboard' ),
events: {
'keyup .tab-running-search-input': 'search_tests',
'keyup .tab-completed-search-input': 'search_tests'
},
running_test_pagination: null,
completed_test_pagination: null,
initialize: function ( args ) {
this.running_tests = args.running_tests;
this.completed_tests = args.completed_tests;
this.dashboard_stats = args.dashboard_stats;
this.listenTo( this.completed_tests, 'remove', function () {
this.completed_test_pagination.changePage();
}, this );
},
render: function () {
this.$el.html( this.template( {stats: this.dashboard_stats} ) );
this.render_running_tests();
this.render_completed_tests();
return this;
},
render_running_tests: function () {
var running_test_list = new TestListView( {
template_item: TVE_Dash.tpl( 'running-test-item' ),
template_no_item: TVE_Dash.tpl( 'running-test-no-item' ),
template_no_search_item: TVE_Dash.tpl( 'running-test-no-search-item' ),
el: this.$el.find( '.tab-running-test-items-list' ),
collection: this.running_tests
} );
this.running_test_pagination = new TestPagination( {
collection: this.running_tests,
view: running_test_list,
el: this.$el.find( '.tab-running-pagination' )
} );
this.running_test_pagination.changePage();
},
render_completed_tests: function () {
var completed_test_list = new TestListView( {
template_item: TVE_Dash.tpl( 'completed-test-item' ),
template_no_item: TVE_Dash.tpl( 'completed-test-no-item' ),
template_no_search_item: TVE_Dash.tpl( 'completed-test-no-search-item' ),
el: this.$el.find( '.tab-completed-test-items-list' ),
collection: this.completed_tests
} );
this.completed_test_pagination = new TestPagination( {
collection: this.completed_tests,
view: completed_test_list,
el: this.$el.find( '.tab-completed-pagination' )
} );
this.completed_test_pagination.changePage();
},
search_tests: function ( e ) {
var search = jQuery( e.target ).val(),
pagination;
if ( e.currentTarget.className.indexOf( 'running' ) !== - 1 ) {
pagination = this.running_test_pagination;
} else if ( e.currentTarget.className.indexOf( 'completed' ) !== - 1 ) {
pagination = this.completed_test_pagination;
} else {
return;
}
pagination.changePage( null, {
page: 1,
search_by: search
} );
}
} );

View File

@@ -0,0 +1,69 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 1/16/2018
* Time: 4:55 PM
*/
var delete_test_modal = require( './../../modals/delete' );
module.exports = Backbone.View.extend( {
template: '',
tagName: 'tr',
events: {
'click .tab-delete-test': 'delete_test'
},
initialize: function ( attr ) {
this.template = attr.template;
},
render: function () {
this.$el.html( this.template( {model: this.model} ) );
return this;
},
delete_test: function () {
if ( this.model.get( 'status' ) !== 'completed' ) {
return;
}
TVE_Dash.modal( delete_test_modal, {
submit: _.bind( function () {
TVE_Dash.showLoader();
var self = this;
jQuery.ajax( {
cache: false,
url: ThriveAbAdmin.ajax.url,
method: 'POST',
dataType: 'json',
data: {
id: this.model.get( 'id' ),
page_id: this.model.get( 'page_id' ),
route: 'deletecompletedtestadmin',
action: ThriveAbAdmin.ajax.action,
custom: ThriveAbAdmin.ajax.controller_action,
nonce: ThriveAbAdmin.ajax.nonce
}
} ).done( function ( response ) {
if ( response.success ) {
self.collection.remove( self.model );
TVE_Dash.success( response.text );
} else {
TVE_Dash.err( response.text );
}
TVE_Dash.hideLoader();
} );
}, this ),
title: '',
description: TVE_Dash.sprintf( ThriveAbAdmin.t.about_to_delete_variation, ['<strong>' + this.model.get( 'title' ) + '</strong>'] ),
btn_yes_txt: ThriveAbAdmin.t.yes,
btn_no_txt: ThriveAbAdmin.t.no,
'max-width': '20%',
width: '20%'
} );
}
} );

View File

@@ -0,0 +1,51 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 1/16/2018
* Time: 1:31 PM
*/
var TestItemView = require( './test-item' );
module.exports = Backbone.View.extend( {
template: '',
events: {},
initialize: function ( attr ) {
this.template_item = attr.template_item;
this.template_no_item = attr.template_no_item;
this.template_no_search_item = attr.template_no_search_item;
this.collection = attr.collection;
},
render: function ( collection, c_source ) {
var c = this.collection;
this.$el.empty();
if ( typeof collection !== 'undefined' ) {
c = new Backbone.Collection( collection );
}
if ( c.length === 0 ) {
var _no_results_template = this.template_no_item();
if ( c_source === 'search_by' ) {
_no_results_template = this.template_no_search_item();
}
this.$el.html( _no_results_template );
} else {
c.each( this.renderOne, this );
}
return this;
},
renderOne: function ( item ) {
var view = new TestItemView( {
template: this.template_item,
collection: this.collection,
model: item
} );
this.$el.append( view.render().$el );
}
} );

View File

@@ -0,0 +1,124 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 1/16/2018
* Time: 5:24 PM
*/
module.exports = Backbone.View.extend( {
template: TVE_Dash.tpl( 'pagination' ),
events: {
'click a.page': 'changePage',
'change .tab-items-per-page': 'changeItemPerPage'
},
currentPage: 1,
pageCount: 1,
itemsPerPage: 10,
total_items: 0,
collection: null,
params: null,
view: null,
initialize: function ( options ) {
this.collection = options.collection;
this.view = options.view;
},
changeItemPerPage: function ( event ) {
this.itemsPerPage = jQuery( event.target ).val();
this.changePage( null, {page: 1} );
},
changePage: function ( event, args ) {
var data = {
itemsPerPage: this.itemsPerPage
};
/* Set the current page of the pagination. This can be changed by clicking on a page or by just calling this method with params */
if ( event && typeof event.currentTarget !== 'undefined' ) {
data.page = jQuery( event.currentTarget ).attr( 'value' );
} else if ( args && typeof args.page !== 'undefined' ) {
data.page = parseInt( args.page );
} else {
data.page = this.currentPage;
}
/* just to make sure */
if ( data.page < 1 ) {
data.page = 1;
}
/* Parse args sent to pagination */
if ( typeof args !== 'undefined' ) {
/* When "per page" filter changes, those values are not sent so we save them in the view so we can have them for later */
if ( typeof args.search_by !== 'undefined' ) {
this.search_by = args.search_by;
}
}
/* In case we've saved this before */
data.search_by = this.search_by ? this.search_by.toLowerCase() : '';
if ( typeof this.view != 'undefined' && this.view != null ) {
/* Prepare params for pagination render */
this.updateParams( data.page, this.collection.length );
var currentCollection = this.collection.clone(),
from = (this.currentPage - 1) * this.itemsPerPage,
collectionSlice,
removeIds = [],
c_source = '';
if ( typeof currentCollection.comparator !== 'undefined' ) {
currentCollection.sort();
}
if ( data.search_by ) {
currentCollection.each( function ( model ) {
var title = model.get( 'title' ).toLowerCase(),
goal_pages = JSON.stringify( model.get( 'goal_pages' ) ).toLowerCase(),
page_title = model.get( 'page_title' ).toLowerCase();
if ( title.indexOf( data.search_by ) === - 1 && goal_pages.indexOf( data.search_by ) === - 1 && page_title.indexOf( data.search_by ) === - 1 ) {
removeIds.push( model );
}
} );
for ( var i in removeIds ) {
currentCollection.remove( removeIds[i] );
}
c_source = 'search_by';
}
collectionSlice = currentCollection.chain().rest( from ).first( this.itemsPerPage ).value();
/* render sliced view collection */
this.view.render( collectionSlice, c_source );
/* render pagination */
this.render();
}
return false;
},
updateParams: function ( page, total ) {
this.currentPage = page;
this.total_items = total;
this.pageCount = Math.ceil( this.total_items / this.itemsPerPage );
},
setupParams: function ( page ) {
this.currentPage = page;
this.total_items = this.collection.length;
this.pageCount = Math.ceil( this.total_items / this.itemsPerPage );
},
render: function () {
this.$el.html( this.template( {
currentPage: parseInt( this.currentPage ),
pageCount: parseInt( this.pageCount ),
total_items: parseInt( this.total_items ),
itemsPerPage: parseInt( this.itemsPerPage )
} ) );
TVE_Dash.materialize( this.$el );
return this;
}
} );

View File

@@ -0,0 +1,127 @@
var traffic_model = require( '../models/traffic' );
module.exports = Backbone.Collection.extend( {
/**
* Based on the excluded_model traffic
* Distributes the remaining traffic equally to the other models in collection
*
* @param excluded_model
*/
distribute_traffic: function ( excluded_model ) {
if ( this.length <= 1 ) {
return;
}
var _new_traffic = parseInt( (100 - excluded_model.get( 'traffic' )) / (this.length - 1) );
this.each( function ( model ) {
if ( model.get( model.idAttribute ) !== excluded_model.get( excluded_model.idAttribute ) ) {
model.set( 'traffic', _new_traffic );
}
}, this );
var _rest = (100 - excluded_model.get( 'traffic' )) % (this.length - 1);
/**
* add rest to 1st or last element from collection
*/
if ( _rest ) {
var _last_model = this.last();
if ( _last_model.get( _last_model.idAttribute ) === excluded_model.get( excluded_model.idAttribute ) ) {
this.first().set( 'traffic', _new_traffic + _rest );
} else {
_last_model.set( 'traffic', _new_traffic + _rest );
}
}
},
/**
* split the removed traffic equally to remained items
*
* @param excluded_model
* @param traffic
*/
split_traffic: function ( excluded_model, traffic ) {
var split_traffic = parseInt( traffic / (this.length - 1) );
this.each( function ( model ) {
if ( model.get( model.idAttribute ) === excluded_model.get( excluded_model.idAttribute ) ) {
return;
}
model.set( 'traffic', model.get( 'traffic' ) + split_traffic );
}, this );
var _rest = parseInt( traffic % (this.length - 1) );
/**
* add rest to 1st or last element from collection
*/
if ( _rest ) {
var _last_model = this.last();
if ( _last_model.get( _last_model.idAttribute ) === excluded_model.get( excluded_model.idAttribute ) ) {
var _first = this.first();
_first.set( 'traffic', _first.get( 'traffic' ) + _rest );
} else {
_last_model.set( 'traffic', _last_model.get( 'traffic' ) + _rest );
}
}
},
/**
* Create a new Traffic Model with traffic prop from the collection
* and send it to server to be saved
*/
save_distributed_traffic: function () {
var traffic = new traffic_model();
this.each( function ( model ) {
traffic.set( model.get( model.idAttribute ), model.get( 'traffic' ) );
}, this );
traffic.save();
},
equalize_traffic: function () {
var new_traffic = parseInt( 100 / this.length ),
mod = 100 % this.length;
this.each( function ( model ) {
model.set( 'traffic', new_traffic );
} );
this.findWhere( {is_control: true} ).set( 'traffic', new_traffic + mod );
},
allocate_traffic: function ( excluded_model, diff ) {
this.each( function ( model ) {
if ( model.get( model.idAttribute ) === excluded_model.get( excluded_model.idAttribute ) ) {
return;
}
var traffic = parseInt( model.get( 'traffic' ) );
var _new_traffic = traffic + diff;
if ( _new_traffic < 0 ) {
diff = _new_traffic;
_new_traffic = 0;
} else if ( _new_traffic > 100 ) {
diff = _new_traffic - 100;
_new_traffic = 100;
} else {
diff = 0;
}
model.set( 'traffic', _new_traffic );
}, this );
}
} );

View File

@@ -0,0 +1,19 @@
var base = require( './base' ),
traffic_model = require( '../models/traffic' );
module.exports = base.extend( {
model: require( '../models/test-item' ),
save_distributed_traffic: function () {
var traffic = new traffic_model();
this.each( function ( model ) {
traffic.set( model.get( 'variation_id' ), model.get( 'traffic' ) );
}, this );
traffic.save();
}
} );

View File

@@ -0,0 +1,7 @@
var base = require( './base' );
module.exports = base.extend( {
model: require( '../models/variation' )
} );

View File

@@ -0,0 +1,59 @@
var base_view = require( './../views/base' );
module.exports = base_view.extend( {
className: 'tvd-input-field',
template: TVE_Dash.tpl( 'util/edit-title' ),
events: {
'keyup input': 'keyup',
'change input': function () {
var trim_value = this.input.val().trim();
if ( ! trim_value ) {
this.input.addClass( 'tvd-invalid' );
return false;
}
this.model.set( 'post_title', trim_value );
return false;
},
'blur input': function () {
this.model.trigger( 'thrive-ab-title-no-change' );
}
},
initialize: function ( args ) {
var is_valid_args = true;
if ( ! args ) {
is_valid_args = false;
}
if ( typeof args !== 'object' ) {
is_valid_args = false;
}
if ( typeof args === 'object' && typeof args.el === 'undefined' ) {
is_valid_args = false;
}
if ( ! is_valid_args ) {
console.log( 'Error: invalid arguments for edit title control' );
return;
}
this.render();
},
keyup: function ( event ) {
if ( event.which === 27 ) {
this.model.trigger( 'thrive-ab-title-no-change' );
}
},
render: function () {
this.$el.html( this.template( {item: this.model} ) );
this.input = this.$( 'input' );
return this;
},
focus: function () {
this.input.focus().select();
}
} );

View File

@@ -0,0 +1,172 @@
/**
* Autocomplete input
*/
var base_view = Backbone.View,
base_model = require( './../models/base' );
var page_model = base_model.extend( {
defaults: function () {
return {
post_id: '',
post_title: ''
};
},
validate: function ( attrs ) {
var _errors = [];
if ( ! attrs.post_title || ! attrs.post_id ) {
_errors.push( this.validation_error( 'post_title', 'Page not selected' ) );
}
return _errors.length ? _errors : undefined;
}
} );
var view = base_view.extend( {
className: 'tvd-input-field thrive-ab-page-search',
template: TVE_Dash.tpl( 'util/page-search' ),
initialize: function ( attrs ) {
if ( ! attrs || ! attrs.model ) {
this.model = new page_model();
}
this.goal_pages = attrs.goal_pages;
this.on( 'select', this.select );
this.on( 'save-page', this.save_page );
},
render: function () {
this.$el.html( this.template( {item: this.model} ) );
TVE_Dash.data_binder( this );
this.$autocomplete = this.$( '.page-search' );
this.$autocomplete.on( 'input', _.bind( function () {
this.model.set( {
post_id: null
} );
this.$( '.thrive-ab-edit-page' ).attr( 'href', 'javascript:void(0)' ).addClass( 'tvd-btn-flat-secondary' );
this.$( '.thrive-ab-preview-page' ).attr( 'href', 'javascript:void(0)' ).addClass( 'tvd-btn-flat-secondary' );
}, this ) );
this.bind();
return this;
},
select: function ( item ) {
this.$( '.thrive-ab-edit-page' ).attr( 'href', item.edit_url ).removeClass( 'tvd-btn-flat-secondary' ).show();
this.$( '.thrive-ab-preview-page' ).attr( 'href', item.url ).removeClass( 'tvd-btn-flat-secondary' ).show();
},
save_page: function ( item ) {
var self = this,
_options = {
title: item.title
};
TVE_Dash.showLoader();
ThriveAB.ajax.do( 'add_new_page', 'post', _options ).done( function ( response ) {
self.model.set( {
post_id: response.post.ID,
post_title: response.post.post_title
} );
self.select( {
edit_url: response.post.edit_url,
url: response.post.guid
} );
TVE_Dash.hideLoader();
} );
},
bind: function () {
var self = this,
last,
cache;
this.$autocomplete.autocomplete( {
appendTo: this.$el,
minLength: 2,
delay: 300,
source: function ( request, response ) {
var exclude_ids;
if ( self.goal_pages ) {
exclude_ids = Object.keys( self.goal_pages );
exclude_ids.push( ThriveAB.page.ID );
} else {
exclude_ids = [ThriveAB.page.ID];
}
var _options = {
q: request.term,
exclude_id: exclude_ids
};
ThriveAB.ajax.do( 'post_search', 'post', _options )
.done( function ( data ) {
if ( true || data.length === 0 ) {
data.push( {
title: request.term,
label: request.term,
type: 'Create New Page',
value: request.term
} );
}
cache = data;
response( data );
} );
last = request.term;
},
select: function ( event, ui ) {
self.$autocomplete.val( ui.item.title );
self.model.set( {
post_id: ui.item.id,
post_title: ui.item.title
} );
if ( ui.item.id ) {
self.trigger( 'select', ui.item );
}
return false;
}
} );
this.$autocomplete.data( 'ui-autocomplete' )._renderItem = function ( ul, item ) {
var $li = jQuery( '<li/>' );
if ( typeof item.id === 'undefined' ) {
$li.addClass( 'ui-not-found' );
$li.click( function () {
self.trigger( 'save-page', item );
} );
}
var _html;
if ( item.type === 'Create New Page' ) {
_html = '<span class="post-name" style="width: 60%">' + item.label + '</span><span class="post-type" style="width: 40%">' + item.type + '</span>';
} else {
_html = '<span class="post-name">' + item.label + '</span><span class="post-type">' + item.type + '</span>';
}
$li.append( _html ).appendTo( ul );
return $li;
};
}
} );
module.exports = {
model: page_model,
view: view
};

View File

@@ -0,0 +1,82 @@
var base = require( '../views/base' );
module.exports = base.extend( {
initialize: function () {
base.prototype.initialize.apply( this, arguments );
this.$( 'input' ).attr( 'data-value', this.model.get( 'traffic' ) );
this.listenTo( this.model, 'change:traffic', function ( model, _value ) {
this.$( 'input' ).val( _value );
this.$( 'input' ).attr( 'data-value', _value );
} );
if ( this.model.collection.length === 1 ) {
this.disable();
}
},
on_change: function ( event ) {
this.model.collection.save_distributed_traffic();
return false;
},
on_input: function ( event ) {
var _value = event.target.value;
if ( _value.length <= 0 ) {
event.target.value = 0;
}
if ( isNaN( parseInt( _value ) ) || parseInt( _value ) > 100 ) {
_value = _value.substring( 0, _value.length - 1 );
}
if ( isNaN( parseInt( _value ) ) ) {
_value = 0;
}
event.target.value = parseInt( _value );
var _diff = parseInt( event.target.dataset.value ) - _value;
_diff = parseInt(_diff);
this.set_traffic( parseInt( event.target.value ), _diff );
return false;
},
set_traffic: function ( value, diff ) {
var _value = parseInt( value );
if ( isNaN( _value ) ) {
_value = 0;
}
if ( _value < 0 ) {
_value = 0;
}
if ( _value > 100 ) {
_value = 100;
}
this.model.set( 'traffic', _value );
//this.model.collection.distribute_traffic( this.model );
var traffic_alocated = this.model.collection.allocate_traffic( this.model, diff );
},
enable: function () {
this.$( 'input' ).removeAttr( 'disabled' );
},
disable: function () {
this.$( 'input' ).attr( 'disabled', 'disabled' );
}
} );

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(()=>{var a;(a=jQuery)((function(){var t=a(".thrive-ab-traffic-input"),i={};a.each(t,(function(a,t){i[t.dataset.tab_variation_id]=parseInt(t.dataset.tab_variation_value)})),t.on("change",(function(){jQuery.ajax({cache:!1,url:ThriveAbEditPost.ajax.url,method:"POST",dataType:"json",data:{tab_edit_post_traffic:i,route:"traffic",action:ThriveAbEditPost.ajax.action,custom:ThriveAbEditPost.ajax.controller_action,nonce:ThriveAbEditPost.ajax.nonce}}).done((function(a){}))})),t.on("input",(function(t){var r=t.target.value,e=t.target.dataset.tab_variation_id;r.length<=0&&(t.target.value=0),(isNaN(parseInt(r))||parseInt(r)>100)&&(r=r.substring(0,r.length-1)),isNaN(parseInt(r))&&(r=0),(r=parseInt(r))<0&&(r=0),r>100&&(r=100);var n,v,o=parseInt(t.target.dataset.tab_variation_value)-r;i[e]=r,a("#thrive-ab-traffic-range-"+e).val(r).attr("data-tab_variation_value",r),a("#thrive-ab-traffic-input-"+e).val(r).attr("data-tab_variation_value",r),n=e,v=o,a.each(i,(function(t,r){if(t===n)return!0;var e=r+v;e<0?(v=e,e=0):e>100?(v=e-100,e=100):v=0,i[t]=e,a("#thrive-ab-traffic-range-"+t).val(e).attr("data-tab_variation_value",e),a("#thrive-ab-traffic-input-"+t).val(e).attr("data-tab_variation_value",e)}))}))}))})();

View File

@@ -0,0 +1 @@
(()=>{var t={1889:t=>{var e=null;t.exports=TVE.modal.base.extend({events:function(){return _.extend({},TVE.modal.base.prototype.events(),{"click .tcb-modal-save":"reset_stats"})},reset_stats:function(){this.running_test=!1,this.$("#thrive-ab-reset-stats").is(":checked")&&(this.running_test=TVE.CONST.ajax.thrive_ab.running_test),this.trigger("reset_stats",this.running_test),TVE.main.overlay(),this.close()},after_initialize:function(){this.$el.addClass("medium")}},{get_instance:function(t){return e||(e=new TVE.ResetStatsModal({el:t})),e}})}},e={};function s(a){var n=e[a];if(void 0!==n)return n.exports;var i=e[a]={exports:{}};return t[a](i,i.exports,s),i.exports}!function(t){TVE.ResetStatsModal=s(1889);var e={selector:"body",iframe_srcs:[],init:function(){TVE.CONST.ajax.thrive_ab.running_test?TVE.add_filter("validate_saved_content",t.proxy(this.validate_saved_content_filter,this)):TVE.main.on("tve.save_post.success",t.proxy(this.on_save,this)),TVE.add_filter("tcb.edit_mode.disabled_buttons",(function(t){return t.add(TVE.main.$("#thrive-ab-create-test"))}))},validate_saved_content_filter:function(t,e){var s=this;if(s.save_callback=e,void 0===TVE.CONST.reset_stats){var a=TVE.ResetStatsModal.get_instance(TVE.modal.get_element("reset-stats"));return a.open({top:"20%"}),delete TVE.KEEP_OVERLAY,TVE.main.overlay("close"),a.on("reset_stats",s.save_with_reset_stats,s),!1}return TVE.CONST.reset_stats||delete TVE.CONST.reset_stats,!0},save_with_reset_stats:function(t){TVE.main.overlay(),TVE.CONST.reset_stats=t,this.on_save()},on_save:function(){if(!TVE.PreventPreviewImageGenerate){var t=TVE.inner_$(this.selector);TVE.generateElementPreview(t,this.save.bind(this),{bgcolor:"white",style:{padding:0,margin:0,outline:"none","overflow-y":"hidden"},width:t.width(),height:1e3,imageTypeCallback:"toJpeg"},!0)}},save:function(e){var s=new FormData;e&&s.append("preview_file",TVE.base64ToBlob(e),TVE.CONST.post_id+".png"),s.append("custom","save_variation_thumb"),s.append("action",TVE.CONST.ajax.thrive_ab.action),s.append("post_id",TVE.CONST.post_id),void 0!==TVE.CONST.reset_stats&&s.append("reset_data",TVE.CONST.reset_stats),t.ajax({type:"POST",url:TVE.CONST.ajax_url,data:s,processData:!1,contentType:!1}),void 0!==TVE.CONST.reset_stats&&TVE.main.editor_settings.save(null,null,this.save_callback)}};!function t(){if(void 0===TVE.inner)return setTimeout(t,100);e.init()}()}(jQuery)})();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(()=>{var o,t=t||{$j:jQuery.noConflict()};ThriveAB=ThriveAB||{},(o=t.$j)((function(){ThriveAB.dashboard_hook()})),void 0!==ThriveAB.test_type&&"optins"===ThriveAB.test_type&&o("body").off("should_submit_form.tcb").on("should_submit_form.tcb",".thrv_lead_generation",(function(o){return o.flag_need_data=!0,!0})),ThriveAB.dashboard_hook=function(){"undefined"!=typeof TVE_Dash&&!0!==TVE_Dash.ajax_sent&&o(document).on("tve-dash.load",(function(){TVE_Dash.add_load_item("top_lazy_load",ThriveAB.impression_data)}))}})();

View File

@@ -0,0 +1,104 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 11/16/2017
* Time: 4:27 PM
*/
(function ( $ ) {
//DOM ready
$( function () {
var $traffic_input = $( '.thrive-ab-traffic-input' ),
variations = {};
$.each( $traffic_input, function ( index, input ) {
variations[input.dataset.tab_variation_id] = parseInt( input.dataset.tab_variation_value );
} );
$traffic_input.on( 'change', function () {
tab_save_traffic();
} );
$traffic_input.on( 'input', function ( event ) {
var _value = event.target.value,
_variation_id = event.target.dataset.tab_variation_id;
if ( _value.length <= 0 ) {
event.target.value = 0;
}
if ( isNaN( parseInt( _value ) ) || parseInt( _value ) > 100 ) {
_value = _value.substring( 0, _value.length - 1 );
}
if ( isNaN( parseInt( _value ) ) ) {
_value = 0;
}
_value = parseInt( _value );
if ( _value < 0 ) {
_value = 0;
}
if ( _value > 100 ) {
_value = 100;
}
var _diff = parseInt( event.target.dataset.tab_variation_value ) - _value;
variations[_variation_id] = _value;
$( '#thrive-ab-traffic-range-' + _variation_id ).val( _value ).attr( 'data-tab_variation_value', _value );
$( '#thrive-ab-traffic-input-' + _variation_id ).val( _value ).attr( 'data-tab_variation_value', _value );
tab_allocate_traffic( _variation_id, _diff );
} );
function tab_allocate_traffic( selected_variation_id, diff ) {
$.each( variations, function ( variation_id, traffic ) {
if ( variation_id === selected_variation_id ) {
//Ignores the selected variation
return true;
}
var _new_traffic = traffic + diff;
if ( _new_traffic < 0 ) {
diff = _new_traffic;
_new_traffic = 0;
} else if ( _new_traffic > 100 ) {
diff = _new_traffic - 100;
_new_traffic = 100;
} else {
diff = 0;
}
variations[variation_id] = _new_traffic;
$( '#thrive-ab-traffic-range-' + variation_id ).val( _new_traffic ).attr( 'data-tab_variation_value', _new_traffic );
$( '#thrive-ab-traffic-input-' + variation_id ).val( _new_traffic ).attr( 'data-tab_variation_value', _new_traffic );
} );
}
function tab_save_traffic() {
jQuery.ajax( {
cache: false,
url: ThriveAbEditPost.ajax.url,
method: 'POST',
dataType: 'json',
data: {
tab_edit_post_traffic: variations,
route: 'traffic',
action: ThriveAbEditPost.ajax.action,
custom: ThriveAbEditPost.ajax.controller_action,
nonce: ThriveAbEditPost.ajax.nonce
}
} ).done( function ( response ) {
} );
}
} );
})( jQuery );

View File

@@ -0,0 +1,104 @@
( function ( $ ) {
TVE.ResetStatsModal = require( './modals/reset-stats' );
var ThriveAB = {
selector: 'body',
iframe_srcs: [],
init: function () {
var self = this;
if ( TVE.CONST.ajax.thrive_ab.running_test ) {
TVE.add_filter( 'validate_saved_content', $.proxy( this.validate_saved_content_filter, this ) );
} else {
TVE.main.on( 'tve.save_post.success', $.proxy( this.on_save, this ) );
}
/* disable "CREATE NEW A/B TEST" button when an element enters edit mode */
TVE.add_filter( 'tcb.edit_mode.disabled_buttons', function ( $buttons ) {
return $buttons.add( TVE.main.$( '#thrive-ab-create-test' ) );
} );
},
validate_saved_content_filter: function ( valid, callback ) {
var self = this;
self.save_callback = callback;
if ( typeof TVE.CONST.reset_stats === 'undefined' ) {
var ResetStatsModal = TVE.ResetStatsModal.get_instance( TVE.modal.get_element( 'reset-stats' ) );
ResetStatsModal.open( {
top: '20%'
} );
delete TVE.KEEP_OVERLAY;
TVE.main.overlay( 'close' );
ResetStatsModal.on( 'reset_stats', self.save_with_reset_stats, self );
return false;
}
if ( ! TVE.CONST.reset_stats ) {
delete TVE.CONST.reset_stats;
}
return true;
},
save_with_reset_stats: function ( running_test ) {
TVE.main.overlay();
TVE.CONST.reset_stats = running_test;
this.on_save();
},
on_save: function () {
if ( ! TVE.PreventPreviewImageGenerate ) {
var $element = TVE.inner_$( this.selector );
TVE.generateElementPreview( $element, this.save.bind( this ), {
bgcolor: 'white',
style: {
padding: 0,
margin: 0,
outline: 'none',
'overflow-y': 'hidden'
},
width: $element.width(),
height: 1000,
imageTypeCallback: 'toJpeg'
}, true );
}
},
save: function ( data_source ) {
var form = new FormData();
if ( data_source ) {
form.append( 'preview_file', TVE.base64ToBlob( data_source ), TVE.CONST.post_id + '.png' );
}
form.append( 'custom', 'save_variation_thumb' );
form.append( 'action', TVE.CONST.ajax.thrive_ab.action );
form.append( 'post_id', TVE.CONST.post_id );
if ( typeof TVE.CONST.reset_stats !== 'undefined' ) {
form.append( 'reset_data', TVE.CONST.reset_stats );
}
$.ajax( {
type: 'POST',
url: TVE.CONST.ajax_url,
data: form,
processData: false,
contentType: false,
} );
if ( typeof TVE.CONST.reset_stats !== 'undefined' ) {
TVE.main.editor_settings.save( null, null, this.save_callback );
}
}
};
function check_editor() {
if ( TVE.inner === undefined ) {
return setTimeout( check_editor, 100 );
}
ThriveAB.init();
}
check_editor();
} )( jQuery );

View File

@@ -0,0 +1,50 @@
/**
* Required in frontend only, when a variation is displayed
* - to allow TCB trigger ajax request for custom html forms to register conversion on TOP
* - to register impressions for a variation by TD Lazy Loading
*/
var ThriveGlobal = ThriveGlobal || {$j: jQuery.noConflict()};
ThriveAB = ThriveAB || {};
(function ( $ ) {
/**
* DOM Ready
*/
$( function () {
//hook into dashboard ajax request
ThriveAB.dashboard_hook();
} );
/**
* In case on current variation exists a LG Element with custom html
* we need to set some data on submit() event to allow conversions to be registered
*/
if ( typeof ThriveAB.test_type !== 'undefined' && ThriveAB.test_type === 'optins' ) {
$( 'body' ).off( 'should_submit_form.tcb' ).on( 'should_submit_form.tcb', '.thrv_lead_generation', function ( event ) {
event.flag_need_data = true;
return true;
} );
}
/**
* Try to hook into dashboard ajax lazy load request
* and inject some data to pe processed by TOP on server, usually register impression
*/
ThriveAB.dashboard_hook = function () {
if ( typeof TVE_Dash === 'undefined' || TVE_Dash.ajax_sent === true ) {
return;
}
$( document ).on( 'tve-dash.load', function () {
/**
* assign some data on dash request to be caught on server
* @see Thrive_AB_Ajax:dashboard_lazy_load()
*/
TVE_Dash.add_load_item( 'top_lazy_load', ThriveAB.impression_data );
} );
};
})( ThriveGlobal.$j );

View File

@@ -0,0 +1,32 @@
(function ( $ ) {
//DOM ready
$( function () {
var ThriveAB = window.ThriveAB || {},
util = require( './util' ),
router = require( './router' );
_.extendOwn( ThriveAB.ajax, util.ajax );
Backbone.emulateHTTP = true;
ThriveAB.router = new router();
Backbone.history.stop();
Backbone.history.start();
if ( ! Backbone.history.fragment ) {
if ( ThriveAB.current_test ) {
return ThriveAB.router.navigate( '#test', {trigger: true} );
}
if ( ThriveAB.running_test ) {
ThriveAB.router.navigate( '#test/' + ThriveAB.running_test.id, {trigger: true} );
} else {
ThriveAB.router.navigate( '#dashboard', {trigger: true} );
}
}
} );
})( jQuery );

View File

@@ -0,0 +1,35 @@
/**
* General Archive Modal
*/
module.exports = TVE_Dash.views.Modal.extend( {
template: TVE_Dash.tpl( 'modals/html-archive' ),
events: {
'click .tvd-modal-submit': 'submit'
},
initialize: function ( args ) {
TVE_Dash.views.Modal.prototype.initialize.apply( this, arguments );
this.$el.addClass( 'tvd-red' );
},
render: function () {
TVE_Dash.views.Modal.prototype.render.apply( this, arguments );
this.$( '.tvd-modal-close' ).addClass( 'tvd-white-text' );
return this;
},
submit: function () {
if ( typeof this.data['submit'] !== 'function' ) {
throw new Error( 'Submit data not implemented' );
}
this.data.submit.apply( this, arguments );
this.close();
}
} );

View File

@@ -0,0 +1,60 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 12/14/2017
* Time: 1:04 PM
*/
module.exports = TVE_Dash.views.Modal.extend( {
template: TVE_Dash.tpl( 'modals/html-change-automatic-winner' ),
events: {
'click .tvd-modal-submit': 'submit'
},
afterInitialize: function ( args ) {
TVE_Dash.views.Modal.prototype.afterInitialize.apply( this, arguments );
this.listenTo( this.model, 'change:auto_win_enabled', this.toggle_settings_input );
},
afterRender: function () {
TVE_Dash.views.Modal.prototype.afterRender.apply( this, arguments );
TVE_Dash.data_binder( this );
this.toggle_settings_input();
},
toggle_settings_input: function () {
var _inputs = this.$( '#auto-win-settings input' ),
_auto_win = this.model.get( 'auto_win_enabled' );
this.$( '#auto-win-enabled' ).prop( 'checked', _auto_win == 1 );
if ( _inputs.length ) {
if ( _auto_win == 1 ) {
_inputs.removeAttr( 'disabled' );
} else {
var defaults = this.model.defaults();
delete defaults.id;
this.model.set( defaults );
_inputs.attr( 'disabled', 'disabled' );
}
}
},
submit: function () {
if ( ! this.model.isValid( {step: 'winner_settings'} ) ) {
return;
}
var save_options = {
save_test_settings: true
};
this.model.save( save_options, {
success: _.bind( function () {
this.close();
}, this ),
error: _.bind( function () {
this.close();
}, this )
} );
}
} );

View File

@@ -0,0 +1,35 @@
/**
* General Delete Modal
*/
module.exports = TVE_Dash.views.Modal.extend( {
template: TVE_Dash.tpl( 'modals/html-delete' ),
events: {
'click .tvd-modal-submit': 'submit'
},
initialize: function ( args ) {
TVE_Dash.views.Modal.prototype.initialize.apply( this, arguments );
this.$el.addClass( 'tvd-red' );
},
render: function () {
TVE_Dash.views.Modal.prototype.render.apply( this, arguments );
this.$( '.tvd-modal-close' ).addClass( 'tvd-white-text' );
return this;
},
submit: function () {
if ( typeof this.data['submit'] !== 'function' ) {
throw new Error( 'Submit data not implemented' );
}
this.data.submit.apply( this, arguments );
this.close();
}
} );

View File

@@ -0,0 +1,33 @@
module.exports = TVE_Dash.views.Modal.extend( {
initialize: function ( args ) {
if ( this.model.get( 'type' ) === 'monetary' && this.model.get( 'goal_pages' ) === 'sendowl' ) {
this.template = TVE_Dash.tpl( 'modals/goal/sendowl' );
} else {
this.template = TVE_Dash.tpl( 'modals/goal/' + this.model.get( 'type' ) );
}
TVE_Dash.views.Modal.prototype.initialize.apply( this, arguments );
},
render: function () {
TVE_Dash.views.Modal.prototype.render.apply( this, arguments );
var _tpl;
if ( this.model.get( 'type' ) === 'monetary' ) {
_tpl = TVE_Dash.tpl( 'modals/goal/revenue-row' );
} else if ( this.model.get( 'type' ) === 'visits' ) {
_tpl = TVE_Dash.tpl( 'modals/goal/page-row' );
}
if ( _tpl ) {
this.collection.each( function ( item ) {
this.$( '.thrive-ap-goal-pages' ).append( _tpl( {model: item} ) );
}, this );
}
return this;
},
} );

View File

@@ -0,0 +1,38 @@
var _instance = null;
module.exports = TVE.modal.base.extend( {
events: function () {
return _.extend( {}, TVE.modal.base.prototype.events(), {
'click .tcb-modal-save': 'reset_stats'
} );
},
reset_stats: function () {
this.running_test = false;
if ( this.$( '#thrive-ab-reset-stats' ).is( ':checked' ) ) {
this.running_test = TVE.CONST.ajax.thrive_ab.running_test;
}
this.trigger('reset_stats', this.running_test );
TVE.main.overlay();
this.close();
},
after_initialize: function () {
this.$el.addClass( 'medium' );
}
}, {
/**
* "Singleton" implementation for modal instance
*
* @param el
*/
get_instance: function ( el ) {
if ( ! _instance ) {
_instance = new TVE.ResetStatsModal( {
el: el
} );
}
return _instance;
}
} );

View File

@@ -0,0 +1,133 @@
var optins_settings = require( '../views/goals/optins-settings' ),
monetary_settings = require( '../views/goals/monetary-settings' ),
visits_settings = require( '../views/goals/visits-settings' );
module.exports = TVE_Dash.views.ModalSteps.extend( {
className: 'tvd-modal tvd-modal-fixed-footer',
template: TVE_Dash.tpl( 'modals/html-test' ),
events: function () {
return _.extend( TVE_Dash.views.ModalSteps.prototype.events, {
'click .tvd-modal-submit': 'submit',
'click .thrive-ab-goal': function ( event ) {
if ( event.currentTarget.classList.contains( 'thrive-ab-disabled' ) ) {
return false;
}
this.$( ".thrive-ab-goal" ).removeClass( "thrive-ab-selected" );
event.currentTarget.classList.add( "thrive-ab-selected" );
jQuery( '.tvd-modal-content' ).animate( {
scrollTop: jQuery( "#thrive-ab-goal-settings" ).offset().top
}, 500 );
this.model.set( 'type', event.currentTarget.dataset.goal );
}
} );
},
initialize: function ( args ) {
TVE_Dash.views.ModalSteps.prototype.initialize.apply( this, arguments );
this.listenTo( this.model, 'change:type', this.render_goal_settings );
this.listenTo( this.model, 'change:auto_win_enabled', this.toggle_settings_input );
},
afterRender: function () {
TVE_Dash.views.ModalSteps.prototype.afterRender.apply( this, arguments );
TVE_Dash.data_binder( this );
this.toggle_settings_input();
var has_forms = this.model.get( 'items' ).where( {has_form: false} ).length > 0;
},
gotoStep: function ( index ) {
TVE_Dash.views.ModalSteps.prototype.gotoStep.apply( this, arguments );
var _tabs = this.$step.find( '.tvd-tab' );
if ( _tabs.length ) {
_tabs.addClass( 'tvd-disabled' );
jQuery( _tabs[ index ] ).removeClass( 'tvd-disabled' ).find( 'a' ).first().trigger( 'click' );
}
return this;
},
beforeNext: function () {
return this.model.isValid( {
step: this.currentStep
} );
},
toggle_settings_input: function () {
if ( this.model.get( 'auto_win_enabled' ) === true ) {
this.$( '#auto-win-settings' ).show();
} else {
this.model.set( this.model.defaults() );
this.$( '#auto-win-settings' ).hide();
}
},
render_goal_settings: function () {
var _options = {
model: this.model
};
delete this.model.attributes.service;
if ( this.goal_settings_view ) {
this.goal_settings_view.remove();
}
switch ( this.model.get( 'type' ) ) {
case 'monetary':
this.goal_settings_view = new monetary_settings( _options );
break;
case 'visits':
this.goal_settings_view = new visits_settings( _options );
break;
case 'optins':
this.goal_settings_view = new optins_settings( _options );
break;
}
this.$( '#thrive-ab-goal-settings' ).html( this.goal_settings_view.render().$el );
},
submit: function () {
if ( ! this.model.isValid() ) {
return false;
}
TVE_Dash.showLoader( true );
this.model.save( null, {
success: _.bind( function () {
location.href = ThriveAB.page.edit_link;
}, this ),
error: _.bind( function () {
TVE_Dash.hideLoader();
TVE_Dash.err( 'Test could not be saved' );
} )
} );
}
} );

View File

@@ -0,0 +1,22 @@
module.exports = TVE_Dash.views.Modal.extend( {
events: {
'click .tvd-modal-submit': function () {
TVE_Dash.showLoader( true );
location.reload();
}
},
template: TVE_Dash.tpl( 'modals/variation-winner' ),
afterInitialize: function () {
this.model.set( 'label', TVE_Dash.sprintf( ThriveAB.t.variation_winner, this.model.get( 'title' ) ) );
/**
* add custom class to modal
*/
this.$el.addClass( 'thrive-ab-set-winner' );
}
} );

View File

@@ -0,0 +1,37 @@
var test_table = require( './../views/test/table' ),
variation_winner_modal = require( './variation-winner' );
module.exports = TVE_Dash.views.Modal.extend( {
template: TVE_Dash.tpl( 'modals/html-winner' ),
afterInitialize: function () {
this.model.on( 'winner_selected', function ( test, item ) {
this.close();
TVE_Dash.modal( variation_winner_modal, {
model: item,
title: '',
no_close: true,
dismissible: false
} );
}, this );
return this;
},
afterRender: function () {
var item_template_name = 'modals/winner/test/item/' + this.model.get( 'type' );
var test_view = new test_table( {
model: this.model,
collection: this.collection,
item_template_name: item_template_name
} );
test_view.template = TVE_Dash.tpl( 'modals/winner/test/' + this.model.get( 'type' ) );
this.$( '#test-view' ).html( test_view.render().$el );
}
} );

View File

@@ -0,0 +1,40 @@
(function ( $ ) {
module.exports = Backbone.Model.extend( {
idAttribute: 'ID',
defaults: function () {
return {
ID: ''
}
},
url: function () {
var url = ThriveAB.ajax.get_url( this.get_action() + '&' + this.get_route() );
if ( $.isNumeric( this.get( 'ID' ) ) ) {
url += '&ID=' + this.get( 'ID' );
}
return url;
},
get_action: function () {
return 'action=' + ThriveAB.ajax.controller_action;
},
get_route: function () {
return 'route=no_route';
},
validation_error: function ( field, message, callback ) {
return {
field: field,
message: message,
callback: callback
};
}
} );
})( jQuery );

View File

@@ -0,0 +1,23 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 12/7/2017
* Time: 9:57 AM
*/
var base = require( './base' );
module.exports = base.extend( {
defaults: function () {
return _.extend( base.prototype.defaults(), {
ID: 0,
title: '',
x_axis: [],
y_axis: ''
} );
},
get_route: function () {
return 'route=report';
}
} );

View File

@@ -0,0 +1,52 @@
var base = require( './base' );
module.exports = base.extend( {
idAttribute: 'id',
defaults: function () {
return {
id: '',
revenue: 0
};
},
get_route: function () {
return 'route=testitem';
},
validate: function ( attrs, options ) {
var _errors = [];
if ( options.test instanceof Backbone.Model ) {
var test_type = options.test.get( 'type' ),
validator_callback = 'validate_' + test_type;
if ( test_type && typeof this[validator_callback] === 'function' ) {
_errors = this[validator_callback]( attrs, options );
}
}
return _errors.length ? _errors : undefined;
},
validate_monetary: function ( attrs, options ) {
var _errors = [];
if ( isNaN( parseFloat( attrs.revenue ) ) ) {
_errors.push( this.validation_error( 'revenue', 'invalid revenue' ) );
}
return _errors;
},
validate_visits: function ( attrs, options ) {
var _errors = [];
return _errors;
}
} );

View File

@@ -0,0 +1,248 @@
var base = require( './base' ),
variations_collection = require( '../collections/variations' ),
test_item_model = require( './test-item' ),
test_items_collection = require( '../collections/test-items' );
module.exports = base.extend( {
idAttribute: 'id',
defaults: function () {
var items = this.get( 'items' );
return _.extend( {}, {
id: '',
auto_win_enabled: 0,
auto_win_min_conversions: items && items.length ? ( items.length - 1 ) * 100 : 100,
auto_win_min_duration: 14,
auto_win_chance_original: 95
} );
},
get_route: function () {
return 'route=tests';
},
initialize: function ( attrs ) {
if ( ! attrs.items ) {
throw new Error( 'Test model must have items defined' );
}
/**
* convert
*/
if ( attrs.items instanceof variations_collection ) {
var items = [];
attrs.items.each( function ( variation, index ) {
items.push( {
variation_id: variation.get( 'ID' ),
title: variation.get( 'post_title' ),
is_control: variation.get( 'is_control' ) === true,
has_form: variation.get( 'has_form' )
} );
} );
this.set( 'items', new test_items_collection( items ) );
} else if ( attrs.items.length ) {
this.set( 'items', new test_items_collection( attrs.items ) );
}
},
parse: function ( response ) {
if ( response.items ) {
response.items = new test_items_collection( response.items );
}
},
validate: function ( attrs, options ) {
var _errors = [];
if ( options.step !== undefined ) {
_errors = _.union( _errors, this[ 'validate_step_' + options.step ].apply( this, arguments ) );
return _errors.length ? _errors : undefined;
}
if ( ! attrs.type ) {
_errors.push( this.validation_error( 'type', '', function () {
TVE_Dash.err( 'Select type' );
} ) );
return _errors;
}
var _type = attrs.type,
_type_validation_method = 'validate_' + _type;
//validate type dynamically
if ( typeof this[ _type_validation_method ] === 'function' ) {
_errors = this[ _type_validation_method ]( attrs, options );
return _errors;
}
return _errors.length ? _errors : undefined;
},
validate_monetary: function ( attrs, options ) {
var _errors = [];
//validate if service set when user wants to start a test
//it enters here too if user wants to change the winner settings for a running test
if ( ! attrs.service && ! attrs.save_test_settings ) {
_errors.push( this.validation_error( 'service', '', function () {
TVE_Dash.err( ThriveAB.t.select_measurement_option );
} ) );
}
if ( 'visit_page' === attrs.service && ( ! attrs.goal_pages || Object.keys( attrs.goal_pages ).length === 0 ) ) {
_errors.push( this.validation_error( 'goal_page', '', function () {
TVE_Dash.err( ThriveAB.t.select_thank_you_page );
} ) );
}
return _errors.length ? _errors : undefined;
},
validate_visits: function ( attrs, options ) {
var _errors = [];
if ( ! attrs.goal_pages || Object.keys( attrs.goal_pages ).length === 0 ) {
_errors.push( this.validation_error( 'goal_page', '', function () {
TVE_Dash.err( ThriveAB.t.select_goal_page );
} ) );
}
return _errors.length ? _errors : undefined;
},
validate_optins: function ( attrs, options ) {
var _errors = [];
return _errors.length ? _errors : undefined;
},
validate_step_winner_settings: function ( attrs, options ) {
var fields = {
auto_win_min_conversions: attrs.auto_win_min_conversions,
auto_win_min_duration: attrs.auto_win_min_duration,
auto_win_chance_original: attrs.auto_win_chance_original
};
return this._validate_fields( fields );
},
_validate_fields: function ( fields ) {
var _errors = [];
/**
* check if one of the fields has validator; if yes then and execute it and stack any error
*/
_.each( fields, function ( value, prop ) {
if ( typeof this[ 'validate_' + prop ] === 'function' ) {
_errors = _.union( _errors, this[ 'validate_' + prop ]( value ) );
}
}, this );
if ( _errors.length ) {
return _errors;
}
},
validate_step_0: function ( attrs, options ) {
var fields = {
title: attrs.title,
auto_win_min_conversions: attrs.auto_win_min_conversions,
auto_win_min_duration: attrs.auto_win_min_duration,
auto_win_chance_original: attrs.auto_win_chance_original
};
return this._validate_fields( fields );
},
validate_title: function ( value ) {
if ( ! value ) {
return [
this.validation_error( 'title', ThriveAB.t.invalid_test_title )
];
}
return [];
},
validate_auto_win_min_conversions: function ( value ) {
if ( isNaN( parseInt( value ) ) ) {
return [
this.validation_error( 'auto_win_min_conversions', ThriveAB.t.not_number_min_win_conversions )
];
}
if ( parseInt( value ) <= 0 ) {
return [
this.validation_error( 'auto_win_min_conversions', ThriveAB.t.greater_zero_min_win_conversions )
];
}
return [];
},
validate_auto_win_min_duration: function ( value ) {
if ( isNaN( parseInt( value ) ) ) {
return [
this.validation_error( 'auto_win_min_duration', ThriveAB.t.invalid_auto_win_min_duration )
];
}
if ( parseInt( value ) <= 0 ) {
return [
this.validation_error( 'auto_win_min_duration', ThriveAB.t.invalid_auto_win_min_duration )
];
}
return [];
},
validate_auto_win_chance_original: function ( value ) {
if ( isNaN( parseInt( value ) ) ) {
return [
this.validation_error( 'auto_win_chance_original', ThriveAB.t.invalid_auto_win_chance_original )
];
}
if ( parseInt( value ) <= 0 || parseInt( value ) > 100 ) {
return [
this.validation_error( 'auto_win_chance_original', ThriveAB.t.invalid_auto_win_chance_original )
];
}
return [];
},
search_page_label: function () {
var _label = 'Search Page',
_type = this.get( 'type' );
if ( _type === 'monetary' ) {
_label = 'Thank you page'
} else if ( _type === 'visits' ) {
_label = 'Goal page';
}
return _label;
}
} );

View File

@@ -0,0 +1,10 @@
var base = require( './base' );
module.exports = base.extend( {
get_route: function () {
return 'route=traffic';
}
} );

View File

@@ -0,0 +1,17 @@
var base = require( './base' );
module.exports = base.extend( {
defaults: function () {
return _.extend( base.prototype.defaults(), {
is_control: false,
traffic: 0
} );
},
get_route: function () {
return 'route=variations';
}
} );

View File

@@ -0,0 +1,91 @@
var reports = require( './views/report/report' ),
variation_collection = require( './collections/variations' ),
dashboard = require( './views/dashboard' ),
test_model = require( './models/test' );
(function ( $ ) {
module.exports = Backbone.Router.extend( {
view: null,
$el: $( '#tab-dashboard-wrapper' ),
routes: {
'dashboard(/:action)': 'dashboard',
'test(/:id)': 'reports'
},
/**
* dashboard route callback
*/
dashboard: function ( action ) {
if ( this.view ) {
this.view.remove();
}
if ( typeof ThriveAB === 'undefined' ) {
console.log( 'Thrive Optimize have not localized required data !' );
return;
}
this.view = new dashboard( {
el: this.$el,
model: new Backbone.Model( ThriveAB.page ),
collection: new variation_collection( ThriveAB.variations ),
archived: new variation_collection( ThriveAB.archived ),
} );
if ( action === 'start-test' ) {
this.view.$( '#thrive-ab-start-test' ).trigger( 'click' );
}
this.check_license();
},
/**
* reports route callback
*/
reports: function ( id ) {
if ( this.view ) {
this.view.remove();
}
var model = new test_model( id ? ThriveAB.running_test : ThriveAB.current_test );
this.view = new reports( {
el: this.$el,
model: model
} );
this.check_license();
},
check_license: function(){
if ( ThriveAB.license.gp && ThriveAB.license.show_lightbox) {
TVE_Dash.modal( TVE_Dash.views.LicenseModal, {
model: {
title: 'Thrive Optimize',
license_class: 'grace-period',
product_class: 'tab',
license_link: ThriveAB.license.link,
grace_time: ThriveAB.license.grace_time
},
className: 'tvd-modal tvd-license-modal tvd-modal-grace-period',
width: '950px',
'max-width': '950px',
} );
} else if ( ThriveAB.license.exp && ! ThriveAB.license.gp ) {
TVE_Dash.modal( TVE_Dash.views.LicenseModal, {
model: {
title: 'Thrive Optimize',
license_class: 'expired',
product_class: 'tab',
license_link: ThriveAB.license.link
},
className: 'tvd-modal tvd-license-modal tvd-modal-expired',
no_close: true,
width: '950px',
dismissible: false,
'max-width': '950px',
} );
$( '#tab-admin-dashboard-wrapper' ).replaceWith( $('#tab-admin-dashboard-wrapper').clone() );
}
}
} );
})( jQuery );

View File

@@ -0,0 +1,59 @@
(function ( $ ) {
module.exports = {
ajax: {
get_url: function ( query_string ) {
var _q = this.url.indexOf( '?' ) !== - 1 ? '&' : '?';
if ( ! query_string || ! query_string.length ) {
return this.url + _q + '_nonce=' + this.nonce;
}
query_string = query_string.replace( /^(\?|&)/, '' );
query_string += '&nonce=' + this.nonce;
return this.url + _q + query_string;
},
data: function ( custom_action, type, extra_data, data_type ) {
return {
url: this.url,
dataType: typeof data_type === 'undefined' ? 'json' : data_type,
type: type || 'get',
data: _.extend( {
action: this.action,
custom: custom_action,
nonce: this.nonce
}, extra_data || {} ),
error: function ( jqXHR, textStatus, errorThrown ) {
if ( typeof jqXHR.tcb_error === 'function' && jqXHR.tcb_error.apply( jqXHR, arguments ) === false ) {
return;
}
TVE_Dash.hideLoader();
if ( jqXHR.responseText ) {
try {
var response = JSON.parse( jqXHR.responseText );
TVE_Dash.err( response.message );
} catch ( e ) {
TVE_Dash.err( jqXHR.responseText );
}
return;
}
if ( ! errorThrown ) {
errorThrown = 'An unexpected error occurred. ' + ( jqXHR.status ? ' (Status code: ' + jqXHR.status + ')' : '' );
} else {
errorThrown = 'Unexpected error: ' + ( jqXHR.status ? jqXHR.status + ': ' : '' ) + errorThrown;
}
// finally just the error text
TVE_Dash.err( errorThrown );
}
}
},
do: function ( action, type, extra_data, data_type ) {
return $.ajax( this.data( action, type, extra_data, data_type ) );
}
}
};
})( jQuery );

View File

@@ -0,0 +1,59 @@
var base = require( './base' ),
delete_modal = require( '../modals/delete' );
module.exports = base.extend( {
className: 'tvd-col tvd-l3 tvd-m6',
template: TVE_Dash.tpl( 'html-archived-variation-card' ),
initialize: function ( args ) {
base.prototype.initialize.apply( this, args );
this.published_variations = args.published_variations;
this.archived_variations = args.archived_variations;
},
delete: function () {
TVE_Dash.modal( delete_modal, {
submit: _.bind( function () {
this.remove();
this.model.destroy();
}, this ),
model: this.model,
btn_yes_txt: ThriveAB.t.delete_title,
btn_no_txt: ThriveAB.t.cancel,
title: ThriveAB.t.delete_variation,
description: TVE_Dash.sprintf( ThriveAB.t.about_to_delete, this.model.get( 'post_title' ) )
} );
return false;
},
restore: function () {
var self = this;
this.model.set( 'action', 'publish' );
TVE_Dash.showLoader();
this.model.save( null, {
success: function ( model ) {
model.collection = self.published_variations;
self.published_variations.add( model );
self.published_variations.equalize_traffic();
self.published_variations.save_distributed_traffic();
self.remove();
self.archived_variations.remove( model );
TVE_Dash.success( ThriveAB.t.variation_added, 1000 );
},
error: function () {
TVE_Dash.err( ThriveAB.t.add_variation_error );
},
complete: function () {
TVE_Dash.hideLoader();
}
} );
},
render: function () {
this.$el.html( this.template( {item: this.model} ) );
}
} );

View File

@@ -0,0 +1,60 @@
/**
* @description Base View
* @extends Backbone.View
*/
module.exports = Backbone.View.extend( {
events: {
'click .click': '_call',
'input .input': '_call',
'change .change': '_call',
'mousedown .mousedown': '_call',
'mouseenter .mouseenter': '_call',
'mouseup .mouseup': '_call',
'keyup .keyup-enter': '_keyup_enter'
},
initialize: function () {
this.render();
},
_call: function ( e ) {
/**
* Do not allow actions on disabled controls
*/
if ( e.currentTarget.disabled || e.currentTarget.classList.contains( 'tve-disabled' ) ) {
return false;
}
var m = e.currentTarget.getAttribute( 'data-fn-' + e.type ) || e.currentTarget.getAttribute( 'data-fn' );
if ( m && m === '__return_false' ) {
e.stopPropagation();
e.preventDefault();
return false;
}
if ( typeof this[m] === 'function' ) {
return this[m].call( this, e, e.currentTarget );
}
/**
* call external function on the base TVE object
*/
if ( m && m.indexOf( 'f:' ) === 0 ) {
var fn = TVE, parts = m.split( ':' )[1].split( '.' ), context = window;
while ( fn && parts.length ) {
context = fn;
fn = fn[parts.shift()];
}
if ( typeof fn === 'function' ) {
return fn.call( context, e );
}
}
},
render: function () {
}
} );

View File

@@ -0,0 +1,143 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 12/5/2017
* Time: 10:56 AM
*/
module.exports = Backbone.Model.extend( {
defaults: function () {
return {
id: '',
title: '',
renderTo: '',
type: 'line',
suffix: '',
data: []
};
},
initialize: function () {
var title = this.get( 'title' ),
type = this.get( 'type' ),
renderTo = this.get( 'renderTo' );
this.chart = this.dochart( title, type, renderTo );
},
empty: function () {
while ( this.chart.series.length > 0 ) {
this.chart.series[0].remove( true );
}
},
redraw: function () {
var title = this.get( 'title' ),
data = this.get( 'data' ),
x_axis = this.get( 'x_axis' ),
y_axis = this.get( 'y_axis' ),
ids = [],
x_axis_length = this.get( 'x_axis' ).length;
//add series or update data if it already exists
for ( var i in data ) {
ids.push( data[i].id );
var series = this.chart.get( data[i].id );
if ( ! series ) {
this.chart.addSeries( data[i], false, false )
} else {
series.setData( data[i].data );
}
}
//delete old series
for ( i = 0; i < this.chart.series.length; i ++ ) {
if ( ids.indexOf( this.chart.series[i].options.id ) < 0 ) {
this.chart.series[i].remove( false );
i --;
}
}
this.chart.get( 'time_interval' ).setCategories( x_axis );
this.chart.xAxis[0].update( {
tickInterval: x_axis_length > 13 ? Math.ceil( x_axis_length / 13 ) : 1
} );
this.chart.setTitle( {text: title} );
if ( this.chart.yAxis[0].axisTitle ) {
this.chart.yAxis[0].axisTitle.attr( {
text: y_axis
} );
}
this.chart.redraw();
this.chart.hideLoading();
},
showLoading: function () {
this.chart.showLoading();
},
hideLoading: function () {
this.chart.hideLoading();
},
dochart: function ( title, type, renderTo ) {
var self = this;
return new Highcharts.Chart( {
chart: {
type: type,
renderTo: renderTo,
style: {
fontFamily: 'Open Sans,sans-serif'
}
},
colors: ThriveAB.chart_colors,
yAxis: {
allowDecimals: false,
title: {
text: 'Engagements'
},
min: 0
},
xAxis: {
id: 'time_interval'
},
credits: {
enabled: false
},
title: {
text: title
},
tooltip: {
shared: false,
useHTML: true,
formatter: function () {
if ( this.series.type == 'scatter' ) {
/* We don't display tooltips for the scatter graph */
return false;
} else {
return this.x + '<br/>' +
this.series.name + ': ' + '<b>' + this.y + '</b>' + self.get( 'suffix' );
}
}
},
plotOptions: {
series: {
dataLabels: {
shape: 'callout',
backgroundColor: 'rgba(0, 0, 0, 0.75)',
style: {
color: '#FFFFFF',
textShadow: 'none'
}
},
events: {
legendItemClick: function () {
if ( this.type == 'scatter' ) {
/* The labels are not hidden by clicking on the legend so we have to do it manually */
if ( this.visible ) {
jQuery( '.highcharts-data-labels' ).hide();
} else {
jQuery( '.highcharts-data-labels' ).show();
}
}
}
}
}
}
} );
}
} );

View File

@@ -0,0 +1,137 @@
var base_view = require( './base' ),
variation_view = require( './variation' ),
archived_variation_view = require( './archived_variation' ),
test_model = require( '../models/test' ),
variation_model = require( '../models/variation' ),
modal_test = require( '../modals/test' );
module.exports = base_view.extend( {
template: TVE_Dash.tpl( 'dashboard/dashboard' ),
initialize: function ( args ) {
this.archived = args.archived;
base_view.prototype.initialize.apply( this, arguments );
this.listenTo( this.collection, 'add', function ( model ) {
this.render_variation( model );
this.render_action_button();
this.toggle_traffic_control();
} );
this.listenTo( this.collection, 'remove', function ( model ) {
this.render_action_button();
this.toggle_traffic_control();
} );
this.listenTo( this.archived, 'add', function ( model ) {
this.render_archived_variation( model );
} );
},
render_action_button: function () {
this.$( '#thrive-ab-start-test' ).toggleClass( 'top-hide-action', this.collection.length < 2 );
this.$( '.thrive-ab-display-archived-container' ).toggleClass( 'hide', this.archived.length < 1 );
},
render: function () {
this.$el.html( this.template() );
this.$list = this.$( '#thrive-ab-card-list' );
this.$archived_list = this.$( '#thrive-ab-card-list-archived' );
this.render_action_button();
this.collection.each( function ( item, index, list ) {
this.render_variation( item );
}, this );
this.archived.each( function ( item, index, list ) {
this.render_archived_variation( item );
}, this );
},
render_archived_variation: function ( item ) {
var _view = new archived_variation_view( {
model: item,
archived_variations: this.archived,
published_variations: this.collection
} );
this.$archived_list.append( _view.$el );
},
render_variation: function ( item ) {
var _view = new variation_view( {
model: item,
archived_variations: this.archived,
published_variations: this.collection
} );
var $add_new_card = this.$list.find( '> .tvd-col' ).last();
$add_new_card.remove();
this.$list.append( _view.$el );
this.$list.append( $add_new_card );
},
display_archived: function () {
this.$archived_list.toggle();
this.$( '.thrive-ab-display-archived' ).toggleClass( 'active' );
},
add_new_variation: function () {
var _new_traffic = parseInt( 100 / ( this.collection.length + 1 ) ),
_model = new variation_model( {
post_parent: this.model.get( 'ID' ),
post_title: TVE_Dash.sprintf( ThriveAB.t.variation_no, this.collection.length + 1 ),
traffic: _new_traffic
} );
TVE_Dash.showLoader();
_model.save( null, {
success: _.bind( function ( model ) {
this.collection.add( model );
model.collection.distribute_traffic( model );
model.collection.save_distributed_traffic();
TVE_Dash.success( ThriveAB.t.variation_added, 1000 );
}, this ),
error: function () {
TVE_Dash.err( ThriveAB.t.add_variation_error );
},
complete: _.bind( function () {
TVE_Dash.hideLoader();
}, this )
} );
},
start_test: function () {
var new_test_model = new test_model( {
page_id: ThriveAB.page.ID,
items: this.collection
} );
TVE_Dash.modal( modal_test, {
model: new_test_model,
'max-width': '80%'
} );
return false;
},
toggle_traffic_control: function () {
if ( this.collection.length === 1 ) {
this.$( '.thrive-ab-card-footer input' ).attr( 'disabled', 'disabled' );
} else {
this.$( '.thrive-ab-card-footer input' ).removeAttr( 'disabled' );
}
},
equalize_traffic: function () {
this.collection.equalize_traffic();
this.collection.save_distributed_traffic();
return false;
}
} );

View File

@@ -0,0 +1,71 @@
var base_view = require( '../base' ),
page_search = require( './../../controls/page_search' );
module.exports = base_view.extend( {
className: 'test-item-form tvd-col tvd-s12',
template: TVE_Dash.tpl( 'goals/page' ),
type: null,
events: {
'click .thrive-ab-remove-page': 'remove_page'
},
initialize: function ( args ) {
if ( args.test && args.test instanceof Backbone.Model ) {
this.test = args.test
}
this.model.set('type', this.test.get('type'));
this.page_search_view = new page_search.view( {
model: this.model,
goal_pages: this.test.get('goal_pages')
} );
this.page_search_view.test = this.test;
this.goal_pages = this.test.get( 'goal_pages' );
if ( ! this.goal_pages ) {
this.goal_pages = {};
}
this.listenTo( this.model, 'change:revenue', this.onRevenueChange );
this.listenTo( this.model, 'change:post_id', this.onPostChange );
},
onPostChange: function () {
if ( this.model.get( 'post_id' ) != null ) {
this.goal_pages[this.model.get( 'post_id' )] = {
post_id: this.model.get( 'post_id' ),
post_title: this.model.get( 'post_title' ),
revenue: this.model.get( 'revenue' )
};
this.test.set( 'goal_pages', this.goal_pages );
}
},
onRevenueChange: function () {
if ( this.model.get( 'post_id' ) ) {
this.onPostChange();
}
},
render: function () {
this.$el.html( this.template( {item: this.page_search_view.model, test: this.test} ) );
setTimeout( _.bind( function () {
TVE_Dash.materialize( this.$el );
}, this ), 0 );
TVE_Dash.data_binder( this );
this.$( '.page-search' ).html( this.page_search_view.render().$el );
return this;
},
remove_page: function () {
delete this.goal_pages[this.model.get( 'post_id' )];
this.test.set( 'goal_pages', this.goal_pages );
this.$el.unbind();
this.remove();
}
} );

View File

@@ -0,0 +1,71 @@
var settings = require( './settings' );
( function ( $ ) {
module.exports = settings.extend( {
template: TVE_Dash.tpl( 'goals/monetary-settings' ),
events: function () {
var parent_settings = settings.prototype.events.apply( this, arguments );
return _.extend( parent_settings, {
'change #thrive-ab-monetary-services': 'on_service_change'
} );
},
render: function () {
this.$el.html( this.template( {item: this.model} ) );
this.$( '.thrive-ab-monetary-service' ).hide();
this.init_services();
this.render_goal_pages();
return this;
},
on_service_change: function ( event ) {
var $services = this.$( '.thrive-ab-monetary-service' ),
service = event.currentTarget.value;
$services.hide();
this.model.set( 'service', service );
if ( service.length <= 0 ) {
return;
}
$services.closest( '#' + service ).css( 'display', 'block' );
},
init_services: function () {
if ( typeof ThriveAB.monetary_services === 'undefined' || ThriveAB.monetary_services.length <= 0 ) {
return;
}
var $dropdown = this.$( '#thrive-ab-monetary-services' ),
services = Object.keys( ThriveAB.monetary_services );
_.each( ThriveAB.monetary_services, function ( service, slug ) {
var $option = $( '<option/>' )
.attr( 'value', slug )
.text( typeof service.label ? service.label : slug );
$dropdown.append( $option );
}, this );
if ( services.length === 1 ) {
$dropdown.val( services[ 0 ] ).change();
$dropdown.parents( '.tvd-row' ).first().hide();
}
}
} );
} )( jQuery );

View File

@@ -0,0 +1,12 @@
var base_view = require( '../base' );
module.exports = base_view.extend( {
template: TVE_Dash.tpl( 'goals/optins-settings' ),
render: function () {
this.$el.html( this.template() );
return this;
}
} );

View File

@@ -0,0 +1,65 @@
var base_view = require( '../base' ),
goal_page = require( './goal_page' ),
base_model = require( './../../models/base' ),
page_search = require( '../../controls/page_search' );
module.exports = base_view.extend( {
className: 'tvd-col tvd-s12',
events: function () {
return _.extend( base_view.prototype.events, {
'click .thrive-ab-add-new-goal': 'add_goal_page_field'
} );
},
initialize: function () {
this.item_form_views = [];
},
render: function () {
this.$el.html( this.template( {item: this.model} ) );
this.render_goal_pages();
return this;
},
render_goal_pages: function () {
var goal_pages = this.model.get( 'goal_pages' ),
form_view,
self = this;
if ( goal_pages ) {
_.each( goal_pages, function ( element, index ) {
form_view = new goal_page( {
test: self.model,
model: new base_model( element )
} );
self.item_form_views.push( form_view );
self.$( '#item-forms' ).append( self.create_goal_page( form_view ) );
} );
} else {
this.add_goal_page_field();
}
},
add_goal_page_field: function () {
var form_view = new goal_page( {
test: this.model,
model: new base_model()
} );
this.item_form_views.push( form_view );
this.$( '#item-forms' ).append( this.create_goal_page( form_view ) );
},
create_goal_page: function ( input_view ) {
return input_view.render().$el;
}
} );

View File

@@ -0,0 +1,6 @@
var settings = require( './settings' );
module.exports = settings.extend( {
template: TVE_Dash.tpl( 'goals/visits-settings' )
} );

View File

@@ -0,0 +1,72 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 11/29/2017
* Time: 11:42 AM
*/
var base_view = require( '../base' ),
line_chart = require( '../charts/line-chart' );
module.exports = base_view.extend( {
current_interval: 'day',
type: 'conversion_rate',
render_to: '',
initialize: function ( options ) {
base_view.prototype.initialize.apply( this, arguments );
this.render_to = options.render_to;
this.update_chart();
},
draw_chart: function ( render_to ) {
this.chart = new line_chart( {
title: '',
type: 'spline',
data: [],
renderTo: render_to
} );
this.chart.showLoading();
},
interval_changed: function ( type, interval ) {
var self = this;
if ( interval === this.current_interval && type === this.type ) {
return;
}
this.current_interval = interval;
this.type = type;
if ( typeof this.chart !== 'undefined' ) {
this.chart.showLoading();
}
this.model.fetch( {
data: jQuery.param( {
type: type,
interval: interval
} ),
success: function () {
self.update_chart();
}
} );
},
update_chart: function () {
if ( typeof this.chart === 'undefined' ) {
this.draw_chart( this.render_to );
}
this.chart.set( 'data', this.model.get( 'data' ) );
this.chart.set( 'title', '' );
this.chart.set( 'x_axis', this.model.get( 'x_axis' ) );
this.chart.set( 'y_axis', this.model.get( 'y_axis' ) );
this.chart.redraw();
this.update_description();
},
update_description: function () {
this.$( '#thrive-ab-chart-title' ).html( this.model.get( 'title' ) );
this.$( '#thrive-ab-chart-total-value' ).html( this.model.get( 'total_over_time' ) + ' ' + this.model.get( 'test_type_txt' ) );
}
} );

View File

@@ -0,0 +1,25 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 11/27/2017
* Time: 2:31 PM
*/
var base_view = require( '../base' );
module.exports = base_view.extend( {
template: TVE_Dash.tpl( 'report/report-item' ),
tagName: 'tr',
initialize: function () {
},
render: function () {
this.$el.html( this.template( {model: this.model} ) );
return this;
}
} );

View File

@@ -0,0 +1,61 @@
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 11/27/2017
* Time: 10:20 AM
*/
var base_view = require( '../base' ),
report_item_view = require( './report-item' ),
report_chart = require( './report-chart' ),
chart_model = require( './../../models/report-chart' ),
test_table_view = require( './../test/table' ),
winner_modal = require( './../../modals/winner' );
module.exports = base_view.extend( {
template: TVE_Dash.tpl( 'report/report' ),
initialize: function () {
base_view.prototype.initialize.apply( this, arguments );
},
render: function () {
this.$el.html( this.template( {
model: this.model,
edit_page_link: ThriveAB.page.edit_link
} ) );
this.$( 'select' ).select2();
var table_view = new test_table_view( {
el: this.$( '#thrive-ab-test' ),
model: this.model
} );
this.do_chart();
},
update_chart: function ( event, dom ) {
var type = this.$( '.tab-graph-type' ).val(),
interval = this.$( '.tab-graph-interval' ).val();
this.report_chart.interval_changed( type, interval );
},
do_chart: function () {
this.report_chart = new report_chart( {
el: this.$el,
model: new chart_model( ThriveAB.test_chart ),
render_to: 'tab-test-chart'
} );
},
stop_test: function () {
TVE_Dash.modal( winner_modal, {
model: this.model,
collection: this.collection,
'max-width': '80%',
width: '80%',
title: ThriveAB.t.choose_winner
} );
}
} );

View File

@@ -0,0 +1,13 @@
var base = require( '../base' );
module.exports = base.extend( {
initialize: function () {
this.template = TVE_Dash.tpl( 'test/item/goal/' + this.model.get( 'type' ) );
},
render: function () {
this.$el.html( this.template( {} ) );
return this;
}
} );

View File

@@ -0,0 +1,137 @@
var base = require( './../base' ),
stop_variation_modal = require( './../../modals/delete' ),
range = require( '../../controls/range' ),
variation_winner_modal = require( './../../modals/variation-winner' );
module.exports = base.extend( {
template: TVE_Dash.tpl( 'test/item/view' ),
className: '',
initialize: function ( attrs ) {
this.active_items = attrs.active_items;
this.stopped_items = attrs.stopped_items;
this.table_model = attrs.table_model;
},
render: function () {
this.$el.html( this.template( {
item: this.model,
table_model: this.table_model
} ) );
if ( this.className.length > 0 ) {
this.$el.attr( 'class', this.className );
}
if ( parseInt( this.model.get( 'active' ) ) !== 0 ) {
new range( {
el: this.$( '.thrive-ab-test-item-traffic' ),
/**
* because the active_items is a collection with active items too
* model.item is from whole collection of test items
*/
model: this.active_items.findWhere( {id: this.model.get( 'id' )} )
} );
}
return this;
},
set_as_winner: function ( model, callback ) {
if ( ! (model instanceof Backbone.Model) ) {
model = this.model;
}
TVE_Dash.showLoader( true );
ThriveAB.ajax.do( 'set_winner', 'post', model.toJSON() )
.done( _.bind( function () {
if ( typeof callback === 'function' ) {
callback( model );
} else {
this.table_model.trigger( 'winner_selected', this.table_model, model );
TVE_Dash.hideLoader();
}
}, this ) );
return false;
},
stop_variation: function () {
TVE_Dash.modal( stop_variation_modal, {
submit: _.bind( function () {
TVE_Dash.showLoader();
this.model.set( 'stop_test_item', true );
this.model.save( null, {
wait: true,
/**
* tell server to stop the item and return the item model to update the backbone model
*/
success: _.bind( function ( model ) {
/**
* split the removed traffic and save it
*/
this.active_items.split_traffic( model, model.previousAttributes().traffic );
this.active_items.save_distributed_traffic();
/**
* remove vew from active items list
*/
this.remove();
/**
* add to stopped items to be rendered
*/
this.stopped_items.add( model );
/**
* remove it from active collection items
* so splitting traffic will not take into consideration this item/model
*/
this.active_items.remove( model );
var index = ThriveAB.current_test.items.findIndex( function ( element ) {
return element.id == model.get( 'id' );
} );
if ( index >= 0 ) {
ThriveAB.current_test.items[index].active = 0;
}
if ( this.active_items.length === 1 ) {
this.set_as_winner( this.active_items.first(), function ( model ) {
TVE_Dash.hideLoader();
TVE_Dash.modal( variation_winner_modal, {
model: model,
title: '',
no_close: true,
dismissible: false
}
);
} );
}
}, this ),
error: _.bind( function () {
TVE_Dash.hideLoader();
TVE_Dash.err( ThriveAB.t.variation_status_not_changed );
}, this )
} );
}, this ),
title: '',
description: TVE_Dash.sprintf( ThriveAB.t.about_to_stop_variation, this.model.get( 'title' ) ),
btn_yes_txt: ThriveAB.t.stop,
btn_no_txt: ThriveAB.t.keep_it_running,
'max-width': '20%',
width: '20%'
} );
}
} );

View File

@@ -0,0 +1,144 @@
var base = require( '../base' ),
test_item = require( './item' ),
change_automatic_winner_settings = require( '../../modals/automatic-winner-settings' ),
test_items_collection = require( '../../collections/test-items' ),
test_model = require( '../../models/test' ),
goal = require( './goal' ),
modal_goal = require( '../../modals/goal' );
module.exports = base.extend( {
template: TVE_Dash.tpl( 'test/table' ),
initialize: function ( attr ) {
this.template = TVE_Dash.tpl( 'test/' + this.model.get( 'type' ) + '-table' );
if ( attr.item_template_name ) {
this.item_template_name = attr.item_template_name;
}
var raw_test = ThriveAB.current_test || ThriveAB.running_test;
this.active_items = new test_items_collection( _.filter( raw_test.items, {active: '1'} ) );
this.stopped_items = new test_items_collection( _.filter( raw_test.items, {active: '0'} ) );
this.listenTo( this.stopped_items, 'add', function ( model ) {
this.$stopped_item_list.append( this.render_item( model ).$el );
} );
this.listenTo( this.active_items, 'remove', function ( model ) {
this.render_active_items();
} );
this.listenTo( this.model, 'change:auto_win_enabled', this.toggle_auto_win_text );
base.prototype.initialize.apply( this, arguments );
},
render: function () {
this.$el.html( this.template( {item: this.model} ) );
this.toggle_auto_win_text( this.model );
if ( this.model.get( 'status' ) === 'completed' ) {
this.$( '.thrive-ab-test-footer' ).hide();
}
this.$item_list = this.$( '.thrive-ab-test-items' );
this.$stopped_item_list = this.$( '.thrive-ab-test-stopped-items' );
this.collection = this.model.get( 'items' );
this.render_variations();
this.render_goal_type();
return this;
},
toggle_auto_win_text: function ( model ) {
var text = model.get( 'auto_win_enabled' ) != 0 ? ThriveAB.t.auto_win_enabled : ThriveAB.t.auto_win_disabled;
this.$( '#thrive-ab-auto-win-text' ).text( text );
},
render_variations: function () {
this.render_active_items();
this.render_stopped_items();
},
render_active_items: function () {
this.$item_list.empty();
this.active_items.each( function ( item ) {
this.$item_list.append( this.render_item( item ).$el );
}, this );
return this;
},
render_stopped_items: function () {
this.$stopped_item_list.empty();
this.stopped_items.each( function ( item ) {
this.$stopped_item_list.append( this.render_item( item ).$el );
}, this );
return this;
},
render_item: function ( item ) {
var item_view = new test_item( {
model: item,
table_model: this.model,
active_items: this.active_items,
stopped_items: this.stopped_items
} );
if ( this.item_template_name ) {
item_view.template = TVE_Dash.tpl( this.item_template_name );
} else if ( item.get( 'is_control' ) ) {
item_view.template = TVE_Dash.tpl( 'test/item/control-view-' + this.model.get( 'type' ) );
item_view.className = 'tab-control-row';
} else if ( parseInt( item.get( 'active' ) ) === 0 ) {
item_view.template = TVE_Dash.tpl( 'test/item/view-stopped-' + this.model.get( 'type' ) );
} else {
item_view.template = TVE_Dash.tpl( 'test/item/view-' + this.model.get( 'type' ) );
}
return item_view.render();
},
render_goal_type: function () {
new goal( {
el: this.$( '#thrive-ab-conversion-goals' ),
model: this.model
} ).render();
},
/**
* Change Automatic Winner Settings
*/
change_automatic_winner_settings: function () {
TVE_Dash.modal( change_automatic_winner_settings, {
model: this.model,
title: ThriveAB.t.automatic_winner_settings
} );
return false;
},
view_conversion_goal_details: function () {
TVE_Dash.modal( modal_goal, {
collection: new Backbone.Collection( Object.values( this.model.get( 'goal_pages' ) ) ),
model: this.model,
title: ''
} );
return false;
}
} );

View File

@@ -0,0 +1,128 @@
var base = require( './base' ),
base_model = require( './../models/base' ),
range = require( '../controls/range' ),
delete_modal = require( '../modals/archive' ),
title_editor = require( './../controls/edit_title' );
module.exports = base.extend( {
className: 'tvd-col tvd-l3 tvd-m6',
template: TVE_Dash.tpl( 'html-variation-card' ),
initialize: function ( args ) {
base.prototype.initialize.apply( this, args );
this.archived_variations = args.archived_variations;
this.published_variations = args.published_variations;
},
archive: function () {
TVE_Dash.modal( delete_modal, {
submit: _.bind( function () {
this.remove();
this.model.set( 'action', 'archive' );
this.model.save();
this.published_variations.distribute_traffic( this.model.set( 'traffic', 0 ) );
this.published_variations.save_distributed_traffic();
this.archived_variations.add( this.model );
this.published_variations.remove( this.model );
}, this ),
model: this.model,
btn_yes_txt: ThriveAB.t.archive_title,
btn_no_txt: ThriveAB.t.cancel,
title: ThriveAB.t.archive_variation,
description: TVE_Dash.sprintf( ThriveAB.t.about_to_archive, this.model.get( 'post_title' ) )
} );
return false;
},
delete: function () {
TVE_Dash.modal( delete_modal, {
submit: _.bind( function () {
this.remove();
this.model.destroy();
this.published_variations.equalize_traffic();
this.published_variations.save_distributed_traffic();
}, this ),
model: this.model,
btn_yes_txt: ThriveAB.t.delete_title,
btn_no_txt: ThriveAB.t.cancel,
title: ThriveAB.t.delete_variation,
description: TVE_Dash.sprintf( ThriveAB.t.about_to_delete, this.model.get( 'post_title' ) )
} );
return false;
},
duplicate: function () {
var clone = this.model.clone(),
_new_traffic = parseInt( 100 / ( this.published_variations.length + 1 ) );
clone.set( {
ID: '',
post_title: 'Copy of ' + this.model.get( 'post_title' ),
source_id: this.model.get( 'ID' ),
traffic: _new_traffic,
is_control: false,
post_parent: ThriveAB.page.ID
} );
TVE_Dash.showLoader();
clone.save( null, {
success: _.bind( function ( model ) {
this.published_variations.add( model );
this.published_variations.equalize_traffic();
this.published_variations.save_distributed_traffic();
TVE_Dash.success( ThriveAB.t.variation_added, 1000 );
}, this ),
error: function () {
TVE_Dash.err( ThriveAB.t.add_variation_error );
},
complete: function () {
TVE_Dash.hideLoader();
}
} );
},
edit_title: function ( e ) {
var self = this,
$original_title = this.$el.find( '.thrive-ab-title-content' ),
editModel = new base_model( {post_title: this.model.get( 'post_title' )} );
editModel.on( 'thrive-ab-title-no-change', function () {
self.$( '.thrive-ab-title-editor' ).html( '' );
$original_title.show();
} );
editModel.on( 'change:post_title', function () {
self.model.save( {post_title: editModel.get( 'post_title' )}, {patch: true} );
self.$( '.thrive-ab-title-editor' ).html( '' );
$original_title.find( '.thrive-ab-card-title' ).html( editModel.get( 'post_title' ) );
$original_title.show();
} );
var titleEditor = new title_editor( {
el: this.$( '.thrive-ab-title-editor' ),
model: editModel
} );
$original_title.hide().after( titleEditor.render().$el );
titleEditor.focus();
return false;
},
render: function () {
this.$el.html( this.template( {item: this.model} ) );
new range( {
el: this.$( '.thrive-ab-card-footer' ),
model: this.model
} );
}
} );

View File

@@ -0,0 +1,207 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
/**
* Class Thrive_AB_Admin_View_Test_Page
* Used for viewing a test details(report)
*
* - enqueues the scripts/styles
* - localizes required data based on test id from query string
*/
class Thrive_AB_Admin_View_Test_Page {
/**
* @var int
*/
protected $_test_id;
/**
* @var Thrive_AB_Test
*/
protected $test;
public function __construct() {
$test_id = ! empty( $_GET['test_id'] ) ? sanitize_key( $_GET['test_id'] ) : null;
if ( $test_id ) {
$this->_test_id = (int) $test_id;
/**
* Enqueue Scripts
*/
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
/**
* Enqueue Styles
*/
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ), PHP_INT_MAX );
add_action( 'admin_print_footer_scripts', array( $this, 'print_backbone_templates' ) );
add_action( 'admin_print_footer_scripts', array( $this, 'include_svg' ) );
add_filter( 'admin_title', array( $this, 'get_title' ), 10, 2 );
}
}
public function get_title() {
$admin_title = thrive_ab()->plugin_name() . ' - ' . $this->_get_test()->title;
return $admin_title;
}
/**
* enqueue scripts
*/
public function enqueue_scripts() {
wp_enqueue_script( 'backbone' );
$js_suffix = defined( 'TVE_DEBUG' ) && TVE_DEBUG ? '.js' : '.min.js';
wp_enqueue_script( 'thrive-ab-dashboard', thrive_ab()->url( 'assets/js/dist/app' . $js_suffix ), array(
'backbone',
'jquery-ui-autocomplete',
'tve-dash-main-js',
), Thrive_AB::V, true );
$this->localize_data();
/**
* Enqueue dash js file cos it is needed for Modals, Views and Materialize
*/
tve_dash_enqueue_script( 'tve-dash-main-js', TVE_DASH_URL . '/js/dist/tve-dash.min.js', array(
'jquery',
'backbone',
), false, true );
tve_dash_enqueue_script( 'tve-dash-highcharts', TVE_DASH_URL . '/js/util/highcharts/highcharts.js', array(
'jquery',
), false, true );
tve_dash_enqueue_script( 'tve-dash-highcharts-more', TVE_DASH_URL . '/js/util/highcharts/highcharts-more.js', array(
'jquery',
'tve-dash-highcharts',
), false, true );
}
/**
* enqueue styles
*/
public function enqueue_styles() {
/**
* Inherit CSS from dashboard
*/
tve_dash_enqueue_style( 'tve-dash-styles-css', TVE_DASH_URL . '/css/styles.css' );
wp_enqueue_style( 'thrive-ab', thrive_ab()->url( 'assets/css/dashboard.css' ), array(
'tve-dash-styles-css',
), Thrive_AB::V );
/**
* Use this css file to overwrite the css for this page only
*/
wp_enqueue_style( 'thrive-ab-view-test-page', thrive_ab()->url( 'assets/css/admin/tab-view-test-page.css' ), array(
'tve-dash-styles-css',
), Thrive_AB::V );
}
/**
* put the backbone templates into page for later usage
*/
public function print_backbone_templates() {
$templates = tve_dash_get_backbone_templates( thrive_ab()->path( 'includes/views/backbone' ), 'backbone' );
tve_dash_output_backbone_templates( $templates );
}
protected function _get_test( $test_id = null ) {
if ( ! ( $this->test instanceof Thrive_AB_Test ) ) {
$this->test = new Thrive_AB_Test( (int) $this->_test_id );
}
return $this->test;
}
private function localize_data() {
$data = array();
try {
$test = $this->_get_test();
$test->get_items();
$page = new Thrive_AB_Page( (int) $test->page_id );
$report_manager = new Thrive_AB_Report_Manager();
$data['current_test'] = $test->get_data();
$data['page'] = $page->get_data();
$data['t'] = include( thrive_ab()->path( 'includes/i18n.php' ) );
$data['chart_colors'] = array(
'#20a238',
'#2f82d7',
'#fea338',
'#dd383d',
'#ab31a4',
'#95d442',
'#36c4e2',
'#525252',
'#f3643e',
'#e26edd',
);
$data['license'] = Thrive_AB::license_data();
$data['test_chart'] = $report_manager->get_test_chart_data(
array(
'test_id' => (int) $this->_test_id,
'type' => 'conversion_rate',
)
);
$data['ajax'] = array(
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( Thrive_AB_Ajax::NONCE_NAME ),
'action' => Thrive_AB_Ajax::$action,
'controller_action' => Thrive_AB_Ajax::$controller_action,
);
} catch ( Exception $e ) {
die( $e->getMessage() );
}
wp_localize_script( 'thrive-ab-dashboard', 'ThriveAB', $data );
}
/**
* puts the page html required for viewing a test
*/
public function render() {
echo '<div id="tab-dashboard-wrapper"></div>';
}
/**
* include svg file with all required icons
* usually used on admin_print_footer_scripts
*/
public function include_svg() {
include thrive_ab()->path( '/assets/fonts/icons.svg' );
}
}

View File

@@ -0,0 +1,378 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
class Thrive_AB_Ajax_Controller {
/**
* @var Thrive_AB_Ajax_Controller
*/
protected static $_instance;
private function __construct() {
}
public static function instance() {
if ( ! self::$_instance ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Sets the request's header with server protocol and status
* Sets the request's body with specified $message
*
* @param string $message the error message.
* @param string $status the error status.
*/
protected function error( $message, $status = '404 Not Found' ) {
header( $_SERVER['SERVER_PROTOCOL'] . ' ' . $status );
wp_send_json_error( array( 'message' => $message ) );
}
/**
* Returns the params from $_POST or $_REQUEST
*
* @param int $key the parameter kew.
* @param null $default the default value.
*
* @return mixed|null|$default
*/
protected function param( $key, $default = null ) {
return isset( $_POST[ $key ] ) ? $_POST[ $key ] : ( isset( $_REQUEST[ $key ] ) ? $_REQUEST[ $key ] : $default );
}
/**
* Entry-point for each ajax request
* This should dispatch the request to the appropriate method based on the "route" parameter
*
* @return array|object
*/
public function handle() {
/* Check if user still has the cap to use the plugin */
if ( ! Thrive_AB_Product::has_access() ) {
$this->error( __( 'You do not have this capability anymore', 'thrive-ab-page-testing' ) );
}
if ( ! check_ajax_referer( Thrive_AB_Ajax::NONCE_NAME, 'nonce', false ) ) {
$this->error( __( 'Invalid request.', 'thrive-ab-page-testing' ) );
}
$route = $this->param( 'route' );
$route = preg_replace( '#([^a-zA-Z0-9-])#', '', $route );
$function = $route . '_action';
if ( ! method_exists( $this, $function ) ) {
$this->error( sprintf( __( 'Method %s not implemented', 'thrive-ab-page-testing' ), $function ) );
}
$method = empty( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ? 'GET' : $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
$model = json_decode( file_get_contents( 'php://input' ), true );
return call_user_func( array( $this, $function ), $method, $model );
}
protected function tests_action( $method, $model ) {
$response = array();
switch ( $method ) {
case 'POST':
case 'PUT':
try {
if ( ! empty( $model['save_test_settings'] ) ) {
unset( $model['save_test_settings'] );
unset( $model['items'] );
Thrive_AB_Test_Manager::save_test( $model );
return true;
}
$test = Thrive_AB_Test_Manager::save_test( $model );
$test->start()->save();
$response = $test->get_data();
} catch ( Exception $e ) {
$this->error( $e->getMessage() );
}
break;
}
return $response;
}
protected function variations_action( $method, $model ) {
$response = array();
switch ( $method ) {
case 'PATCH':
try {
$id = (int) $this->param( 'ID' );
$variation = new Thrive_AB_Page_Variation( $id );
$model['ID'] = $id;
$response = $variation->save( $model );
} catch ( Exception $e ) {
$this->error( $e->getMessage() );
}
break;
case 'PUT':
case 'POST':
try {
$post_parent = ! empty( $model['post_parent'] ) ? $model['post_parent'] : null;
$page = new Thrive_AB_Page( $post_parent );
$model['meta']['traffic'] = ! empty( $model['traffic'] ) ? (int) $model['traffic'] : 0;
$model['meta']['is_control'] = ! empty( $model['is_control'] ) ? (bool) $model['is_control'] : false;
if ( ! empty( $model['action'] ) && $model['action'] == 'publish' ) {
//case it is an archived variation and we want it restored
$model['meta']['status'] = 'deleted';
$variation = $page->save_variation( $model );
$model['source_id'] = $model['ID'];
$model['ID'] = null;
$model['meta']['status'] = 'published';
} elseif ( ! empty( $model['action'] ) && $model['action'] == 'archive' ) {
// case it is a published archived and we want it archived
$model['meta']['status'] = 'archived';
} else {
// anything else
$model['meta']['status'] = 'published';
}
$variation = $page->save_variation( $model );
$variation->set_page( $page->get_post() );
if ( ! empty( $model['source_id'] ) ) {
$source_variation = new Thrive_AB_Page_Variation( $model['source_id'] );
$variation_data = $variation->get_data();
if ( ! empty( $variation_data ) ) {
$variation_id = $variation_data['ID'];
$source_variation->get_meta()->init( array(
get_post_type( $post_parent ),
'template',
) )->copy_to( $variation_id );
$source_variation->copy_thumb_to( $variation_id );
}
}
$response = $variation->get_data();
} catch ( Exception $e ) {
$this->error( $e->getMessage() );
}
break;
case 'DELETE':
try {
$id = (int) $this->param( 'ID', null );
$variation = new Thrive_AB_Page_Variation( $id );
$response = $variation->get_meta()->update( 'status', 'deleted' );
} catch ( Exception $e ) {
$this->error( $e->getMessage() );
}
break;
}
return $response;
}
/**
* Report Action Endpoint
*
* @param $method
* @param $model
*
* @return array
*/
protected function report_action( $method, $model ) {
$response = array();
switch ( $method ) {
case 'GET':
$id = (int) $this->param( 'ID' );
$interval = $this->param( 'interval' );
$type = $this->param( 'type' );
$report_manager = new Thrive_AB_Report_Manager();
return $report_manager->get_test_chart_data( array(
'test_id' => $id,
'interval' => $interval,
'type' => $type,
) );
break;
default:
break;
}
return $response;
}
protected function testitem_action( $method, $model ) {
$response = array();
switch ( $method ) {
case 'POST':
case 'PUT':
if ( ! empty( $model['stop_test_item'] ) ) {
$variation = new Thrive_AB_Page_Variation( (int) $model['variation_id'] );
$meta = $variation->get_meta();
$meta->update( 'traffic', 0 );
$meta->update( 'status', 'archived' );
$item = new Thrive_AB_Test_Item( (int) $model['id'] );
$item->stop()->save();
$item->variation = $variation;
$data = $item->get_data();
return $data;
}
break;
default:
break;
}
return $response;
}
protected function traffic_action( $method, $model ) {
if ( isset( $model['ID'] ) ) {
unset( $model['ID'] );
}
$edit_post_traffic = $this->param( 'tab_edit_post_traffic' );
if ( empty( $model ) && ! empty( $edit_post_traffic ) && is_array( $edit_post_traffic ) ) {
/**
* Traffic From Edit Post View
*/
if ( array_sum( $edit_post_traffic ) < 100 ) {
$model = $edit_post_traffic;
}
}
foreach ( $model as $id => $traffic ) {
$variation = new Thrive_AB_Variation( $id );
$variation->get_meta()->update( 'traffic', (int) $traffic );
}
}
/**
* Called From Optimize Admin Dashboard
* Returns all tests that are stored in database for display
*
* @return array
* @throws Exception
*/
protected function testsforadmin_action() {
$test_manager = new Thrive_AB_Test_Manager();
$report_manager = new Thrive_AB_Report_Manager();
$all_tests = $test_manager->get_tests();
$stats = $report_manager->get_admin_dashboard_stats();
$goals = array(
'monetary' => __( 'Revenue', 'thrive-ab-page-testing' ),
'visits' => __( 'Goal Page Visit', 'thrive-ab-page-testing' ),
'optins' => __( 'Subscriptions', 'thrive-ab-page-testing' ),
);
$return = array();
$return['running_tests'] = array();
$return['completed_tests'] = array();
$return['dashboard_stats'] = $stats;
foreach ( $all_tests as $test ) {
//When a post is in trash list, we should hide it from the Admin Tests Table
if ( get_post_status( $test['page_id'] ) !== 'publish' ) {
continue;
}
$test['date_started_pretty'] = date_i18n( 'd F Y', strtotime( $test['date_started'] ) );
$test['date_completed_pretty'] = date_i18n( 'd F Y', strtotime( $test['date_completed'] ) );
$test['goal'] = $goals[ $test['type'] ];
$test['unique_impressions'] = 0;
$test['conversions'] = 0;
try {
$ab_page = new Thrive_AB_Page( (int) $test['page_id'] );
} catch ( Exception $e ) {
continue;
}
$test['test_link'] = $ab_page->get_test_link( $test['id'] );
$test['page_title'] = $ab_page->post_title;
$items = $test_manager->get_items_by_filters( array( 'test_id' => $test['id'] ) );
foreach ( $items as $item ) {
$test['unique_impressions'] += (int) $item['unique_impressions'];
$test['conversions'] += (int) $item['conversions'];
}
if ( $test['status'] === 'running' ) {
$return['running_tests'][] = $test;
} elseif ( $test['status'] === 'completed' ) {
$return['completed_tests'][] = $test;
}
}
function running_tests_sort( $a, $b ) {
return strtotime( $b['date_started'] ) - strtotime( $a['date_started'] );
}
function completed_tests_sort( $a, $b ) {
return strtotime( $b['date_completed'] ) - strtotime( $a['date_completed'] );
}
usort( $return['running_tests'], 'running_tests_sort' );
usort( $return['completed_tests'], 'completed_tests_sort' );
return $return;
}
/**
* Deletes a test from the Admin Dashboard
*
* @return array
*/
protected function deletecompletedtestadmin_action() {
$return = array(
'success' => 0,
'text' => __( 'There was an error in the process.', 'thrive-ab-page-testing' ),
);
$id = (int) $this->param( 'id' );
$page_id = (int) $this->param( 'page_id' );
if ( ! empty( $id ) && ! empty( $page_id ) ) {
Thrive_AB_Test_Manager::delete_test( array(
'id' => $id,
'page_id' => $page_id,
) );
$return['success'] = 1;
$return['text'] = __( 'The test was deleted successfully!', 'thrive-ab-page-testing' );
}
return $return;
}
}

View File

@@ -0,0 +1,386 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
/**
* Class Thrive_AB_Ajax
*
* Defines the ajax actions and implements their hooks
*/
class Thrive_AB_Ajax {
const NONCE_NAME = 'thrive-ab-ajax-nonce';
const REGISTER_IMPRESSION_ACTION_NAME = 'register_impression';
public static $controller_action = 'thrive_ab_ajax_controller';
public static $action = 'thrive_ab_ajax_action';
public static $post_id = null;
public static function init() {
self::add_ajax_actions();
}
public static function add_ajax_actions() {
$actions = array(
self::$action => true,
self::$controller_action => true,
);
foreach ( $actions as $action => $nopriv ) {
add_action( 'wp_ajax_' . $action, array( __CLASS__, $action ) );
if ( $nopriv ) {
add_action( 'wp_ajax_nopriv_' . $action, array( __CLASS__, $action ) );
}
}
/**
* hook of TD lazy load
*/
add_filter( 'tve_dash_main_ajax_top_lazy_load', array( __CLASS__, 'dashboard_lazy_load' ), 10, 2 );
}
/**
* Handler for all
*/
public static function thrive_ab_ajax_action() {
$custom = isset( $_REQUEST['custom'] ) ? $_REQUEST['custom'] : '';
$response = array();
if ( method_exists( __CLASS__, $custom ) ) {
$response = call_user_func( array( __CLASS__, $custom ), $_REQUEST );
} else {
wp_send_json_error();
}
wp_send_json( $response );
}
public static function thrive_ab_ajax_controller() {
check_ajax_referer( self::NONCE_NAME, 'nonce' );
require_once dirname( __FILE__ ) . '/class-thrive-ab-ajax-controller.php';
$response = Thrive_AB_Ajax_Controller::instance()->handle();
wp_send_json( $response );
}
public static function post_search( $filters ) {
$s = trim( wp_unslash( $filters['q'] ) );
$s = trim( $s );
$selected_post_types = array_merge( array(
'post',
'product',
), array_diff( get_post_types(), apply_filters( 'tcb_post_grid_banned_types', array() ) ) );
if ( empty( $filters['exclude_id'] ) ) {
$filters['exclude_id'] = array();
}
$args = array(
'post_type' => $selected_post_types,
'post_status' => 'publish',
's' => $s,
'numberposts' => - 1,
'post__not_in' => $filters['exclude_id'],
'orderby' => 'title',
'order' => 'ASC',
);
$posts = array();
foreach ( get_posts( $args ) as $item ) {
$title = $item->post_title;
if ( ! empty( $s ) ) {
$item->post_title = preg_replace( "#($s)#i", '<b>$0</b>', $item->post_title );
}
$post = array(
'label' => $item->post_title,
'title' => $title,
'id' => $item->ID,
'value' => $item->post_title,
'url' => get_permalink( $item->ID ),
'edit_url' => tcb_get_editor_url( $item->ID ),
'type' => $item->post_type,
'is_popup' => isset( $post_types_data[ $item->post_type ] ) && ! empty( $post_types_data[ $item->post_type ]['event_action'] ),
);
if ( $post['is_popup'] ) {
$post['url'] = '#' . $post_types_data[ $item->post_type ]['name'] . ': ' . $title;
$post['event_action'] = $post_types_data[ $item->post_type ]['event_action'];
$post['post_type_name'] = $post_types_data[ $item->post_type ]['name'];
}
$posts [] = $post;
}
return $posts;
}
public static function add_new_page( $data ) {
if ( empty( $data['title'] ) ) {
die( __( 'Page could not be saved!', 'thrive-ab-page-testing' ) );
}
$attrs = array(
'post_content' => '<p>Thank you page</p>',
'post_title' => $data['title'],
'post_status' => 'publish',
'post_type' => 'page',
);
$post_id = wp_insert_post( $attrs );
if ( $post_id === 0 || is_wp_error( $post_id ) ) {
die( __( 'Page could not be saved!', 'thrive-ab-page-testing' ) );
}
$post = get_post( $post_id );
$post->edit_url = tcb_get_editor_url( $post_id );
return array(
'post' => $post,
);
}
public static function set_winner( $data ) {
if ( empty( $data['page_id'] ) || empty( $data['variation_id'] ) ) {
return null;
}
$page = new Thrive_AB_Page( (int) $data['page_id'] );
$winner_variation = new Thrive_AB_Page_Variation( (int) $data['variation_id'] );
$page_variation = new Thrive_AB_Page_Variation( (int) $data['page_id'] );
/**
* close test
*/
$running_test = $page->get_running_test();
if ( ! ( $running_test instanceof Thrive_AB_Test ) ) {
return null;
}
$test_item = new Thrive_AB_Test_Item();
$filters = array(
'test_id' => $running_test->id,
'page_id' => $data['page_id'],
'variation_id' => $data['page_id'],
);
$test_item->init_by_filters( $filters );
/**
* add new page variation to db with content from page
*/
$new_variation = $page->save_variation( array(
'post_title' => $page_variation->post_title,
) );
$page_variation->get_meta()->init( array(
'page',
'template',
'variation',
) )->copy_to( $new_variation->ID );
/**
* Changes the Winner Variation ID in the log table to be relevant for the report.
* This was done because on winning, a test item changes the variation_id
*/
Thrive_AB_Event_Manager::bulk_update_log( array( 'variation_id' => $new_variation->ID ), array(
'variation_id' => $test_item->variation_id,
'test_id' => $test_item->test_id,
'page_id' => $test_item->page_id,
) );
/**
* for page test item set the new variation which has the content from page
*/
$test_item->variation_id = $new_variation->ID;
/**
* set the content of winning variation to page
*/
$winner_variation->get_meta()->init( array(
'page',
'template',
'variation',
) );
$winner_variation->get_meta()->copy_to( $page_variation->ID );
if ( $winner_variation->is_control() ) {
$new_variation->get_meta()->update( 'status', 'deleted' );
} else {
$winner_variation->get_meta()->update( 'status', 'deleted' );
}
$notification_manager_item = null;
if ( (int) $test_item->is_control === 1 && (int) $test_item->id === (int) $data['id'] ) {
$test_item->is_winner = 1;
$notification_manager_item = $test_item;
} else {
/**
* Save Winner Item
*/
$winner_item = $winner_variation->get_test_item();
$winner_item->is_winner = 1;
$winner_item->save();
$notification_manager_item = $winner_item;
}
$test_item->save();
/**
* archive all other variations
*/
$all_variations = $page->get_variations( array(), 'obj' );
/** @var Thrive_AB_Page_Variation $item */
foreach ( $all_variations as $item ) {
if ( $item->is_control() ) {
continue;
}
$item->get_meta()->update( 'status', 'archived' );
}
/**
* Set status to completed once everything is done
*/
$running_test->stop()->save();
$_nm_variation = (object) $notification_manager_item->get_data();
$_nm_test = (object) $running_test->get_data();
$_nm_test->trigger_source = 'tab';
$_nm_test->url = $page->get_test_link( $running_test->id );
$_nm_variation->variation = array( 'post_title' => $_nm_variation->title, 'key' => $_nm_variation->id );
do_action( 'tab_action_set_test_item_winner', $_nm_variation, $_nm_test );
return $data;
}
public static function localize( $data ) {
if ( ! is_array( $data ) ) {
$data = array( 'ajax' );
}
$post_id = $data['post']->post_parent ? (int) $data['post']->post_parent : (int) $data['post']->ID;
try {
$page = new Thrive_AB_Page( $post_id );
$running_test = $page->get_running_test();
$data['ajax']['thrive_ab'] = array(
'action' => self::$action,
'running_test' => $running_test instanceof Thrive_AB_Test ? $running_test->id : false,
);
} catch ( Exception $e ) {
}
return $data;
}
public static function save_variation_thumb() {
if ( ! empty( $_REQUEST['reset_data'] ) ) {
Thrive_AB_Event_Manager::reset_test_data( $_REQUEST['reset_data'] );
}
if ( ! isset( $_FILES['preview_file'] ) ) {
return array(
'success' => false,
);
}
self::$post_id = $_REQUEST['post_id'];
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once( ABSPATH . 'wp-admin/includes/file.php' );
}
add_filter( 'upload_dir', array( __CLASS__, 'upload_dir' ) );
$moved_file = wp_handle_upload( $_FILES['preview_file'], array(
'action' => 'thrive_ab_ajax_action',
'unique_filename_callback' => array( __CLASS__, 'get_preview_filename' ),
) );
remove_filter( 'upload_dir', array( __CLASS__, 'upload_dir' ) );
if ( empty( $moved_file['url'] ) ) {
return array(
'success' => false,
);
}
$editor = wp_get_image_editor( $moved_file['file'] );
$editor->resize( 800, 500 );
$editor->save( $moved_file['file'] );
return array(
'success' => true,
);
}
public static function get_preview_filename() {
return self::$post_id . '.png';
}
public static function upload_dir( $upload ) {
$sub_dir = '/thrive-ab-page-testing/variations';
$upload['path'] = $upload['basedir'] . $sub_dir;
$upload['url'] = $upload['baseurl'] . $sub_dir;
$upload['subdir'] = $sub_dir;
return $upload;
}
/**
* Callback of TD Lazy Load
*
* @param $array
* @param $params
*
* @return mixed
*/
public static function dashboard_lazy_load( $array, $params ) {
/**
* register unique impression
*/
if ( ! empty( $params ) && is_array( $params ) && isset( $params['action'] ) && $params['action'] === self::REGISTER_IMPRESSION_ACTION_NAME ) {
$page_id = $params['page_id'];
$test_id = $params['test_id'];
$variation_id = $params['variation_id'];
Thrive_AB_Event_Manager::do_impression( $page_id, $test_id, $variation_id );
}
return $array;
}
}
Thrive_AB_Ajax::init();

View File

@@ -0,0 +1,380 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
class Thrive_AB_Admin {
protected static $_variations = array();
public static $pages
= array(
'view_test' => array(
'slug' => 'tab_admin_view_test',
),
);
public static function init() {
/**
* Init pages
*/
add_action( 'init', array( __CLASS__, 'init_pages' ) );
/**
* on this filter we are very sure the user is on post.php within edit case/action
*/
add_filter( 'replace_editor', array( __CLASS__, 'remove_tar_edit_button' ), 10, 2 );
add_filter( 'admin_body_class', array( __CLASS__, 'wp_editor_body_class' ), 10, 4 );
add_filter( 'page_row_actions', array( __CLASS__, 'page_row_actions' ), 11, 2 );
/**
* Add Thrive A/B Page Testing To Dashboard
*/
add_filter( 'tve_dash_admin_product_menu', array( __CLASS__, 'add_to_dashboard_menu' ) );
/**
* Add admin scripts and styles
*/
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
/**
* On Page delete, delete all the A/B Test Data
*/
add_action( 'delete_post', array( __CLASS__, 'delete_page_tests' ), 10 );
/**
* Hooks the notification manager trigger types
*/
add_filter( 'td_nm_trigger_types', array( __CLASS__, 'filter_nm_trigger_types' ) );
/**
* Thrown by Thrive Themes, maybe we should support more themes?
*/
add_filter( 'thrive_save_post_option', array( __CLASS__, 'save_meta_to_variations' ), 10, 3 );
}
/**
* Hook when a Thrive Theme Post Option is saved
* We need to replicate it to it's variations
*
* @param $post_id
* @param $meta_name
* @param $meta_value
*
* @return mixed
*/
public static function save_meta_to_variations( $meta_value, $post_id, $meta_name ) {
$post_id = (int) $post_id;
try {
$page = new Thrive_AB_Page( $post_id );
if ( empty( self::$_variations ) ) {
self::$_variations = $page->get_variations( array(), 'obj' );
}
/** @var Thrive_AB_Page_Variation $variation */
foreach ( self::$_variations as $variation ) {
if ( $variation->ID === $post_id ) {
continue;
}
$variation->get_meta()->update( $meta_name, $meta_value );
}
} catch ( Exception $e ) {
}
return $meta_value;
}
/**
* On page delete - Delete all the AB page data linked to the deleted page
*
* @param int $post_id
*/
public static function delete_page_tests( $post_id = 0 ) {
if ( empty( $post_id ) ) {
return;
}
$test_manager = new Thrive_AB_Test_Manager();
$tests = $test_manager->get_tests( array( 'page_id' => $post_id ), 'array' );
if ( empty( $tests ) ) {
return;
}
foreach ( $tests as $test ) {
Thrive_AB_Test_Manager::delete_test( array(
'id' => $test['id'],
'page_id' => $post_id,
) );
}
}
public static function remove_tar_edit_button( $return, $post ) {
try {
$page = new Thrive_AB_Page( (int) $post->ID );
$test_id = $page->get_meta()->get( 'running_test_id' );
} catch ( Exception $e ) {
}
if ( ! empty( $test_id ) ) {
remove_action( 'edit_form_after_title', array( tcb_admin(), 'admin_edit_button' ) );
add_action( 'edit_form_after_title', array( __CLASS__, 'tar_edit_button' ) );
}
return $return;
}
public static function tar_edit_button() {
include dirname( __FILE__ ) . '/views/admin/tar-edit-button.php';
}
public static function wp_editor_body_class( $classes ) {
$screen = get_current_screen();
if ( empty( $screen ) || ! $screen->base || 'post' != $screen->base ) {
return $classes;
}
$post_type = get_post_type();
$post_id = get_the_ID();
if ( empty( $post_id ) || empty( $post_type ) ) {
return $classes;
}
try {
$page = new Thrive_AB_Page( (int) $post_id );
$test_id = $page->get_meta()->get( 'running_test_id' );
} catch ( Exception $e ) {
}
if ( ! empty( $test_id ) ) {
$classes .= ' tcb-hide-wp-editor';
}
return $classes;
}
public static function page_row_actions( $actions, $page ) {
if ( empty( $actions['tcb'] ) ) {
return $actions;
}
try {
$page_instance = new Thrive_AB_Page( $page );
$test_id = $page_instance->get_meta()->get( 'running_test_id' );
if ( ! empty( $test_id ) ) {
/**
* when a pages has a test running remove some actions
*/
unset( $actions['tcb'] );
unset( $actions['edit_as_new_draft'] );
unset( $actions['trash'] );
$test_url = Thrive_AB_Test_Manager::get_test_url( $test_id );
$icon_url = thrive_ab()->url( 'assets/images/tab-logo.png' );
?>
<style type="text/css">
.thrive-ab-view-test-action {
background: url('<?php echo $icon_url ?>');
background-size: 17px 17px;
padding-left: 20px;
background-repeat: no-repeat;
}
</style>
<?php
$actions['thrive-ab'] = '<a class="thrive-ab-view-test-action" href="' . $test_url . '">' . __( 'View test details', 'thrive-ab-page-testing' ) . '</a>';
}
} catch ( Exception $e ) {
}
return $actions;
}
/**
* Push the Thrive A/B Testing to Thrive Dashboard menu
*
* @param array $menus items already in Thrive Dashboard.
*
* @return array
*/
public static function add_to_dashboard_menu( $menus = array() ) {
if ( ! class_exists( 'Thrive_AB_Product', false ) ) {
require_once dirname( __FILE__ ) . '/class-thrive-ab-product.php';
}
$menus['tab'] = array(
'parent_slug' => 'tve_dash_section',
'page_title' => __( 'Thrive Optimize', 'thrive-ab-page-testing' ),
'menu_title' => __( 'Thrive Optimize', 'thrive-ab-page-testing' ),
'capability' => Thrive_AB_Product::cap(),
'menu_slug' => 'tab_admin_dashboard',
'function' => array( __CLASS__, 'dashboard' ),
);
return $menus;
}
/**
* Enqueue all required scripts and styles
*
* @param string $hook page hook.
*/
public static function enqueue_scripts( $hook ) {
$accepted_hooks = apply_filters( 'tab_accepted_admin_pages', array(
'thrive-dashboard_page_tab_admin_dashboard',
'admin_page_tab_admin_view_test',
) );
if ( ! in_array( $hook, $accepted_hooks, true ) ) {
return;
}
/**
* Enqueue dash scripts
*/
tve_dash_enqueue();
if ( ! thrive_ab()->license_activated() ) {
return;
}
$js_suffix = defined( 'TVE_DEBUG' ) && TVE_DEBUG ? '.js' : '.min.js';
/**
* Specific admin styles
*/
wp_enqueue_style( 'tab-admin-style', thrive_ab()->url( 'assets/css/admin-styles.css' ), array(), Thrive_AB::V );
wp_enqueue_script( 'jquery' );
wp_enqueue_script( 'backbone' );
wp_enqueue_script( 'tab-admin-js', thrive_ab()->url( 'assets/js/dist/tab-admin' . $js_suffix ), array(
'jquery',
'backbone',
), Thrive_AB::V, true );
wp_localize_script( 'tab-admin-js', 'ThriveAbAdmin', self::get_localization() );
/**
* Output the main templates for backbone views used in dashboard.
*/
add_action( 'admin_print_footer_scripts', array( __CLASS__, 'render_backbone_templates' ) );
}
/**
* Render backbone templates
*/
public static function render_backbone_templates() {
$templates = tve_dash_get_backbone_templates( thrive_ab()->path( 'includes/views/admin/backbone' ), 'backbone' );
tve_dash_output_backbone_templates( $templates );
}
/**
* Output Thrive A/B Testing Dashboard - the main plugin admin page
*/
public static function dashboard() {
if ( ! thrive_ab()->license_activated() ) {
return;
}
include dirname( __FILE__ ) . '/views/admin/dashboard.php';
}
/**
* Hook into TD Notification Manager and push trigger types
*
* @param $trigger_types
*
* @return array
*/
public static function filter_nm_trigger_types( $trigger_types ) {
if ( ! in_array( 'split_test_ends', array_keys( $trigger_types ) ) ) {
$trigger_types['split_test_ends'] = __( 'A/B Test Ends', 'thrive-ab-page-testing' );
}
return $trigger_types;
}
/**
* Gets the javascript variables.
*
* @return array
*/
public static function get_localization() {
return array(
't' => array(
'Thrive_Dashboard' => __( 'Thrive Dashboard', 'thrive-ab-page-testing' ),
'Dashboard' => __( 'Optimize Dashboard', 'thrive-ab-page-testing' ),
'about_to_delete_variation' => __( 'Are you sure you want to delete %s ?', 'thrive-ab-page-testing' ),
'yes' => __( 'Yes', 'thrive-ab-page-testing' ),
'no' => __( 'No', 'thrive-ab-page-testing' ),
),
'ajax' => array(
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( Thrive_AB_Ajax::NONCE_NAME ),
'action' => Thrive_AB_Ajax::$action,
'controller_action' => Thrive_AB_Ajax::$controller_action,
),
'dash_url' => admin_url( 'admin.php?page=tve_dash_section' ),
'license' => Thrive_AB::license_data(),
);
}
/**
* callback for setting an admin menu for viewing a test
* initialize the page and add it to admin menu as submenu page
* it's not displayed on wp menu
*/
public static function view_test_menu() {
$view_test_page = new Thrive_AB_Admin_View_Test_Page();
$has_access = class_exists( 'Thrive_AB_Product', false ) ? Thrive_AB_Product::cap() : current_user_can( 'manage_options' );
add_submenu_page(
null,
__( 'View test', 'thrive-ab-page-testing' ),
__( 'View Test', 'thrive-ab-page-testing' ),
$has_access,
self::$pages['view_test']['slug'],
array( $view_test_page, 'render' )
);
}
public static function init_pages() {
require_once dirname( __FILE__ ) . '/admin/class-thrive-ab-view-test-page.php';
add_action( 'admin_menu', array( __CLASS__, 'view_test_menu' ) );
}
}
Thrive_AB_Admin::init();

View File

@@ -0,0 +1,91 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
class Thrive_AB_Checker {
/**
* @var array
*/
private static $_tar_required = array(
'active' => true,
'min_version' => '2.0.17',
);
public static function init() {
register_activation_hook( THRIVE_AB_PLUGIN_FILE, array( __CLASS__, 'check' ) );
}
/**
* Check if the requirements for this plugin are fulfilled
* If not a notification is pushed into stack for later display
*
* @return void
*/
public static function check() {
$details = self::get_thrive_architect_details();
if ( empty( $details['active'] ) ) {
Thrive_Admin_Notices::push_notice( 'active' );
return;
}
if ( ! self::is_required_version( $details['version'] ) ) {
Thrive_Admin_Notices::push_notice( 'min_version' );
return;
}
}
/**
* Check if the TAr version is greater or equal with required one
*
* @param $tar_version
*
* @return bool
*/
protected static function is_required_version( $tar_version ) {
if ( defined( 'TVE_DEBUG' ) && $tar_version === '0.dev' ) {
return true;
}
return version_compare( $tar_version, self::$_tar_required['min_version'], '>=' );
}
/**
* @return array
*/
public static function get_thrive_architect_details() {
$_defaults = array(
'active' => false,
'version' => 0,
);
$is_active = is_plugin_active( 'thrive-visual-editor/thrive-visual-editor.php' );
$version = defined( 'TVE_VERSION' ) ? TVE_VERSION : 0;
$_defaults['active'] = $is_active;
$_defaults['version'] = $version;
return $_defaults;
}
public static function get_tar_required_version() {
return self::$_tar_required['min_version'];
}
}
Thrive_AB_Checker::init();

View File

@@ -0,0 +1,55 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
/**
* Class Thrive_AB_Event_Manager
*/
class Thrive_AB_Cookie_Manager {
protected static $_instance;
public function __construct() {
}
public static function instance() {
if ( empty( self::$_instance ) ) {
self::$_instance = new self();
}
return self::$_instance;
}
public static function set_cookie( $test_id = null, $page_id = null, $variation_id = null, $type = 1 ) {
setcookie( 'top-variation-' . $type . '-' . $test_id . '-' . $page_id, $variation_id, time() + ( 30 * 24 * 3600 ), '/' );
$_COOKIE[ 'top-variation-' . $type . '-' . $test_id . '-' . $page_id ] = $variation_id;
}
public static function get_cookie( $test_id = null, $page_id = null, $type = 1 ) {
return isset( $_COOKIE[ 'top-variation-' . $type . '-' . $test_id . '-' . $page_id ] ) ? $_COOKIE[ 'top-variation-' . $type . '-' . $test_id . '-' . $page_id ] : null;
}
public static function set_impression_cookie( $test_item, $sec = 5 ) {
$cookie_name = 'top-impression-' . $test_item;
$sec = intval( $sec );
setcookie( $cookie_name, $test_item, time() + $sec, '/' );
$_COOKIE[ $cookie_name ] = $test_item;
}
public static function get_impression_cookie( $test_item ) {
$cookie_name = 'top-impression-' . $test_item;
return isset( $_COOKIE[ $cookie_name ] ) ? $_COOKIE[ $cookie_name ] : null;
}
}
return Thrive_AB_Cookie_Manager::instance();

View File

@@ -0,0 +1,440 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
class Thrive_AB_Dashboard {
static protected $_instance;
private function __construct() {
$this->_init();
}
/**
* The only point to entry in this class
* and it is instantiated only if the query var is true
*/
public static function instance() {
if ( thrive_ab()->get_query()->get_var( 'thrive-variations' ) !== 'true' ) {
return null;
}
if ( ! current_user_can( 'edit_posts' ) || ! Thrive_AB_Product::has_access() ) {
return null;
}
if ( ! self::$_instance ) {
self::$_instance = new self();
}
if ( function_exists( 'tve_do_not_cache_page' ) ) {
tve_do_not_cache_page();
}
return self::$_instance;
}
private function _clear_scripts() {
global $wp_filter;
remove_all_actions( 'wp_head' );
remove_all_actions( 'wp_footer' );
remove_all_actions( 'wp_enqueue_scripts' );
remove_all_actions( 'wp_print_styles' );
remove_all_actions( 'wp_print_footer_scripts' );
remove_all_actions( 'print_footer_scripts' );
remove_all_actions( 'admin_bar_menu' );
remove_all_filters( 'template_redirect' );
remove_all_filters( 'page_template' );
add_action( 'wp_head', 'wp_enqueue_scripts' );
add_action( 'wp_head', 'wp_print_styles' );
add_action( 'wp_head', 'wp_print_head_scripts' );
add_action( 'wp_head', '_wp_render_title_tag', 1 );
add_action( 'wp_footer', '_wp_footer_scripts' );
add_action( 'wp_footer', 'wp_print_footer_scripts', 20 );
add_action( 'wp_footer', 'print_footer_scripts', 1000 );
}
/**
* Clear the styles and scripts and add required ones
*/
private function _init() {
if ( ! thrive_ab()->license_activated() ) {
return;
}
$this->_clear_scripts();
/**
* Layout
*/
add_filter( 'page_template', array( $this, 'layout' ) );
add_filter( 'home_template', array( $this, 'layout' ) );
add_action( 'template_redirect', array( $this, 'layout' ) );
/**
* Enqueue Scripts
*/
add_action( 'wp_print_footer_scripts', array( $this, 'enqueue_scripts' ) );
/**
* Enqueue Styles
*/
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ), PHP_INT_MAX );
add_action( 'wp_print_footer_scripts', array( $this, 'print_backbone_templates' ) );
add_action( 'wp_print_footer_scripts', 'tve_dash_backbone_templates' );
/**
* Works only if the theme supports title-tag
* Thrive Themes do not support title-tag
*
* @see _wp_render_title_tag
*
*/
add_filter( 'document_title_parts', array( $this, 'get_title' ) );
}
/**
* HTML title to be displayed in AB Dashboard
* Works only if current_theme_supports( 'title-tag' )
*
* @param $title_tags
*
* @return mixed
*/
public function get_title( $title_tags ) {
array_unshift( $title_tags, thrive_ab()->plugin_name() );
return $title_tags;
}
/**
* Specify the template file to be used on dashboard
*
* @return string
*/
public function layout() {
include dirname( __FILE__ ) . '/views/layouts/dashboard.php';
die;
}
/**
* Enqueues the necessary script files for AB Dashboard
*/
public function enqueue_scripts() {
wp_enqueue_script( 'backbone' );
$js_suffix = defined( 'TVE_DEBUG' ) && TVE_DEBUG ? '.js' : '.min.js';
wp_enqueue_script( 'thrive-ab-dashboard', thrive_ab()->url( 'assets/js/dist/app' . $js_suffix ), array(
'backbone',
'jquery-ui-autocomplete',
'tve-dash-main-js',
), Thrive_AB::V, true );
$this->localize_data();
/**
* Enqueue dash js file cos it is needed for Modals, Views and Materialize
*/
tve_dash_enqueue_script( 'tve-dash-main-js', TVE_DASH_URL . '/js/dist/tve-dash.min.js', array(
'jquery',
'backbone',
), false, true );
tve_dash_enqueue_script( 'tve-dash-highcharts', TVE_DASH_URL . '/js/util/highcharts/highcharts.js', array(
'jquery',
), false, true );
tve_dash_enqueue_script( 'tve-dash-highcharts-more', TVE_DASH_URL . '/js/util/highcharts/highcharts-more.js', array(
'jquery',
'tve-dash-highcharts',
), false, true );
}
private function localize_data() {
global $post;
try {
if ( is_home() && 'page' === get_option( 'show_on_front' ) && ( $post = get_option( 'page_on_front' ) ) ) {
$post = get_post( $post );
}
$page = new Thrive_AB_Page( $post );
} catch ( Exception $e ) {
return;
}
$data = array(
'page' => $page->get_data(),
'ajax' => array(
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( Thrive_AB_Ajax::NONCE_NAME ),
'action' => Thrive_AB_Ajax::$action,
'controller_action' => Thrive_AB_Ajax::$controller_action,
),
't' => include( thrive_ab()->path( 'includes/i18n.php' ) ),
'chart_colors' => array(
'#20a238',
'#2f82d7',
'#fea338',
'#dd383d',
'#ab31a4',
'#95d442',
'#36c4e2',
'#525252',
'#f3643e',
'#e26edd',
),
);
$test_id = thrive_ab()->get_query()->get_var( 'test-id' );
if ( $test_id ) {
$current_test = $page->get_tests( array(
'id' => $test_id,
), 'obj' );
if ( count( $current_test ) ) {
$current_test = current( $current_test );
}
if ( $current_test instanceof Thrive_AB_Test ) {
if ( thrive_ab()->get_query()->get_var( 'generate-stats' ) ) {
$this->generate_random_stats( 12, $current_test );
}
$current_test->get_items();
$current_test = $current_test->get_data();
}
$data['current_test'] = $current_test;
}
if ( empty( $data['current_test'] ) && $page->get_running_test() instanceof Thrive_AB_Test ) {
$data['running_test'] = $page->get_running_test()->get_data();
} else {
$all_variations = $this->get_variations_for_page( $page );
$data['variations'] = $all_variations['published'];
$data['archived'] = $all_variations['archived'];
}
if ( ! empty( $data['current_test'] ) || ! empty( $data['running_test'] ) ) {
$test_id = ! isset( $test_id ) ? $data['running_test']['id'] : $data['current_test']['id'];
$report_manager = new Thrive_AB_Report_Manager();
$data = array_merge(
$data,
array(
'test_chart' => $report_manager->get_test_chart_data(
array(
'test_id' => (int) $test_id,
'type' => 'conversion_rate',
)
),
)
);
}
$visit_page_monetary = array(
'name' => 'Visit Page',
'label' => __( 'A customer visits certain pages on my site', 'thrive-ab-page-testing' ),
'slug' => 'visit_page',
);
$data['monetary_services'][ $visit_page_monetary['slug'] ] = $visit_page_monetary;
$data['monetary_services'] = apply_filters( 'thrive_ab_monetary_services', $data['monetary_services'] );
$data['license'] = Thrive_AB::license_data();
wp_localize_script( 'thrive-ab-dashboard', 'ThriveAB', $data );
}
/**
* Generate event logs for a test
*
* @param $days int
* @param $test Thrive_AB_Test
*
* @throws
*/
protected function generate_random_stats( $days, $test ) {
$days = intval( $days );
if ( $days <= 0 || $days > 100 || ! ( $test instanceof Thrive_AB_Test ) ) {
return;
}
$variation_ids = array();
$variation_items = array();
$test_items = $test->get_items();
/** @var Thrive_AB_Test_Item $test_item */
foreach ( $test_items as $test_item ) {
$variation_ids[] = $test_item->variation_id;
$variation_items[ $test_item->variation_id ] = $test_item;
}
$goal_page = null;
$goal_page_ids = array_keys( $test->goal_pages );
$goal_pages = $test->goal_pages;
if ( count( $goal_page_ids ) === 1 ) {
$goal_page = $goal_pages[ $goal_page_ids[0] ];
} else {
$step = 1000;
$index = (int) rand( 0, count( $goal_page_ids ) * $step );
$goal_page = $goal_pages[ $goal_page_ids[ $index ] ];
}
/**
* Loop through dates
*/
for ( $d = $days; $d > 0; $d -- ) {
$date = date( 'Y-m-d H:i:s', time() - ( $d * 60 * 60 * 24 ) );
/**
* loop variations
*/
foreach ( $variation_ids as $variation_id ) {
$test_item = $variation_items[ $variation_id ];
$step = 1000;
$total_impressions = rand( 0, 30 * $step );
$total_impressions = intval( $total_impressions / $step );
$total_conversions = rand( 0, $total_impressions * $step );
$total_conversions = intval( $total_conversions / $step );
$event_model = array(
'page_id' => $test->page_id,
'variation_id' => $variation_id,
'test_id' => $test->id,
'date' => $date,
);
/**
* log impressions
*/
for ( $i = 0; $i < $total_impressions; $i ++ ) {
$impression = $event_model;
$impression['event_type'] = 1;
$event_log = new Thrive_AB_Event( $impression );
$event_log->save();
$test_item->impressions ++;
$test_item->unique_impressions ++;
}
/**
* log conversions
*/
for ( $c = 0; $c < $total_conversions; $c ++ ) {
$conversion = $event_model;
$conversion['event_type'] = 2;
$conversion['revenue'] = isset( $goal_page['revenue'] ) ? $goal_page['revenue'] : 0;
$conversion['goal_page'] = isset( $goal_page['post_id'] ) ? $goal_page['post_id'] : null;
$event_log = new Thrive_AB_Event( $conversion );
$event_log->save();
$test_item->conversions ++;
$test_item->revenue = $test_item->revenue + ( isset( $goal_page['revenue'] ) ? $goal_page['revenue'] : 0 );
}
$test_item->save();
if ( $d === $days ) {
$test->date_started = $date;
$test->save();
}
}
}
}
/**
* If the page does not have any variations automatically creates a new once as control
* On failure of creating the control variation empty array of variations is returned
*
* @param $page Thrive_AB_Page
*
* @return array
*/
public function get_variations_for_page( $page ) {
$result = array(
'published' => array(),
'archived' => array(),
'deleted' => array(),
);
try {
$variations = $page->get_variations( array( 'all' => true ) );
foreach ( $variations as $variation ) {
$variation_object = new Thrive_AB_Variation( (int) $variation['ID'] );
$meta = $variation_object->get_meta();
$variation['status'] = $meta->get( 'status' );
if ( isset( $result[ $variation['status'] ] ) ) {
array_push( $result[ $variation['status'] ], $variation );
}
}
} catch ( Exception $e ) {
die( $e );
}
return $result;
}
/**
* Enqueue css files used on Dashboard
*/
public function enqueue_styles() {
/**
* Inherit CSS from dashboard
*/
tve_dash_enqueue_style( 'tve-dash-styles-css', TVE_DASH_URL . '/css/styles.css' );
wp_enqueue_style( 'thrive-ab', thrive_ab()->url( 'assets/css/dashboard.css' ), array(
'tve-dash-styles-css',
), Thrive_AB::V );
}
/**
* Echoes in HTML the backbone templates used on Dashboard
* Uses Thrive Dashboard functions
*/
public function print_backbone_templates() {
$templates = tve_dash_get_backbone_templates( thrive_ab()->path( 'includes/views/backbone' ), 'backbone' );
tve_dash_output_backbone_templates( $templates );
}
}

View File

@@ -0,0 +1,147 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
/**
* Class Thrive_AB_Editor
*
* Should work only in TAr Edit Mode
*/
class Thrive_AB_Editor {
protected static $_instance;
/**
* @var WP_Post
*/
protected $_post;
private function __construct( $post ) {
$this->_post = $post;
add_action( 'tcb_sidebar_extra_links', array( $this, 'render_buttons' ) );
add_action( 'tcb_main_frame_enqueue', array( $this, 'enqueue' ) );
add_action( 'tcb_has_templates_tab', array( __CLASS__, 'has_templates_tab' ), 10, 1 );
add_action( 'tcb_can_use_landing_pages', array( __CLASS__, 'can_use_landing_pages' ), 10, 1 );
add_filter( 'tcb_main_frame_localize', array( 'Thrive_AB_Ajax', 'localize' ) );
/**
* Includes the modal template files
*/
add_filter( 'tcb_modal_templates', array( __CLASS__, 'include_modal_files' ), 10, 1 );
}
/**
* Init the AB_Editor based on current post type
* and returns the instance if post type is allowed
*
* @return null|Thrive_AB_Editor
*/
public static function init() {
$post = get_post();
if ( ! thrive_ab()->is_cpt_allowed( $post->post_type ) && $post->post_type !== Thrive_AB_Post_Types::VARIATION ) {
return null;
}
if ( ! self::$_instance ) {
self::$_instance = new self( $post );
}
return self::$_instance;
}
public static function include_modal_files( $files = array() ) {
$files[] = thrive_ab()->path( 'includes/views/backbone/editor-modals/reset-stats.php' );
return $files;
}
public static function has_templates_tab( $has ) {
global $post;
if ( $post->post_type === Thrive_AB_Post_Types::VARIATION ) {
$has = true;
}
return $has;
}
public static function can_use_landing_pages( $can ) {
global $post;
if ( $post->post_type === Thrive_AB_Post_Types::VARIATION ) {
$can = true;
}
return $can;
}
/**
* Echoes the html buttons on the top sidebar of TAr Editor
*/
public function render_buttons() {
$html = '';
$page_id = thrive_ab()->maybe_variation( $this->_post ) ? $this->_post->post_parent : $this->_post->ID;
$test_manager = new Thrive_AB_Test_Manager();
$test = $test_manager->get_running_test( $page_id );
if ( $test ) {
$test_url = Thrive_AB_Test_Manager::get_test_url( $test->id );
ob_start();
include 'views/editor/html-running-test-button.php';
$html = ob_get_clean();
} elseif ( thrive_ab()->maybe_variation( $this->_post ) ) {
ob_start();
include 'views/editor/html-variation-button.php';
$html = ob_get_clean();
} elseif ( thrive_ab()->is_cpt_allowed( $this->_post->post_type ) && Thrive_AB_Product::has_access() ) {
ob_start();
include 'views/editor/html-buttons.php';
$html = ob_get_clean();
}
echo $html;
}
/**
* Enqueues the required styles and scripts for TAr Editor
*/
public function enqueue() {
/**
* scripts
*/
wp_enqueue_script( 'thrive-ab-testing-editor-script', plugin_dir_url( THRIVE_AB_PLUGIN_FILE ) . 'assets/js/dist/editor.min.js', array( 'tve-main' ), Thrive_AB::V, true );
/**
* styles
*/
wp_enqueue_style( 'thrive-ab-testing-editor-style', plugin_dir_url( THRIVE_AB_PLUGIN_FILE ) . 'assets/css/editor.css', array( 'tve2_editor_style' ), Thrive_AB::V );
}
public function get_dashboard_url() {
if ( ! thrive_ab()->license_activated() ) {
return admin_url( 'admin.php?page=tve_dash_section' );
}
$is_variation = $this->_post->post_type === Thrive_AB_Post_Types::VARIATION || $this->_post->post_status === Thrive_AB_Post_Status::VARIATION;
$url = get_permalink( $is_variation ? $this->_post->post_parent : $this->_post );
$url = add_query_arg( 'thrive-variations', 'true', $url );
return $url;
}
}

View File

@@ -0,0 +1,185 @@
<?php
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 11/14/2017
* Time: 2:00 PM
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
}
/**
* Class Thrive_AB_Meta_Box_Completed_Tests_Table
*/
class Thrive_AB_Meta_Box_Completed_Tests_Table extends WP_List_Table {
/**
* @var Thrive_AB_Page
*/
private $_page;
private $_page_tests = array();
private $_items_per_page = 100;
/**
* Thrive_AB_Meta_Box_Completed_Tests_Table constructor.
*
* @param $page Thrive_AB_Page
*/
public function __construct( $page ) {
$this->_page = $page;
$this->_page_tests = $this->_page->get_tests( array( 'status' => 'completed' ), 'instance' );
parent::__construct( array(
'singular' => 'thrive-ab-completed-test', //singular name of the listed records
'plural' => 'thrive-ab-completed-tests', //plural name of the listed records
'ajax' => false,
) );
}
/** Text displayed when no customer data is available */
public function no_items() {
echo __( 'There are no Thrive A/B Completed Tests for this page', 'thrive-ab-page-testing' );
}
/**
* Override the parent columns method. Defines the columns to use in your listing table
*
* @return array
*/
public function get_columns() {
$columns = array(
'title' => __( 'Completed Test', 'thrive-ab-page-testing' ),
'notes' => __( 'Description', 'thrive-ab-page-testing' ),
'date_started' => __( 'Start Date', 'thrive-ab-page-testing' ),
'date_completed' => __( 'End Date', 'thrive-ab-page-testing' ),
'view_test' => '',
'delete_test' => '',
);
return $columns;
}
/**
* Define which columns are hidden
*
* @return array
*/
public function get_hidden_columns() {
return array();
}
/**
* Define the sortable columns
*
* @return array
*/
public function get_sortable_columns() {
return array();
}
/**
* Get the table data
*
* @return array
*/
private function table_data() {
$data = array();
/**@var $test Thrive_AB_Test */
foreach ( $this->_page_tests as $test ) {
$tmp = $test->get_data();
$time_started = strtotime( $tmp['date_started'] );
$time_completed = strtotime( $tmp['date_completed'] );
$delete_href = sprintf( admin_url( 'admin.php?action=%s&ab_test_ID=%s&post_ID=%s' ), 'thrive-ab-tests-delete', absint( $tmp['id'] ), absint( $tmp['page_id'] ) );
$preview_link = '<a href="' . $this->_page->get_test_link( $test->id ) . '">' . tcb_icon( 'external-link', true, 'sidebar', 'thrive-ab-edit-post-icons' ) . ' ' . __( 'View Test', 'thrive-ab-page-testing' ) . '</a>';
$delete_link = '<a href="' . $delete_href . '">' . tcb_icon( 'trash-o', true, 'sidebar', 'thrive-ab-edit-post-icons' ) . ' ' . __( 'Delete', 'thrive-ab-page-testing' ) . '</a>';
$tmp['view_test'] = $preview_link;
$tmp['delete_test'] = $delete_link;
$tmp['date_started'] = $time_started ? date( 'd-m-Y', $time_started ) : '';
$tmp['date_completed'] = $time_completed ? date( 'd-m-Y', $time_completed ) : '';
$data[] = $tmp;
}
return $data;
}
/**
* Prepare the items for the table to process
*
* @return Void
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = $this->get_hidden_columns();
$sortable = $this->get_sortable_columns();
$data = $this->table_data();
$per_page = $this->_items_per_page;
$current_page = $this->get_pagenum();
$total_items = count( $data );
$this->set_pagination_args( array(
'total_items' => $total_items,
'per_page' => $per_page,
) );
$data = array_slice( $data, ( ( $current_page - 1 ) * $per_page ), $per_page );
$this->_column_headers = array( $columns, $hidden, $sortable );
$this->items = $data;
}
/**
* Override the parent display method. Defines the HTML content for your listing table
*
* @since 3.1.0
* @access public
*/
public function display() {
$singular = $this->_args['singular'];
$this->screen->render_screen_reader_content( 'heading_list' );
include dirname( __FILE__ ) . '/views/admin/edit-post/tests-table.php';
}
/**
* Define what data to show on each column of the table
*
* @param array $item Data
* @param String $column_name - Current column name
*
* @return Mixed
*/
public function column_default( $item, $column_name ) {
$value = '';
if ( ! empty( $item[ $column_name ] ) ) {
$value = $item[ $column_name ];
}
return $value;
}
/**
* Get a list of CSS classes for the WP_List_Table table tag.
*
* @since 3.1.0
*
* @return array List of CSS classes for the table tag.
*/
public function get_table_classes() {
$classes = array_diff( parent::get_table_classes(), array( 'striped' ) );
return $classes;
}
}

View File

@@ -0,0 +1,391 @@
<?php
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 11/13/2017
* Time: 4:32 PM
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
}
/**
* Class Thrive_AB_Meta_Box_Variations_Table
*/
class Thrive_AB_Meta_Box_Variations_Table extends WP_List_Table {
/**
* @var Thrive_AB_Page
*/
private $_page;
private $_variations_ins = array();
private $_page_tests = array();
private $_table_has_no_test_class = 'thrive-ab-variation-no-test';
private $_variations_per_page = 100;
/**
* Thrive_AB_Meta_Box_Variations_Table constructor.
*
* @param $page Thrive_AB_Page
*/
public function __construct( $page ) {
$this->_page = $page;
$this->_page_tests = $this->_page->get_tests( array( 'status' => 'running' ) );
$this->_variations_ins = $this->_page->get_variations( array(), 'instance' );
parent::__construct( array(
'singular' => 'thrive-ab-variation', //singular name of the listed records
'plural' => 'thrive-ab-variations', //plural name of the listed records
'ajax' => false,
) );
}
/**
* Prepare the items for the table to process
*
* @return Void
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = $this->get_hidden_columns();
$sortable = $this->get_sortable_columns();
$data = $this->_variations_ins;
$per_page = $this->_variations_per_page;
$current_page = $this->get_pagenum();
$total_items = count( $data );
$this->set_pagination_args( array(
'total_items' => $total_items,
'per_page' => $per_page,
) );
$data = array_slice( $data, ( ( $current_page - 1 ) * $per_page ), $per_page );
$this->_column_headers = array( $columns, $hidden, $sortable );
$this->items = $data;
}
/**
* Override the parent columns method. Defines the columns to use in your listing table
*
* @return array
*/
public function get_columns() {
$columns = array(
'post_title' => __( 'Name', 'thrive-ab-page-testing' ),
'traffic' => __( 'Traffic', 'thrive-ab-page-testing' ),
'impressions' => __( 'Unique Visitors', 'thrive-ab-page-testing' ),
'conversions' => __( 'Conversions', 'thrive-ab-page-testing' ),
'conversions_rate' => __( 'Conversions Rate', 'thrive-ab-page-testing' ),
'revenue' => __( 'Revenue', 'thrive-ab-page-testing' ),
'revenue_visitor' => __( 'Revenue per visitor', 'thrive-ab-page-testing' ),
'improvement' => __( 'Improvement', 'thrive-ab-page-testing' ),
'chance_to_beat_orig' => __( 'Chance to Beat Original', 'thrive-ab-page-testing' ),
);
if ( is_array( $this->_page_tests ) && count( $this->_page_tests ) && $this->_page_tests[0]['type'] === 'optins' ) {
$columns['conversions'] = __( 'Subscriptions', 'thrive-ab-page-testing' );
$columns['conversions_rate'] = __( 'Subscriptions Rate', 'thrive-ab-page-testing' );
}
return $columns;
}
/** Text displayed when no customer data is available */
public function no_items() {
echo __( 'There are no Thrive A/B Test variations for this page', 'thrive-ab-page-testing' );
}
/**
* Get a list of CSS classes for the WP_List_Table table tag.
*
* @since 3.1.0
*
* @return array List of CSS classes for the table tag.
*/
public function get_table_classes() {
$classes = parent::get_table_classes();
if ( count( $this->_page_tests ) === 0 ) {
$classes[] = $this->_table_has_no_test_class;
}
$classes = array_diff( $classes, array( 'striped' ) );
return $classes;
}
/**
* Define which columns are hidden
*
* @return array
*/
public function get_hidden_columns() {
$return = array( 'conversions', 'conversions_rate' );
$running_tests = count( $this->_page_tests );
if ( $running_tests === 0 || $running_tests > 0 && ( $this->_page_tests[0]['type'] === 'visits' || $this->_page_tests[0]['type'] === 'optins' ) ) {
$return = array( 'revenue', 'revenue_visitor' );
}
return $return;
}
/**
* Define what data to show on each column of the table
*
* @param array $item Data
* @param String $column_name - Current column name
*
* @return Mixed
*/
public function column_default( $item, $column_name ) {
$value = '';
if ( isset( $item[ $column_name ] ) ) {
$value = $item[ $column_name ];
}
return $value;
}
/**
* Define the sortable columns
*
* @return array
*/
public function get_sortable_columns() {
return array();
}
/**
* Generates content for a single row of the table
*
* @since 3.1.0
*
* @param object $item The current item
*/
public function single_row( $item ) {
if ( (int) $item->get_test_item()->active === 1 ) {
echo '<tr>';
$this->single_row_columns( $item );
echo '</tr>';
}
}
/**
* Override the parent display method. Defines the HTML content for your listing table
*
* @since 3.1.0
* @access public
*/
public function display() {
$singular = $this->_args['singular'];
$this->screen->render_screen_reader_content( 'heading_list' );
include dirname( __FILE__ ) . '/views/admin/edit-post/variations-table.php';
}
/**
* Returns Basic HTML for a column
*
* @param $classes
* @param $data
*
* @return string
*/
private function _column_template( $classes, $data ) {
$html = '';
$html .= "<td class='$classes' $data>";
$html .= '%s';
$html .= '</td>';
return $html;
}
/**
* @param $item Thrive_AB_Variation
* @param $classes
* @param $data
* @param $primary
*
* @return string
*/
public function _column_post_title( $item, $classes, $data, $primary ) {
$html = $this->_column_template( $classes, $data );
$edit_link = '';
// if ( count( $this->_page_tests ) === 0 ) {
$edit_link .= '&nbsp;&nbsp;';
$edit_link .= '<a href="' . $item->get_editor_url() . '" target="_blank" class="top-edit-icon">' . tcb_icon( 'pencil', true, 'sidebar', 'thrive-ab-edit-post-icons' ) . '</a>';
// }
$preview_link = '<a href="' . $item->get_preview_url() . '" target="_blank">' . tcb_icon( 'external-link', true, 'sidebar', 'thrive-ab-edit-post-icons' ) . '</a>';
$post_title = '<span>' . $item->post_title . '</span>&nbsp;' . $preview_link . '' . $edit_link;
return sprintf( $html, $post_title );
}
/**
* @param $item Thrive_AB_Variation
* @param $classes
* @param $data
* @param $primary
*
* @return string
*/
public function _column_traffic( $item, $classes, $data, $primary ) {
$html = $this->_column_template( $classes, $data );
$traffic = $item->get_traffic();
$item_data = $item->get_data();
$range = sprintf( '<input class="thrive-ab-traffic-input" id="thrive-ab-traffic-range-%s" type="range" min="0" max="100" step="1" value="%s" data-tab_variation_value="%s" data-tab_variation_id="%s" />', $item_data['ID'], $traffic, $traffic, $item_data['ID'] );
$input = sprintf( '<input class="thrive-ab-traffic-input" id="thrive-ab-traffic-input-%s" type="number" min="0" max="100" value="%s" data-tab_variation_value="%s" data-tab_variation_id="%s" />', $item_data['ID'], $traffic, $traffic, $item_data['ID'] );
$traffic_html = '<div class="thrive-ab-edit-post-traffic-holder"><div class="thrive-ab-edit-post-slider-holder">' . $range . '</div><div class="thrive-ab-edit-post-input-holder">' . $input . '</div></div>';//%
return sprintf( $html, $traffic_html );
}
/**
* Handles the impressions column content
*
* @param $item Thrive_AB_Variation
* @param $classes
* @param $data
* @param $primary
*
* @return string
*/
public function _column_impressions( $item, $classes, $data, $primary ) {
$html = $this->_column_template( $classes, $data );
return sprintf( $html, $item->get_test_item()->get_impressions() );
}
/**
* Handles the conversions column content
*
* @param $item Thrive_AB_Variation
* @param $classes
* @param $data
* @param $primary
*
* @return string
*/
public function _column_conversions( $item, $classes, $data, $primary ) {
$html = $this->_column_template( $classes, $data );
return sprintf( $html, $item->get_test_item()->get_conversions() );
}
/**
* Handles the conversions_rate column content
*
* @param $item Thrive_AB_Variation
* @param $classes
* @param $data
* @param $primary
*
* @return string
*/
public function _column_conversions_rate( $item, $classes, $data, $primary ) {
$html = $this->_column_template( $classes, $data );
return sprintf( $html, $item->get_test_item()->conversion_rate() . '%' );
}
/**
* Handles the revenue column content
*
* @param $item Thrive_AB_Variation
* @param $classes
* @param $data
* @param $primary
*
* @return string
*/
public function _column_revenue( $item, $classes, $data, $primary ) {
$html = $this->_column_template( $classes, $data );
return sprintf( $html, $item->get_test_item()->revenue );
}
/**
* Handles the revenue_visitor column content
*
* @param $item Thrive_AB_Variation
* @param $classes
* @param $data
* @param $primary
*
* @return string
*/
public function _column_revenue_visitor( $item, $classes, $data, $primary ) {
$html = $this->_column_template( $classes, $data );
$value = $item->get_test_item()->revenue_per_visitor();
return sprintf( $html, $value );
}
/**
* Handles the improvement column content if current item is control
*
* @param $item Thrive_AB_Variation
* @param $classes
* @param $data
* @param $primary
*
* @return string
*/
public function _column_improvement( $item, $classes, $data, $primary ) {
$html = '';
if ( $item->is_control() ) {
$classes .= ' thrv-ab-variation-control';
$html .= "<td class='$classes' $data colspan='2'>[ " . __( 'This is the control', 'thrive-ab-page-testing' ) . ' ]</td>';
echo $html;
return;
}
$improvement = $item->get_test_item()->get_improvement();
$classes .= $improvement < 0 ? ' thrive-ab-red' : ' thrive-ab-green';
$html = $this->_column_template( $classes, $data );
return sprintf( $html, $improvement . '%' );
}
/**
* Handles the chance_to_beat_orig column content if current item is control
*
* @param $item Thrive_AB_Variation
* @param $classes
* @param $data
* @param $primary
*
* @return string
*/
public function _column_chance_to_beat_orig( $item, $classes, $data, $primary ) {
if ( $item->is_control() ) {
return;
}
$chance = $item->get_test_item()->get_chance_to_beat_original();
$classes .= $chance < 0 ? ' thrive-ab-red' : ' thrive-ab-green';
$html = $this->_column_template( $classes, $data );
return sprintf( $html, $chance . '%' );
}
}

View File

@@ -0,0 +1,188 @@
<?php
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 11/13/2017
* Time: 12:58 PM
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
/**
* Class Thrive_AB_Meta_Box
*/
class Thrive_AB_Meta_Box {
private static $_instance;
private $_page;
/**
* @var int
*/
private $_completed_tests_number = 0;
/**
* Thrive_AB_Meta_Box constructor.
*/
private function __construct() {
add_action( 'load-post.php', array( $this, 'add_meta_logic' ) );
add_action( 'admin_init', array( $this, 'meta_box_request_handler' ) );
add_action( 'admin_footer', array( $this, 'include_additional_files' ) );
}
/**
* Checks the post type to be compatible with the plugin post type
*/
public function add_meta_logic() {
if ( empty( $_GET['post'] ) || ! thrive_ab()->is_cpt_allowed( get_post_type( $_GET['post'] ) ) ) {
return;
}
if ( ! isset( $_GET['action'] ) || $_GET['action'] !== 'edit' ) {
return;
}
$this->_page = new Thrive_AB_Page( (int) $_GET['post'] );
$variations = $this->_page->get_variations();
$completed_tests = $this->_page->get_tests( array( 'status' => 'completed' ), 'instance' );
if ( count( $variations ) >= 2 ) {
add_action( 'add_meta_boxes', array( $this, 'add_variation_table_meta_box' ) );
}
$this->_completed_tests_number = count( $completed_tests );
if ( $this->_completed_tests_number >= 1 ) {
add_action( 'add_meta_boxes', array( $this, 'add_completed_tests_table_meta_box' ) );
}
/**
* Enqueue Meta Box Scripts
*/
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_meta_box_scripts' ) );
}
/**
* Adds Scripts to the Meta Box Screen
*
* @param $hook
*/
public function enqueue_meta_box_scripts( $hook ) {
thrive_ab()->enqueue_style( 'thrive-ab-testing-edit-post', plugin_dir_url( THRIVE_AB_PLUGIN_FILE ) . 'assets/css/edit-post.css' );
thrive_ab()->enqueue_script( 'thrive-ab-testing-edit-post', plugin_dir_url( THRIVE_AB_PLUGIN_FILE ) . 'assets/js/dist/edit-post.min.js', array( 'jquery' ) );
wp_localize_script( 'thrive-ab-testing-edit-post', 'ThriveAbEditPost', $this->get_localization() );
}
/**
* Localization variables for Edit Post View
*
* @return array
*/
public function get_localization() {
return array(
'ajax' => array(
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( Thrive_AB_Ajax::NONCE_NAME ),
'action' => Thrive_AB_Ajax::$action,
'controller_action' => Thrive_AB_Ajax::$controller_action,
),
);
}
public function meta_box_request_handler() {
if ( ! empty( $_REQUEST['action'] ) && $_REQUEST['action'] === 'thrive-ab-tests-delete' ) {
Thrive_AB_Test_Manager::delete_test( array(
'id' => $_REQUEST['ab_test_ID'],
'page_id' => $_REQUEST['post_ID'],
) );
$return_url = get_edit_post_link( $_REQUEST['post_ID'], '' );
wp_redirect( $return_url );
exit;
}
}
/**
* Includes svg icons to editor page
*/
public function include_additional_files() {
if ( empty( $_GET['post'] ) || ! thrive_ab()->is_cpt_allowed( get_post_type( $_GET['post'] ) ) ) {
return;
}
if ( ! isset( $_GET['action'] ) || $_GET['action'] !== 'edit' ) {
return;
}
include dirname( __FILE__ ) . '/../assets/fonts/icons.svg';
}
public static function instance() {
if ( ! self::$_instance ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Adds The Variations Meta Box to the screen
*/
public function add_variation_table_meta_box() {
add_meta_box(
'thrive_ab_test_variation_table_meta_box',
__( 'Thrive Optimize - A/B Test Overview', 'thrive-ab-page-testing' ),
array( $this, 'show_variation_table_meta_box' ),
$this->_page->post_type,
'normal',
'high'
);
}
/**
* Adds the Tests Meta Box to the screen
*/
public function add_completed_tests_table_meta_box() {
add_meta_box(
'thrive_ab_test_completed_tests_table_meta_box',
__( 'Thrive Completed A/B Tests', 'thrive-ab-page-testing' ) . ' (' . $this->_completed_tests_number . ')',
array( $this, 'show_completed_tests_table_meta_box' ),
$this->_page->post_type,
'normal',
'high'
);
}
/**
* Callback for variation meta box function
*/
public function show_variation_table_meta_box() {
require_once 'class-thrive-ab-meta-box-variations-table.php';
$variations_table = new Thrive_AB_Meta_Box_Variations_Table( $this->_page );
$variations_table->prepare_items();
$variations_table->display();
}
/**
* Callback for tests meta box function
*/
public function show_completed_tests_table_meta_box() {
require_once 'class-thrive-ab-meta-box-completed-tests-table.php';
$completed_tests_table = new Thrive_AB_Meta_Box_Completed_Tests_Table( $this->_page );
$completed_tests_table->prepare_items();
$completed_tests_table->display();
}
}
Thrive_AB_Meta_Box::instance();

View File

@@ -0,0 +1,347 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
/**
* Class Thrive_AB_Meta
*
* Wrapper upon sets of metas
*
* Use this for posts before deleting the post because the WP deletes the meta and then the post;
*/
class Thrive_AB_Meta {
const PREFIX = 'thrive_ab_';
protected $_post_id;
protected $_meta = array();
protected $_variation_prefix = 'thrive_ab_';
private $_meta_types
= array(
'page',
'template',
'variation',
);
public function __construct( $post_id ) {
$this->_post_id = $post_id;
}
/**
* @return array with meta keys
*/
public function page_meta() {
return array(
'tve_landing_page',
'thrv_lp_template_colours', // Template Colors
'thrv_lp_template_gradients', // Template Gradients
'thrv_lp_template_button', // Template Button Styles
'thrv_lp_template_section', // Template Section Styles
'thrv_lp_template_contentbox', // Template ContentBox Styles
'thrv_lp_template_palettes', // Template Palettes
'tve_globals',
'thrive_tcb_post_fonts',
'tve_page_events',
'thrive_icon_pack',
'tve_has_masonry',
'tve_has_typefocus',
'tve_has_wistia_popover',
'tve_content_before_more',
'tve_updated_post',
'tve_content_more_found',
'tve_custom_css',
'tve_user_custom_css',
//set for landing page but with no tpl suffix
'tve_global_scripts',
'tve_disable_theme_dependency',
'_tve_header',
'_tve_footer',
//template
'thrive_post_template'
);
}
/**
* @return array with meta keys that have template name as suffix
*/
public function template_meta() {
return array(
'thrive_icon_pack',
'thrive_tcb_post_fonts',
'tve_content_before_more',
'tve_content_more_found',
'tve_custom_css',
'tve_globals',
'tve_has_masonry',
'tve_has_typefocus',
'tve_has_wistia_popover',
'tve_page_events',
'tve_updated_post',
'tve_user_custom_css',
);
}
public function variation_meta() {
return array(
'traffic',
'status',
'running_test_id',
);
}
public function get_thrive_theme_meta() {
if ( function_exists( '_thrive_get_meta_fields' ) ) {
return _thrive_get_meta_fields( 'post' ); //dono why the heck is post ? :)
}
$meta = array(
'thrive_meta_show_post_title',
'thrive_meta_post_breadcrumbs',
'thrive_meta_post_featured_image',
'thrive_meta_post_header_scripts',
'thrive_meta_post_body_scripts',
'thrive_meta_post_body_scripts_top',
'thrive_meta_post_custom_css',
'thrive_meta_post_share_buttons',
'thrive_meta_post_floating_icons',
'thrive_meta_social_data_title',
'thrive_meta_social_data_description',
'thrive_meta_social_image',
'thrive_meta_social_twitter_username',
'thrive_meta_post_focus_area_top',
'thrive_meta_post_focus_area_bottom',
);
return $meta;
}
/**
* if the key is not in local array then read it from db
*
* @param $key
*
* @return mixed
*/
public function get( $key ) {
if ( in_array( $key, $this->template_meta() ) ) {
$this->_meta[ $key ] = tve_get_post_meta( $this->_post_id, $key );
} elseif ( in_array( $key, $this->variation_meta() ) ) {
$this->_meta[ $key ] = get_post_meta( $this->_post_id, $this->_variation_prefix . $key, true );
} else {
$this->_meta[ $key ] = get_post_meta( $this->_post_id, $key, true );
}
if ( method_exists( $this, $key ) ) {
return call_user_func( array( $this, $key ) );
}
return $this->_meta[ $key ];
}
/**
* Gets current post meta tve_disable_theme_dependency and covert into int
* - set it into local _meta[] array
*
* @return int
*/
public function tve_disable_theme_dependency() {
$value = (int) get_post_meta( $this->_post_id, 'tve_disable_theme_dependency', true );
$this->_meta['tve_disable_theme_dependency'] = $value;
return $value;
}
/**
* Update the DB and the local value
*
* @param $key
* @param $value
*
* @return mixed
*/
public function update( $key, $value ) {
$this->_meta[ $key ] = $value;
if ( in_array( $key, $this->template_meta() ) ) {
return tve_update_post_meta( $this->_post_id, $key, $value );
}
if ( in_array( $key, $this->variation_meta() ) ) {
return update_post_meta( $this->_post_id, 'thrive_ab_' . $key, $value );
}
return update_post_meta( $this->_post_id, $key, $value );
}
public function init( $types ) {
if ( is_string( $types ) ) {
$types = array( $types );
}
$keys = array();
if ( is_array( $types ) && ! empty( $types ) ) {
foreach ( $types as $type ) {
if ( ! in_array( $type, $this->_meta_types ) ) {
continue;
}
$method = $type . '_meta';
if ( method_exists( $this, $method ) ) {
$keys = array_merge( $keys, call_user_func( array( $this, $method ) ) );
}
}
}
foreach ( $keys as $key ) {
$this->get( $key );
}
return $this;
}
public function copy_to( $post_id ) {
$new_meta = new Thrive_AB_Meta( $post_id );
foreach ( $this->_meta as $key => $value ) {
$new_meta->update( $key, $value );
}
return $new_meta;
}
/**
* Copy thrive themes page options to a variation
*
* @param $variation_id
*
* @return $this
*/
public function copy_thrive_theme_meta( $variation_id ) {
$meta = $this->get_thrive_theme_meta();
foreach ( $meta as $key ) {
$key = '_' . $key;
$value = get_post_meta( $this->_post_id, $key, true );
update_post_meta( $variation_id, $key, $value );
}
return $this;
}
/**
* Copy non thrive post meta to variations
*
* @param $post_id
*
* @return $this
*/
public function copy_non_thrive_meta( $post_id ) {
$theme_metas = get_post_meta( $this->_post_id );
if ( is_array( $theme_metas ) && ! empty( $theme_metas ) ) {
foreach ( $theme_metas as $key => $value ) {
if ( $this->maybe_thrive_meta( $key ) ) {
continue;
} else {
update_post_meta( $post_id, $key, maybe_unserialize( $value[0] ) );
}
}
}
return $this;
}
/**
* Remove unused meta of variantion if it was deleted from parent
*
* @param $post_id
*
* @return $this
*/
public function removed_unused_non_thrive_meta( $post_id ) {
if ( get_post_meta( $post_id, '_thumbnail_id' ) && ! get_post_meta( $this->_post_id, '_thumbnail_id' ) ) {
delete_post_meta( $post_id, '_thumbnail_id' );
}
return $this;
}
/**
* Check if the key contains any of the thrive prefixes
*
* @param $key
*
* @return bool true if contains any prefix which means it is a thrive meta key
*/
public function maybe_thrive_meta( $key ) {
preg_match( '/(thrive)|(tcb)|(tve)|(is_control)/', $key, $matches );
return ! empty( $matches );
}
public function get_data() {
return $this->_meta;
}
public function is_control() {
return $this->_meta['is_control'] == 1;//we should have prefixed that
}
public function get_variation_prefix() {
return $this->_variation_prefix;
}
public static function hooks() {
$instance = new self( null );
add_filter( 'is_protected_meta', array( $instance, 'is_protected' ), 10, 2 );
}
public function is_protected( $is_protected, $meta_key ) {
$variation_metas = $this->variation_meta();
foreach ( $variation_metas as $key ) {
if ( $this->_variation_prefix . $key === $meta_key || $meta_key === 'is_control' ) {
$is_protected = true;
}
}
return $is_protected;
}
}
return Thrive_AB_Meta::hooks();

View File

@@ -0,0 +1,216 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
abstract class Thrive_AB_Model {
/**
* @var wpdb
*/
protected $wpdb;
/**
* @var array associative which has indexes columns from DB
*/
protected $_data;
/**
* Thrive_AB_Model constructor.
* If model is int then a db request is made and _data is initialized
*
* @param array|int $model of data to be save in DB
*/
public function __construct( $model = array() ) {
global $wpdb;
$this->wpdb = $wpdb;
$defaults = $this->_get_default_data();
if ( ! is_array( $model ) && is_int( $model ) ) {
$this->id = $model;
$model = array( 'id' => $model );
$this->init();
$model = array_merge( $model, $this->_data );
}
$this->_data = array_merge( $defaults, $model );
}
/**
* Magic call of prop from _data
*
* @param $key
*
* @return mixed|null
*/
public function __get( $key ) {
if ( method_exists( $this, $key ) ) {
$value = call_user_func( array( $this, $key ) );
} else {
$value = isset( $this->_data[ $key ] ) ? $this->_data[ $key ] : null;
}
return $value;
}
/**
* Magic setter
*
* @param $key
* @param $value
*
* @return mixed
*/
public function __set( $key, $value ) {
if ( method_exists( $this, 'set_' . $key ) ) {
return call_user_func( array( $this, 'set_' . $key ), $value );
}
$this->_data[ $key ] = $value;
return $this->_data[ $key ];
}
/**
* Read from DB the row with id
*
* id prop has to exists in $this->_data
*
* @throws Exception
* @return $this
*/
public function init() {
if ( ! $this->id ) {
throw new Exception( __( 'Invalid model id', 'thrive-ab-page-testing' ) );
}
$data = $this->wpdb->get_row( 'SELECT * FROM ' . $this->_table_name() . ' WHERE id = ' . $this->id, ARRAY_A );
if ( ! empty( $data ) ) {
foreach ( $data as $key => $value ) {
$this->$key = $value;
}
}
return $this;
}
/**
* Based on what exists in $this->_data insert or update the DB
*
* @return $this
* @throws Exception if model is not valid or is not saved in DB
*/
public function save() {
if ( ! $this->is_valid() ) {
throw new Exception( __( sprintf( 'Invalid model %s with data: %s', get_class( $this ), var_export( $this->_data, true ) ), 'thrive-ab-page-testing' ) );
}
$data = $this->_prepare_data();
if ( isset( $data['unique'] ) ) {
unset( $data['unique'] );
}
if ( $this->id ) {
$saved = $this->wpdb->update( $this->_table_name(), $data, array( 'id' => $this->id ) );
} else {
$saved = $this->wpdb->insert( $this->_table_name(), $data );
}
if ( is_wp_error( $saved ) || $saved === false ) {
throw new Exception( __( 'Model could not be saved', 'thrive-ab-page-testing' ) );
}
if ( isset( $this->wpdb->insert_id ) && $this->wpdb->insert_id && empty( $data['id'] ) ) {
$this->id = $this->wpdb->insert_id;
}
return $this;
}
/**
* Delete by id
*
* @return false|int
* @throws Exception
*/
public function delete() {
if ( ! is_numeric( $this->id ) ) {
throw new Exception( __( 'Invalid input for delete', 'thrive-ab-page-testing' ) );
}
return $this->wpdb->delete( $this->_table_name(), array( 'id' => $this->id ) );
}
/**
* If some data is not send at initialization
* default data can be stored in db.
*
* To be overwritten by specific models
*
* @return array
*/
protected function _get_default_data() {
return array();
}
/**
* Access the all _data
*
* @return array
*/
public function get_data() {
return $this->_data;
}
/**
* Called before save. This data is stored in DB
*
* @return array
*/
protected function _prepare_data() {
return $this->_data;
}
/**
* Return what should be localized for JS
*
* @return mixed
*/
public function json_data() {
return $this->_data;
}
/**
* Returns the table name for data to be saved
*
* @return string
*/
abstract protected function _table_name();
/**
* Called before saving _data in DB
*
* @return mixed
*/
abstract protected function is_valid();
}

View File

@@ -0,0 +1,288 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
/**
* Class Thrive_AB_Page
*
* Page that will have variations for its content
*/
class Thrive_AB_Page extends Thrive_AB_Post {
/**
* @var Thrive_AB_Test
*/
protected $_running_test;
public function __construct( $post ) {
parent::__construct( $post );
if ( ! thrive_ab()->is_cpt_allowed( $this->_post->post_type ) ) {
throw new Exception( __( 'Provided post is not a page', 'thrive-ab-page-testing' ) );
}
}
/**
* Query all the variation custom posts for current page post
*
* @param $filters array wp_query args
* @param $type string default array
*
* @return array with elements of Thrive_AB_Page_Variation or array
* @throws Exception
*/
public function get_variations( $filters = array(), $type = 'array' ) {
if ( $this->_post->post_status === Thrive_AB_Post_Status::VARIATION ) {
return array();
}
$query_args = array(
'post_type' => array(
get_post_type( $this->_post->ID ),
Thrive_AB_Post_Types::VARIATION,
),
'post_parent' => $this->_post->ID,
'post_status' => 'any',
'orderby' => 'ID',
'order' => 'ASC',
'fields' => 'ids',
'posts_per_page' => -1,
);
$meta_query = array(
'relation' => 'AND',
);
if ( empty( $filters['all'] ) ) {
$meta_query['status'] = array(
'key' => Thrive_AB_Meta::PREFIX . 'status',
'value' => 'published',
);
} else {
unset( $filters['all'] );
}
$query_args['meta_query'] = $meta_query;
$query_args = array_merge( $query_args, $filters );
$query = new WP_Query( $query_args );
$post_ids = $query->get_posts();
$variations = array();
foreach ( $post_ids as $post_id ) {
$variation = new Thrive_AB_Page_Variation( $post_id );
$is_allowed = thrive_ab()->is_cpt_allowed( $variation->post_type );
if ( $is_allowed && $variation->post_status !== Thrive_AB_Post_Status::VARIATION ) {
//these posts are pages set as child from WP admin
//these posts are not variations
continue;
}
$variation->set_page( $this->_post );
$variations[] = $type === 'array' ? $variation->get_data() : $variation;
}
$parent_as_variation = new Thrive_AB_Page_Variation( $this->_post );
if ( count( $variations ) === 0 ) {
$parent_as_variation->get_meta()->update( 'traffic', 100 );
$parent_as_variation->get_meta()->update( 'is_control', 1 );
$parent_as_variation->get_meta()->update( 'status', 'published' );
}
array_unshift( $variations, $type === 'array' ? $parent_as_variation->get_data() : $parent_as_variation );
return $variations;
}
/**
* @param array $filters
* @param string $type
*
* @return array
*/
public function get_tests( $filters = array(), $type = 'array' ) {
$test_manager = new Thrive_AB_Test_Manager();
$filters = array_merge( $filters, array( 'page_id' => $this->_post->ID ) );
return $test_manager->get_tests( $filters, $type );
}
/**
* Returns the running test id of a page or null if the page has no running test
*
* @return int|null
*/
public function get_running_test_id() {
$running_test = $this->get_tests( array( 'status' => 'running' ) );
return empty( $running_test ) ? null : $running_test[0]['ID'];
}
public function get_running_test() {
if ( ! $this->_running_test instanceof Thrive_AB_Test ) {
$tests = $this->get_tests( array(
'status' => 'running',
), OBJECT );
$this->_running_test = count( $tests ) ? current( $tests ) : null;
}
if ( $this->_running_test instanceof Thrive_AB_Test ) {
$this->_running_test->get_items();
}
return $this->_running_test;
}
/**
* create a custom variation post based on the post
* and return it
*
* @param string $type
*
* @return Thrive_AB_Variation|array
*/
public function get_control_variation( $type = 'array' ) {
/** @var Thrive_AB_Variation $variation */
foreach ( $this->get_variations( array(), 'object' ) as $variation ) {
if ( $variation->get_meta()->get( 'is_control' ) === true ) {
break;
}
}
return $type === 'array' ? $variation->get_data() : $variation;
}
/**
* @param array $model
*
* @return Thrive_AB_Page_Variation
* @throws Exception if the post could not be saved or updated
*
*/
public function save_variation( $model ) {
/**
* Set default data
*/
$model = array_merge( array(
'post_status' => Thrive_AB_Post_Status::VARIATION,
'post_type' => get_post_type( $this->_post->ID ),
'post_parent' => $this->_post->ID,
'post_title' => __( 'Variation', 'thrive-ab-page-testing' ),
), $model );
if ( empty( $model['ID'] ) ) {
$post = wp_insert_post( $model );
} else {
$post = wp_update_post( $model );
}
if ( is_wp_error( $post ) || $post === 0 ) {
throw new ErrorException( __( 'Variation could not be saved', 'thrive-ab-page-testing' ) );
}
$variation = new Thrive_AB_Page_Variation( $post );
$model_has_meta = ! empty( $model['meta'] ) && is_array( $model['meta'] );
if ( $model_has_meta ) {
foreach ( $model['meta'] as $key => $value ) {
$variation->get_meta()->update( $key, $value );
}
$original_page = new Thrive_AB_Meta( $model['post_parent'] );
$variation->get_meta()->update( 'thrive_post_template', $original_page->get( 'thrive_post_template' ) );
}
return $variation;
}
/**
* Public data to be localized
*
* @return array
*/
public function get_data() {
return array(
'edit_link' => get_edit_post_link( $this->_post->ID, '' ),
'ID' => $this->_post->ID,
);
}
/**
* Implemented as hook for 'delete_post' action
*
* @param $post_id
*
* @see Thrive_AB __construct()
*
*/
public static function delete( $post_id ) {
try {
$page = new Thrive_AB_Page( $post_id );
$variations = $page->get_variations( array(), 'object' );
array_shift( $variations );
/** @var Thrive_AB_Page_Variation $variation */
foreach ( $variations as $variation ) {
$variation->get_meta()->update( 'is_control', false );
$variation->delete();
}
} catch ( Exception $e ) {
//if any of the variation cannot be deleted permanently
}
}
/**
* Hook into trash action and don't allow to trash page if it has a running test
*
* @param $null null check is made for NULL
* @param $post
*
* @return NULL or FALSE for not trashing
*/
public static function trash( $null, $post ) {
try {
$page = new Thrive_AB_Page( $post );
$test_id = $page->get_meta()->get( 'running_test_id' );
$null = empty( $test_id ) ? null : false;
} catch ( Exception $e ) {
}
return $null;
}
/**
* Returns the Start Test Url
*
* @return false|string
*/
public function get_start_test_url() {
$url = get_permalink( $this->_post );
$url = add_query_arg( 'thrive-variations', 'true', $url );
return $url;
}
public function get_test_link( $test_id ) {
return Thrive_AB_Test_Manager::get_test_url( $test_id );
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
class Thrive_AB_Post_Status {
const VARIATION = 'tab_variation';
public static function init() {
add_action( 'init', array( __CLASS__, 'register_variation_status' ) );
}
public static function register_variation_status() {
$args = array(
'label' => __( 'TOP Variation', 'thrive-ab-page-testing' ),
'label_count' => _n_noop( 'TOP Variation (%s)', 'TOP Variations (%s)', 'thrive-ab-page-testing' ),
'public' => false,//posts shown in frontend ?
'internal' => false,//internal use?
'private' => false,//posts accessed by their url?
'protected' => true,//for own user see class-wp-query.php line 2895
'exclude_from_search' => false,
'show_in_admin_all_list' => false,//posts shown in admin lists?
'show_in_admin_status_list' => false,//lists among other statues
);
register_post_status( self::VARIATION, $args );
}
}
Thrive_AB_Post_Status::init();

View File

@@ -0,0 +1,80 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
class Thrive_AB_Post_Types {
const VARIATION = 'thrive_ab_variation';
public static function init() {
add_action( 'init', array( __CLASS__, 'register_post_types' ) );
add_filter( 'tve_landing_page_post_types', array( __CLASS__, 'add_variation_type_as_landing_page' ) );
add_filter( 'tve_post_having_non_lp_settings', array( __CLASS__, 'has_non_lp_settings' ) );
add_filter( 'tcb_post_grid_banned_types', array( __CLASS__, 'add_post_grid_banned_types' ) );
}
public static function register_post_types() {
register_post_type( self::VARIATION, array(
'labels' => array(
'name' => 'Thrive Optimize Variation',
),
'hierarchical' => true,
'publicly_queryable' => true,
'query_var' => false,
'rewrite' => false,
) );
}
/**
* Tells TAr variation post type can be used as landing page
*
* @param $post_types
*
* @return array
*/
public static function add_variation_type_as_landing_page( $post_types ) {
$post_types[] = self::VARIATION;
return $post_types;
}
/**
* Add some Optimize post types to Architect Post Grid Element Banned Types
*
* @param array $banned_types
*
* @return array
*/
public static function add_post_grid_banned_types( $banned_types = array() ) {
$banned_types[] = self::VARIATION;
return $banned_types;
}
/**
* Tells TAr to load some settings for variation post type
*
* @param $post_types
*
* @return array
*/
public static function has_non_lp_settings( $post_types ) {
$post_types[] = self::VARIATION;
return $post_types;
}
}
Thrive_AB_Post_Types::init();

View File

@@ -0,0 +1,76 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
class Thrive_AB_Post {
/**
* @var WP_Post
*/
protected $_post;
/**
* @var Thrive_AB_Meta
*/
private $_meta;
/**
* Thrive_AB_Post constructor.
*
* @param $post WP_Post|int
*
* @throws Exception
*/
public function __construct( $post ) {
$post = is_int( $post ) ? get_post( $post ) : $post;
if ( ! ( $post instanceof WP_Post ) ) {
throw new Exception( __( 'Post not found', 'thrive-ab-page-testing' ) );
}
$this->_post = $post;
}
public function get_meta() {
if ( empty( $this->_meta ) ) {
$this->_meta = new Thrive_AB_Meta( $this->_post->ID );
}
return $this->_meta;
}
/**
* @param $meta Thrive_AB_Meta
*/
public function set_meta( $meta ) {
$this->_meta = $meta;
}
public function __get( $key ) {
$value = null;
if ( method_exists( $this, $key ) ) {
$value = call_user_func( array( $this, $key ) );
} elseif ( isset( $this->_post->$key ) ) {
$value = $this->_post->$key;
} elseif ( ( $meta = $this->get_meta()->get( $key ) ) !== null ) {
$value = $meta;
}
return $value;
}
public function get_post() {
return $this->_post;
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* Created by PhpStorm.
* User: Ovidiu
* Date: 12/21/2017
* Time: 2:24 PM
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
/**
* Class Thrive_AB_Product
*/
class Thrive_AB_Product extends TVE_Dash_Product_Abstract {
/**
* Tag of the product
*
* @var string tag.
*/
protected $tag = 'tab';
/**
* Slug
*
* @var string
*/
protected $slug = 'thrive-ab-page-testing';
/**
* Version
*
* @var string
*/
protected $version = Thrive_AB::V;
/**
* Name of the product displayed in Dashboard
*
* @var string title
*/
protected $title = 'Thrive Optimize';
/**
* Type of product
*
* @var string type of the product
*/
protected $type = 'plugin';
/**
* Thrive_AB_Product constructor.
*
* @param array $data info used in dashboard.
*/
public function __construct( $data = array() ) {
parent::__construct( $data );
$this->logoUrl = thrive_ab()->url( 'assets/images/tab-logo.png' );
$this->logoUrlWhite = thrive_ab()->url( 'assets/images/tab-logo-white.png' );
$this->productIds = array();
$this->description = __( 'Boost Conversion Rates by testing two or more variations of a page.', 'thrive-ab-page-testing' );
$this->button = array(
'active' => true,
'url' => admin_url( 'admin.php?page=tab_admin_dashboard' ),
'label' => __( 'Thrive Optimize', 'thrive-ab-page-testing' ),
);
$this->moreLinks = array(
'tutorials' => array(
'class' => '',
'icon_class' => 'tvd-icon-graduation-cap',
'href' => 'https://thrivethemes.com/thrive-optimize-tutorials/',
'target' => '_blank',
'text' => __( 'Tutorials', 'thrive-ab-page-testing' ),
),
'support' => array(
'class' => '',
'icon_class' => 'tvd-icon-life-bouy',
'href' => 'https://thrivethemes.com/support/',
'target' => '_blank',
'text' => __( 'Support', 'thrive-ab-page-testing' ),
),
);
}
/**
* In optimize we need to override the dash product functions just in case the dash is not loaded yet
*/
/**
* Check if the current has access to the product
*
* @return bool
*/
public static function has_access() {
return current_user_can( 'tve-use-tab' );
}
public static function cap() {
return 'tve-use-tab';
}
/**
* Reset plugin to default data
*/
public static function reset_plugin() {
$query = new WP_Query( array(
'post_type' => array(
Thrive_AB_Post_Types::VARIATION,
),
'fields' => 'ids',
'posts_per_page' => '-1',
)
);
$post_ids = $query->posts;
foreach ( $post_ids as $id ) {
wp_delete_post( $id, true );
}
global $wpdb;
$wpdb->query(
"DELETE FROM $wpdb->postmeta WHERE
`meta_key` LIKE '%thrive_ab%';"
);
$tables = array(
'event_log',
'tests',
'test_items',
);
foreach ( $tables as $table ) {
$table_name = thrive_ab()->table_name( $table );
$sql = "TRUNCATE TABLE $table_name";
$wpdb->query( $sql );
}
$wpdb->query(
"DELETE FROM $wpdb->options WHERE
`option_name` LIKE '%thrive_ab%' OR '%is_control%';"
);
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
class Thrive_AB_Query {
private $_query_vars = array(
'thrive-variations' => 'true',
'variation' => 'int',
'test-id' => 'int',
'generate-stats' => 'true',
);
public function __construct() {
add_filter( 'query_vars', array( $this, 'add_query_vars' ) );
}
public function add_query_vars( $vars ) {
foreach ( $this->_query_vars as $key => $value ) {
$vars[] = $key;
}
return $vars;
}
public function get_var( $key ) {
$value = null;
if ( in_array( $key, array_keys( $this->_query_vars ) ) ) {
global $wp;
$value = isset( $wp->query_vars[ $key ] ) ? $wp->query_vars[ $key ] : null;
}
return $value;
}
}

Some files were not shown because too many files have changed in this diff Show More