first commit

This commit is contained in:
2024-07-31 13:12:38 +07:00
commit b4e8cbe182
10213 changed files with 3125839 additions and 0 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
<span class="merlin__button--loading__spinner">
<div class="merlin-spinner">
<svg class="merlin-spinner__svg" viewbox="25 25 50 50">
<circle class="path" cx="50" cy="50" r="20" fill="none" stroke-width="6" stroke-miterlimit="10"></circle>
</svg>
</div>
</span>

View File

@@ -0,0 +1,635 @@
<svg id="sprite" xmlns="http://www.w3.org/2000/svg" version="1.0" class="hidden">
<symbol id="icon-downarrow" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m3.9975605 8.1676949c.007.2380601.0981.4659101.2578095.6425801l7.00195 7.722649c.396981.438242 1.085451.438241 1.48243.000001l7.001949-7.72265c.582578-.6425129.12707-1.6713205-.74024-1.6718803h-14.0058483c-.5631405.0009102-1.0144898.4663998-.9980502 1.0293002z"/>
</symbol>
<symbol id="icon-help" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 100 100" x="0px" y="0px">
<g data-name="Group"><path data-name="Compound Path" d="M50,17.6A32.4,32.4,0,1,0,82.4,50,32.4,32.4,0,0,0,50,17.6Zm3.2,47.7H47.1v-6h6.2ZM59.1,48a15.4,15.4,0,0,1-3,2.8L54.6,52a4.4,4.4,0,0,0-1.6,2.2,9.1,9.1,0,0,0-.3,2.4H47.2a16.2,16.2,0,0,1,.7-4.8,8.5,8.5,0,0,1,2.8-3.1l1.5-1.2a5.3,5.3,0,0,0,1.2-1.2,4.2,4.2,0,0,0,.8-2.5,4.8,4.8,0,0,0-.9-2.9q-.9-1.3-3.3-1.3a3.7,3.7,0,0,0-3.4,1.6,6.1,6.1,0,0,0-1,3.3h-6q.2-5.9,4.1-8.3a10.8,10.8,0,0,1,6-1.6,12.9,12.9,0,0,1,7.7,2.2,7.6,7.6,0,0,1,3.1,6.6A7.5,7.5,0,0,1,59.1,48Z"></path></g>
</symbol>
<symbol id="icon-welcome" width="166px" height="105px" viewBox="0 0 166 105" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Getting-Started">
<g id="getting-started">
<polygon id="Fill-310" fill="#FFFFFF" points="130.588 73.184 139.861 73.184 139.861 67.506 130.588 67.506"></polygon>
<path d="M142.664,103.301 L127.943,103.301 L127.943,72.114 C127.943,71.346 128.566,70.723 129.335,70.723 L141.273,70.723 C142.041,70.723 142.664,71.346 142.664,72.114 L142.664,103.301" id="Fill-312" fill="#FFFFFF"></path>
<path d="M149.481,94.952 C149.461,95.921 148.66,96.69 147.692,96.671 C146.723,96.652 145.954,95.851 145.973,94.882 C145.992,93.914 146.793,93.144 147.762,93.163 C148.73,93.183 149.5,93.984 149.481,94.952" id="Fill-322" fill="#FFFFFF"></path>
<path d="M136.324881,101.023 C135.858427,101.023 135.476088,100.603 135.436155,100.046 C135.341845,98.695 134.442073,97.641 133.295905,97.537 C132.828602,97.494 132.476,97.051 132.476,96.506 L132.476,93.7 L133.750464,93.7 L133.750464,96.094 C135.239888,96.403 136.39965,97.767 136.662189,99.523 L140.749,99.523 L140.749,101.023 L136.324881,101.023 Z" id="Fill-326" fill="#0073AA"></path>
<path d="M151.667,103.665 L151.667,100.387 C151.667,100.097 151.902,99.862 152.192,99.862 L164.85,99.862 C165.14,99.862 165.375,100.097 165.375,100.387 L165.375,103.665 L151.667,103.665" id="Fill-327" fill="#FFFFFF"></path>
<path d="M155.125,103.665 L153.625,103.665 L153.625,100.612 L141.417,100.612 L141.417,103.665 L139.917,103.665 L139.917,100.387 C139.917,99.684 140.489,99.112 141.192,99.112 L153.85,99.112 C154.553,99.112 155.125,99.684 155.125,100.387 L155.125,103.665" id="Fill-328" fill="#0073AA"></path>
<rect id="Rectangle" fill="#E7F7FF" x="141" y="95" width="14" height="4"></rect>
<path d="M142.159,98.554 L154.367,98.554 L154.367,95.725 L142.159,95.725 L142.159,98.554 Z M154.728,100.054 L141.798,100.054 C141.17,100.054 140.659,99.542 140.659,98.914 L140.659,95.363 C140.659,94.735 141.17,94.225 141.798,94.225 L154.728,94.225 C155.356,94.225 155.867,94.735 155.867,95.363 L155.867,98.914 C155.867,99.542 155.356,100.054 154.728,100.054 L154.728,100.054 Z" id="Fill-330" fill="#0073AA"></path>
<path d="M141.55,93.666 L153.758,93.666 L153.758,90.838 L141.55,90.838 L141.55,93.666 Z M154.119,95.166 L141.188,95.166 C140.56,95.166 140.05,94.655 140.05,94.027 L140.05,90.477 C140.05,89.849 140.56,89.338 141.188,89.338 L154.119,89.338 C154.747,89.338 155.258,89.849 155.258,90.477 L155.258,94.027 C155.258,94.655 154.747,95.166 154.119,95.166 L154.119,95.166 Z" id="Fill-332" fill="#0073AA"></path>
<path d="M101.438,7.16 L97.164,7.16 C96.75,7.16 96.414,6.824 96.414,6.41 C96.414,5.996 96.75,5.66 97.164,5.66 L101.438,5.66 C101.852,5.66 102.188,5.996 102.188,6.41 C102.188,6.824 101.852,7.16 101.438,7.16" id="Fill-340" fill="#0073AA"></path>
<path d="M99.301,9.297 C98.887,9.297 98.551,8.961 98.551,8.547 L98.551,4.273 C98.551,3.859 98.887,3.523 99.301,3.523 C99.715,3.523 100.051,3.859 100.051,4.273 L100.051,8.547 C100.051,8.961 99.715,9.297 99.301,9.297" id="Fill-341" fill="#0073AA"></path>
<path d="M42.733,24.023 L38.459,24.023 C38.045,24.023 37.709,23.688 37.709,23.273 C37.709,22.859 38.045,22.523 38.459,22.523 L42.733,22.523 C43.147,22.523 43.483,22.859 43.483,23.273 C43.483,23.688 43.147,24.023 42.733,24.023" id="Fill-342" fill="#0073AA"></path>
<path d="M40.596,26.16 C40.182,26.16 39.846,25.824 39.846,25.41 L39.846,21.136 C39.846,20.722 40.182,20.386 40.596,20.386 C41.01,20.386 41.346,20.722 41.346,21.136 L41.346,25.41 C41.346,25.824 41.01,26.16 40.596,26.16" id="Fill-343" fill="#0073AA"></path>
<path d="M152.15,52.938 C151.958,52.938 151.767,52.864 151.62,52.718 C151.327,52.425 151.327,51.95 151.62,51.657 L154.643,48.635 C154.935,48.342 155.41,48.342 155.703,48.635 C155.996,48.928 155.996,49.402 155.703,49.695 L152.681,52.718 C152.534,52.864 152.343,52.938 152.15,52.938" id="Fill-344" fill="#0073AA"></path>
<path d="M155.173,52.938 C154.98,52.938 154.789,52.864 154.643,52.718 L151.62,49.695 C151.327,49.402 151.327,48.928 151.62,48.635 C151.913,48.342 152.388,48.342 152.681,48.635 L155.703,51.657 C155.996,51.95 155.996,52.425 155.703,52.718 C155.557,52.864 155.365,52.938 155.173,52.938" id="Fill-345" fill="#0073AA"></path>
<path d="M5.332,64.237 C5.14,64.237 4.948,64.164 4.802,64.018 C4.509,63.725 4.509,63.25 4.802,62.957 L7.824,59.935 C8.117,59.642 8.592,59.642 8.885,59.935 C9.178,60.228 9.178,60.702 8.885,60.995 L5.862,64.018 C5.716,64.164 5.524,64.237 5.332,64.237" id="Fill-346" fill="#0073AA"></path>
<path d="M8.354,64.237 C8.162,64.237 7.971,64.164 7.824,64.018 L4.802,60.995 C4.509,60.702 4.509,60.228 4.802,59.935 C5.095,59.642 5.569,59.642 5.862,59.935 L8.885,62.957 C9.178,63.25 9.178,63.725 8.885,64.018 C8.738,64.164 8.547,64.237 8.354,64.237" id="Fill-347" fill="#0073AA"></path>
<polygon id="Fill-348" fill="#0073AA" points="12.078 49.372 29.474 49.372 29.474 47.872 12.078 47.872"></polygon>
<polygon id="Fill-349" fill="#0073AA" points="50.771 7.16 70.439 7.16 70.439 5.66 50.771 5.66"></polygon>
<polygon id="Fill-350" fill="#0073AA" points="112.441 51.426 150.247 51.426 150.247 49.926 112.441 49.926"></polygon>
<polygon id="Fill-351" fill="#0073AA" points="30.974 49.372 34.544 49.372 34.544 47.872 30.974 47.872"></polygon>
<polygon id="Fill-352" fill="#0073AA" points="95.949 13.22 113.346 13.22 113.346 11.72 95.949 11.72"></polygon>
<polygon id="Fill-353" fill="#0073AA" points="114.846 13.22 118.416 13.22 118.416 11.72 114.846 11.72"></polygon>
<path d="M129.29,35.307 C129.29,36.051 128.687,36.654 127.943,36.654 C127.2,36.654 126.597,36.051 126.597,35.307 C126.597,34.563 127.2,33.96 127.943,33.96 C128.687,33.96 129.29,34.563 129.29,35.307" id="Fill-354" fill="#0073AA"></path>
<path d="M60.281,56.195 C60.281,56.939 59.678,57.542 58.934,57.542 C58.19,57.542 57.587,56.939 57.587,56.195 C57.587,55.451 58.19,54.848 58.934,54.848 C59.678,54.848 60.281,55.451 60.281,56.195" id="Fill-355" fill="#0073AA"></path>
<path d="M149.201,21.202 C149.201,21.616 148.865,21.952 148.451,21.952 C148.037,21.952 147.701,21.616 147.701,21.202 C147.701,20.787 148.037,20.451 148.451,20.451 C148.865,20.451 149.201,20.787 149.201,21.202" id="Fill-356" fill="#0073AA"></path>
<path d="M57.468,1.665 C57.468,2.227 57.013,2.682 56.451,2.682 C55.889,2.682 55.434,2.227 55.434,1.665 C55.434,1.103 55.889,0.648 56.451,0.648 C57.013,0.648 57.468,1.103 57.468,1.665" id="Fill-357" fill="#0073AA"></path>
<path d="M122.492,103.665 L47.793,103.665 C43.641,103.665 40.244,100.268 40.244,96.116 L40.244,95.827 C40.244,91.675 43.641,88.278 47.793,88.278 L122.492,88.278 C126.644,88.278 130.041,91.675 130.041,95.827 L130.041,96.116 C130.041,100.268 126.644,103.665 122.492,103.665" id="Fill-358" fill="#FFFFFF"></path>
<path d="M70.679,76.533 C70.679,83.02 65.421,88.278 58.934,88.278 C52.448,88.278 47.189,83.02 47.189,76.533 C47.189,70.047 52.448,64.789 58.934,64.789 C65.421,64.789 70.679,70.047 70.679,76.533" id="Fill-359" fill="#FFFFFF"></path>
<path d="M50.772,78.312 C50.772,80.879 48.691,82.96 46.124,82.96 C43.556,82.96 41.475,80.879 41.475,78.312 C41.475,75.744 43.556,73.663 46.124,73.663 C48.691,73.663 50.772,75.744 50.772,78.312" id="Fill-360" fill="#FFFFFF"></path>
<path d="M48.498,83.603 C48.498,87.387 45.431,90.454 41.648,90.454 C37.865,90.454 34.798,87.387 34.798,83.603 C34.798,79.82 37.865,76.753 41.648,76.753 C45.431,76.753 48.498,79.82 48.498,83.603" id="Fill-361" fill="#FFFFFF"></path>
<path d="M49.505,94.404 C49.505,99.519 45.359,103.665 40.244,103.665 C35.13,103.665 30.983,99.519 30.983,94.404 C30.983,89.289 35.13,85.143 40.244,85.143 C45.359,85.143 49.505,89.289 49.505,94.404" id="Fill-362" fill="#FFFFFF"></path>
<path d="M40.244,104.415 C34.725,104.415 30.233,99.924 30.233,94.404 C30.233,91.145 31.828,88.08 34.499,86.205 L35.36,87.434 C33.089,89.027 31.733,91.633 31.733,94.404 C31.733,99.097 35.552,102.915 40.244,102.915 L40.244,104.415" id="Fill-363" fill="#0073AA"></path>
<path d="M73.306,85.898 C73.306,88.045 71.566,89.785 69.42,89.785 C67.273,89.785 65.533,88.045 65.533,85.898 C65.533,83.752 67.273,82.011 69.42,82.011 C71.566,82.011 73.306,83.752 73.306,85.898" id="Fill-364" fill="#FFFFFF"></path>
<path d="M120.686,76.042 C120.686,81.948 115.899,86.735 109.993,86.735 C104.088,86.735 99.301,81.948 99.301,76.042 C99.301,70.137 104.088,65.349 109.993,65.349 C115.899,65.349 120.686,70.137 120.686,76.042" id="Fill-365" fill="#FFFFFF"></path>
<path d="M122.552,82.011 C122.21,85.8 119.49,88.852 115.712,88.852 C111.934,88.852 108.871,85.789 108.871,82.011 C108.871,78.233 111.934,75.171 115.712,75.171 C119.49,75.171 122.892,78.249 122.552,82.011" id="Fill-366" fill="#FFFFFF"></path>
<path d="M115.7,86.735 C115.357,90.524 112.637,93.576 108.859,93.576 C105.081,93.576 102.019,90.513 102.019,86.735 C102.019,82.957 105.081,79.895 108.859,79.895 C112.637,79.895 116.04,82.973 115.7,86.735" id="Fill-367" fill="#FFFFFF"></path>
<path d="M68.646,86.824 C67.993,90.612 62.806,93.664 55.601,93.664 C48.397,93.664 42.556,90.602 42.556,86.824 C42.556,83.046 48.397,79.983 55.601,79.983 C62.806,79.983 69.295,83.061 68.646,86.824" id="Fill-368" fill="#FFFFFF"></path>
<path d="M136.204,83.664 C135.704,89.203 131.727,93.664 126.204,93.664 C120.681,93.664 116.203,89.187 116.203,83.664 C116.203,78.141 120.681,73.663 126.204,73.663 C131.727,73.663 136.702,78.163 136.204,83.664" id="Fill-369" fill="#FFFFFF"></path>
<path d="M133.476,94.017 C132.993,99.36 129.156,103.665 123.828,103.665 C118.5,103.665 114.18,99.345 114.18,94.017 C114.18,88.689 118.5,84.369 123.828,84.369 C129.156,84.369 133.956,88.71 133.476,94.017" id="Fill-370" fill="#FFFFFF"></path>
<path d="M107.183,83.126 C107.183,86.594 104.371,89.406 100.903,89.406 C97.435,89.406 94.624,86.594 94.624,83.126 C94.624,79.658 97.435,76.847 100.903,76.847 C104.371,76.847 107.183,79.658 107.183,83.126" id="Fill-371" fill="#FFFFFF"></path>
<path d="M99.082,88.633 C99.082,90.657 97.441,92.297 95.418,92.297 C93.395,92.297 91.754,90.657 91.754,88.633 C91.754,86.61 93.395,84.969 95.418,84.969 C97.441,84.969 99.082,86.61 99.082,88.633" id="Fill-372" fill="#FFFFFF"></path>
<path d="M92.195,88.972 L90.695,88.972 C90.695,86.538 92.676,84.558 95.109,84.558 L95.109,86.058 C93.503,86.058 92.195,87.365 92.195,88.972" id="Fill-373" fill="#0073AA"></path>
<path d="M84.029,90.971 C84.029,93.31 80.759,95.207 76.724,95.207 C72.69,95.207 69.42,93.31 69.42,90.971 C69.42,88.632 72.69,86.735 76.724,86.735 C80.759,86.735 84.029,88.632 84.029,90.971" id="Fill-374" fill="#FFFFFF"></path>
<path d="M126.407,73.973 C126.407,76.69 121.797,83.938 121.797,80.606 C121.797,77.89 116.879,78.404 116.879,75.688 C116.879,72.971 119.081,70.769 121.797,70.769 C124.514,70.769 126.407,71.257 126.407,73.973" id="Fill-375" fill="#FFFFFF"></path>
<path d="M125.598,73.972 C124.924,72.481 123.432,71.519 121.797,71.519 C121.219,71.519 120.657,71.635 120.13,71.865 L119.53,70.49 C120.248,70.178 121.011,70.019 121.797,70.019 C124.019,70.019 126.049,71.328 126.965,73.355 L125.598,73.972" id="Fill-376" fill="#0073AA"></path>
<path d="M101.902,84.134 C99.741,81.973 98.551,79.099 98.551,76.042 C98.551,72.984 99.741,70.111 101.902,67.951 C104.063,65.79 106.935,64.6 109.993,64.6 C113.05,64.6 115.924,65.79 118.085,67.951 C120.246,70.112 121.436,72.985 121.436,76.042 L119.936,76.042 C119.936,73.387 118.902,70.89 117.024,69.012 C115.146,67.134 112.649,66.1 109.993,66.1 C107.337,66.1 104.84,67.134 102.963,69.012 C101.085,70.889 100.051,73.386 100.051,76.042 C100.051,78.698 101.085,81.195 102.963,83.073 L101.902,84.134" id="Fill-377" fill="#0073AA"></path>
<polygon id="Fill-378" fill="#FFFFFF" points="77.451 51.464 89.899 51.464 89.899 46.22 77.451 46.22"></polygon>
<path d="M78.201,50.714 L89.148,50.714 L89.148,46.97 L78.201,46.97 L78.201,50.714 Z M76.701,52.214 L90.648,52.214 L90.648,45.47 L76.701,45.47 L76.701,52.214 Z" id="Fill-379" fill="#0073AA"></path>
<path d="M64.551,46.294 L76.328,46.294 L76.328,20.645 L71.981,26.524 C70.731,28.215 69.261,29.72 67.751,31.184 C65.729,33.146 64.551,35.865 64.551,38.748 L64.551,46.294" id="Fill-380" fill="#ADDDF8"></path>
<path d="M75.413,45.544 L72.563,45.544 L72.563,26.999 C72.57,26.989 72.578,26.98 72.585,26.97 L75.413,23.144 L75.413,45.544" id="Fill-381" fill="#8AC4E6"></path>
<path d="M102.228,46.785 L90.451,46.785 L90.451,21.136 L94.797,27.015 C96.048,28.706 97.517,30.211 99.027,31.675 C101.05,33.637 102.228,36.356 102.228,39.239 L102.228,46.785" id="Fill-382" fill="#ADDDF8"></path>
<polyline id="Fill-383" fill="#A6CFE4" points="94.578 26.718 91.618 22.714 91.618 21.328 94.578 25.331 94.578 26.718"></polyline>
<polyline id="Fill-384" fill-opacity="0.35266644" fill="#4B97C6" points="94.578 45.889 91.618 45.889 91.618 22.714 94.578 26.718 94.578 45.889"></polyline>
<path d="M65.301,45.544 L75.577,45.544 L75.577,22.922 L72.585,26.97 C71.271,28.745 69.736,30.303 68.274,31.722 C66.385,33.554 65.301,36.115 65.301,38.748 L65.301,45.544 M77.077,47.044 L63.801,47.044 L63.801,38.748 C63.801,35.712 65.051,32.758 67.229,30.645 C68.647,29.27 70.13,27.766 71.379,26.077 L77.077,18.369 L77.077,47.044" id="Fill-385" fill="#0073AA"></path>
<path d="M91.201,45.544 L101.477,45.544 L101.477,38.748 C101.477,36.115 100.394,33.555 98.505,31.722 C97.043,30.304 95.509,28.747 94.194,26.969 L91.201,22.921 L91.201,45.544 Z M102.977,47.044 L89.701,47.044 L89.701,18.37 L95.401,26.077 C96.65,27.769 98.133,29.272 99.55,30.646 C101.728,32.759 102.977,35.712 102.977,38.748 L102.977,47.044 L102.977,47.044 Z" id="Fill-386" fill="#0073AA"></path>
<path d="M91.618,48.85 L75.413,48.85 L75.413,15.269 C75.413,11.35 77.228,7.653 80.329,5.258 L81.776,4.14 C82.8,3.349 84.23,3.349 85.255,4.14 L86.833,5.36 C89.851,7.691 91.618,11.289 91.618,15.102 L91.618,48.85" id="Fill-387" fill="#FFFFFF"></path>
<path d="M79.37,6.274 L81.3,4.784 C82.666,3.728 84.573,3.728 85.939,4.784 L88.045,6.41 L79.37,6.274" id="Fill-388" fill="#0073AA"></path>
<path d="M75.413,15.269 C75.413,11.35 77.228,7.653 80.329,5.258 L81.776,4.14 C82.288,3.745 82.902,3.547 83.515,3.547 C84.129,3.547 84.742,3.745 85.255,4.14 C84.742,3.745 84.129,3.547 83.515,3.547 C82.902,3.547 82.288,3.745 81.776,4.14 L80.329,5.258 C77.228,7.653 75.413,11.35 75.413,15.269 M85.296,4.172 L85.255,4.14 L85.296,4.172" id="Fill-389" fill="#AFC9D6"></path>
<path d="M78.974,48.85 L75.413,48.85 L75.413,15.269 C75.413,11.35 77.228,7.653 80.329,5.258 L81.776,4.14 C82.288,3.745 82.902,3.547 83.515,3.547 C84.129,3.547 84.742,3.745 85.255,4.14 L85.296,4.172 L85.126,4.303 C84.646,4.096 84.133,3.992 83.62,3.992 C82.801,3.992 81.983,4.256 81.3,4.784 L79.37,6.274 L82.677,6.326 C80.321,8.682 78.974,11.893 78.974,15.269 L78.974,48.85" id="Fill-390" fill="#E7F7FF"></path>
<path d="M82.677,6.326 L79.37,6.274 L81.3,4.784 C81.983,4.256 82.801,3.992 83.62,3.992 C84.133,3.992 84.646,4.096 85.126,4.303 L83.89,5.258 C83.461,5.59 83.056,5.947 82.677,6.326" id="Fill-391" fill="#0073AA"></path>
<path d="M88.386,22.231 L86.886,22.231 C86.886,20.373 85.374,18.861 83.516,18.861 C81.657,18.861 80.144,20.373 80.144,22.231 L78.644,22.231 C78.644,19.546 80.83,17.361 83.516,17.361 C86.201,17.361 88.386,19.546 88.386,22.231" id="Fill-392" fill="#0073AA"></path>
<path d="M76.163,48.1 L90.868,48.1 L90.868,15.103 C90.868,11.547 89.188,8.127 86.374,5.953 L84.797,4.734 C84.041,4.15 82.989,4.15 82.235,4.734 L80.787,5.852 C77.892,8.089 76.163,11.609 76.163,15.269 L76.163,48.1 Z M92.368,49.6 L74.663,49.6 L74.663,15.269 C74.663,11.148 76.609,7.184 79.87,4.664 L81.318,3.547 C82.611,2.547 84.419,2.547 85.714,3.547 L87.291,4.766 C90.471,7.222 92.368,11.086 92.368,15.103 L92.368,49.6 L92.368,49.6 Z" id="Fill-393" fill="#0073AA"></path>
<path d="M100.903,90.155 C97.027,90.155 93.874,87.002 93.874,83.126 C93.874,79.936 96.026,77.141 99.107,76.328 L99.49,77.779 C97.066,78.418 95.374,80.616 95.374,83.126 C95.374,86.175 97.854,88.655 100.903,88.655 L100.903,90.155" id="Fill-394" fill="#0073AA"></path>
<path d="M65.124,87.39 L64.38,86.087 C67.803,84.131 69.929,80.471 69.929,76.533 C69.929,70.471 64.997,65.539 58.935,65.539 C52.872,65.539 47.939,70.471 47.939,76.533 L46.439,76.533 C46.439,69.644 52.045,64.039 58.935,64.039 C65.824,64.039 71.429,69.644 71.429,76.533 C71.429,81.008 69.013,85.167 65.124,87.39" id="Fill-395" fill="#0073AA"></path>
<path d="M44.285,74.873 L43.578,73.551 C44.833,72.879 46.358,72.732 47.716,73.152 L47.274,74.586 C46.293,74.284 45.191,74.389 44.285,74.873" id="Fill-396" fill="#0073AA"></path>
<path d="M69.42,90.535 L69.42,89.035 C71.149,89.035 72.557,87.628 72.557,85.898 C72.557,84.169 71.149,82.762 69.42,82.762 L69.42,81.262 C71.977,81.262 74.057,83.342 74.057,85.898 C74.057,88.455 71.977,90.535 69.42,90.535" id="Fill-397" fill="#0073AA"></path>
<path d="M99.033,94.918 L71.351,94.918 L79.363,85.898 L79.363,52.557 L87.943,52.557 L87.574,82.994 C87.551,84.824 88.383,86.56 89.823,87.691 L99.033,94.918" id="Fill-398" fill="#FFFFFF"></path>
<path d="M92.003,88.408 L91.112,88.408 C88.865,88.386 87.044,86.546 87.044,84.307 L87.044,52.557 L88.544,52.557 L88.544,84.307 C88.544,85.727 89.699,86.894 91.12,86.908 L92.003,86.908 L92.003,88.408" id="Fill-399" fill="#0073AA"></path>
<path d="M76.339,87.574 L73.298,87.574 L73.298,86.074 L76.339,86.074 C77.593,86.074 78.613,85.054 78.613,83.8 L78.613,52.557 L80.113,52.557 L80.113,83.8 C80.113,85.881 78.42,87.574 76.339,87.574" id="Fill-400" fill="#0073AA"></path>
<path d="M74.85,101.262 C74.842,100.523 74.977,99.794 75.249,99.099 C75.521,98.405 75.919,97.781 76.43,97.243 C76.96,96.687 77.588,96.247 78.298,95.938 C79.008,95.628 79.758,95.467 80.527,95.459 C81.263,95.461 81.996,95.585 82.69,95.857 C83.385,96.132 84.009,96.529 84.546,97.039 L83.513,98.127 C83.116,97.75 82.654,97.456 82.141,97.253 C81.643,97.058 81.12,96.959 80.588,96.959 L80.543,96.959 C79.976,96.965 79.422,97.084 78.897,97.313 C78.373,97.542 77.908,97.866 77.517,98.277 C77.141,98.673 76.847,99.135 76.645,99.648 C76.443,100.162 76.344,100.7 76.35,101.246 L74.85,101.262" id="Fill-401" fill="#0073AA"></path>
<path d="M50.021,89.266 C50.014,88.524 50.148,87.797 50.421,87.103 C50.694,86.408 51.092,85.783 51.603,85.246 C52.134,84.689 52.763,84.249 53.472,83.941 C54.18,83.632 54.93,83.471 55.699,83.463 C56.45,83.464 57.17,83.589 57.862,83.861 C58.555,84.134 59.179,84.531 59.718,85.043 L58.685,86.131 C58.286,85.753 57.825,85.459 57.313,85.258 C56.801,85.056 56.251,84.946 55.715,84.963 C55.147,84.969 54.594,85.088 54.07,85.316 C53.546,85.544 53.081,85.868 52.69,86.28 C52.313,86.677 52.019,87.138 51.817,87.651 C51.615,88.165 51.516,88.703 51.521,89.25 L50.021,89.266" id="Fill-402" fill="#0073AA"></path>
<path d="M114.568,86.589 L113.164,86.064 C113.355,85.551 113.444,85.012 113.428,84.461 C113.413,83.912 113.293,83.378 113.072,82.874 C112.846,82.356 112.519,81.895 112.102,81.5 C111.686,81.107 111.206,80.808 110.674,80.609 C110.161,80.417 109.619,80.319 109.07,80.345 C108.521,80.359 107.987,80.48 107.483,80.7 L106.882,79.325 C107.564,79.027 108.286,78.865 109.028,78.845 C109.77,78.831 110.502,78.944 111.198,79.204 C111.919,79.473 112.57,79.879 113.133,80.41 C113.695,80.942 114.138,81.569 114.446,82.272 C114.745,82.955 114.907,83.677 114.928,84.419 C114.949,85.163 114.828,85.894 114.568,86.589" id="Fill-403" fill="#0073AA"></path>
<path d="M81.966,96.485 L80.479,96.284 C80.583,95.52 80.835,94.795 81.228,94.131 C81.62,93.468 82.134,92.898 82.753,92.439 C83.352,91.996 84.019,91.679 84.739,91.493 C85.463,91.31 86.201,91.266 86.935,91.363 L86.736,92.85 C86.194,92.776 85.647,92.809 85.112,92.946 C84.581,93.083 84.087,93.318 83.644,93.645 C83.188,93.982 82.81,94.403 82.52,94.895 C82.228,95.387 82.042,95.921 81.966,96.485" id="Fill-404" fill="#0073AA"></path>
<path d="M114.923,97.504 L113.436,97.303 C113.539,96.541 113.791,95.817 114.185,95.15 C114.579,94.484 115.092,93.915 115.71,93.457 C116.307,93.016 116.975,92.698 117.696,92.512 C118.42,92.326 119.158,92.284 119.893,92.382 L119.693,93.868 C119.151,93.794 118.603,93.826 118.069,93.965 C117.536,94.103 117.042,94.337 116.602,94.662 C116.146,95 115.768,95.421 115.477,95.913 C115.185,96.406 114.999,96.941 114.923,97.504" id="Fill-405" fill="#0073AA"></path>
<path d="M97.82,104.67 C97.09,104.43 96.424,104.05 95.841,103.542 C95.258,103.033 94.791,102.424 94.454,101.733 C94.128,101.065 93.938,100.35 93.888,99.607 C93.836,98.865 93.928,98.131 94.159,97.425 L95.585,97.892 C95.414,98.413 95.346,98.956 95.384,99.505 C95.421,100.055 95.561,100.582 95.802,101.075 C96.051,101.586 96.395,102.035 96.827,102.411 C97.258,102.787 97.75,103.067 98.289,103.244 L97.82,104.67" id="Fill-406" fill="#0073AA" transform="translate(96.081444, 101.047500) rotate(-7.000000) translate(-96.081444, -101.047500) "></path>
<path d="M36.273,88.978 C34.838,87.541 34.048,85.633 34.048,83.604 C34.048,79.412 37.457,76.003 41.648,76.003 C43.405,76.003 45.119,76.617 46.475,77.731 L45.521,78.89 C44.435,77.995 43.059,77.503 41.648,77.503 C38.284,77.503 35.548,80.239 35.548,83.604 C35.548,85.232 36.183,86.765 37.334,87.917 L36.273,88.978" id="Fill-407" fill="#0073AA"></path>
<path d="M130.726,93.384 L130.065,92.036 C133.054,90.573 135.119,87.34 135.457,83.597 C135.655,81.409 134.9,79.259 133.331,77.541 C131.048,75.044 127.321,73.874 124.019,74.673 L123.668,73.214 C127.486,72.293 131.796,73.638 134.438,76.529 C136.295,78.563 137.188,81.12 136.951,83.731 C136.56,88.058 134.233,91.666 130.726,93.384" id="Fill-408" fill="#0073AA"></path>
<path d="M123.828,104.415 L123.828,102.915 C128.517,102.915 132.26,99.145 132.728,93.949 C132.815,92.994 132.716,92.046 132.433,91.133 L133.864,90.688 C134.207,91.79 134.327,92.933 134.223,94.085 C133.683,100.07 129.31,104.415 123.828,104.415" id="Fill-409" fill="#0073AA"></path>
<path d="M43.833,81.368 L17.011,81.368 C16.226,81.368 15.59,82.004 15.59,82.788 L15.59,103.676 L42.442,103.676 C44.033,103.676 45.323,102.386 45.323,100.794 L45.323,82.858 C45.323,82.035 44.656,81.368 43.833,81.368" id="Fill-410" fill="#62A6CF"></path>
<path d="M42.988,104.426 L14.841,104.426 L14.841,82.519 C14.841,81.471 15.693,80.618 16.741,80.618 L34.269,80.618 L34.269,82.118 L16.741,82.118 C16.52,82.118 16.341,82.298 16.341,82.519 L16.341,102.926 L42.988,102.926 C43.862,102.926 44.573,102.215 44.573,101.341 L44.573,82.576 C44.573,82.323 44.368,82.118 44.115,82.118 L42.284,82.118 L42.284,80.618 L44.115,80.618 C45.195,80.618 46.073,81.496 46.073,82.576 L46.073,101.341 C46.073,103.042 44.689,104.426 42.988,104.426" id="Fill-411" fill="#0073AA"></path>
<path d="M34.673,73.02 L40.596,78.943 L40.596,99.62 C40.596,100.556 39.837,101.315 38.902,101.315 L21.218,101.315 C20.283,101.315 19.524,100.556 19.524,99.62 L19.524,74.714 C19.524,73.778 20.283,73.02 21.218,73.02 L34.673,73.02" id="Fill-412" fill="#FFFFFF"></path>
<polyline id="Fill-413" fill="#62A6CF" points="34.673 78.943 34.673 73.02 40.596 78.943 34.673 78.943"></polyline>
<path d="M40.596,79.692 L35.265,79.692 C34.524,79.692 33.923,79.091 33.923,78.351 L33.923,73.02 L35.423,73.02 L35.423,78.192 L40.596,78.192 L40.596,79.692" id="Fill-414" fill="#0073AA"></path>
<path d="M38.901,102.065 L21.219,102.065 L21.219,100.565 L38.901,100.565 C39.422,100.565 39.846,100.141 39.846,99.62 L39.846,79.253 L34.362,73.77 L21.219,73.77 C20.697,73.77 20.273,74.193 20.273,74.714 L20.273,82.695 L18.773,82.695 L18.773,74.714 C18.773,73.366 19.87,72.27 21.219,72.27 L34.983,72.27 L41.346,78.632 L41.346,99.62 C41.346,100.968 40.249,102.065 38.901,102.065" id="Fill-415" fill="#0073AA"></path>
<path d="M38.123,71.295 L44.046,77.218 L44.046,97.895 C44.046,98.831 43.287,99.59 42.351,99.59 L24.668,99.59 C23.732,99.59 22.974,98.831 22.974,97.895 L22.974,72.989 C22.974,72.053 23.732,71.295 24.668,71.295 L38.123,71.295" id="Fill-416" fill="#ADDDF8"></path>
<polyline id="Fill-417" fill="#62A6CF" points="38.123 77.218 38.123 71.295 44.046 77.218 38.123 77.218"></polyline>
<path d="M44.046,77.968 L38.715,77.968 C37.975,77.968 37.373,77.366 37.373,76.626 L37.373,71.295 L38.873,71.295 L38.873,76.468 L44.046,76.468 L44.046,77.968" id="Fill-418" fill="#0073AA"></path>
<polygon id="Fill-419" fill="#0073AA" points="26.133 80.857 41.201 80.857 41.201 79.357 26.133 79.357"></polygon>
<polygon id="Fill-420" fill="#0073AA" points="26.133 84.218 41.201 84.218 41.201 82.718 26.133 82.718"></polygon>
<polygon id="Fill-421" fill="#0073AA" points="26.133 87.577 41.201 87.577 41.201 86.077 26.133 86.077"></polygon>
<polygon id="Fill-422" fill="#0073AA" points="26.133 90.938 41.201 90.938 41.201 89.438 26.133 89.438"></polygon>
<path d="M42.352,100.34 L41.572,100.34 L41.572,98.84 L42.352,98.84 C42.872,98.84 43.296,98.416 43.296,97.896 L43.296,77.528 L37.813,72.045 L24.668,72.045 C24.147,72.045 23.724,72.469 23.724,72.989 L23.724,84.941 L22.224,84.941 L22.224,72.989 C22.224,71.642 23.32,70.545 24.668,70.545 L38.434,70.545 L44.796,76.907 L44.796,97.896 C44.796,99.243 43.699,100.34 42.352,100.34" id="Fill-423" fill="#0073AA"></path>
<path d="M41.572,73.882 L47.496,79.805 L47.496,100.483 C47.496,101.418 46.737,102.177 45.801,102.177 L28.118,102.177 C27.182,102.177 26.423,101.418 26.423,100.483 L26.423,75.577 C26.423,74.641 27.182,73.882 28.118,73.882 L41.572,73.882" id="Fill-424" fill="#FFFFFF"></path>
<polyline id="Fill-425" fill="#E7F7FF" points="41.572 79.805 41.572 73.882 47.496 79.805 41.572 79.805"></polyline>
<path d="M47.495,80.556 L42.164,80.556 C41.424,80.556 40.822,79.953 40.822,79.213 L40.822,73.882 L42.322,73.882 L42.322,79.056 L47.495,79.056 L47.495,80.556" id="Fill-426" fill="#0073AA"></path>
<polygon id="Fill-427" fill="#0073AA" points="29.582 83.445 44.651 83.445 44.651 81.945 29.582 81.945"></polygon>
<polygon id="Fill-428" fill="#0073AA" points="29.582 86.805 44.651 86.805 44.651 85.305 29.582 85.305"></polygon>
<polygon id="Fill-429" fill="#0073AA" points="29.582 90.165 44.651 90.165 44.651 88.665 29.582 88.665"></polygon>
<polygon id="Fill-430" fill="#0073AA" points="29.582 93.525 44.651 93.525 44.651 92.025 29.582 92.025"></polygon>
<path d="M45.801,102.927 L42.779,102.927 L42.779,101.427 L45.801,101.427 C46.321,101.427 46.745,101.003 46.745,100.482 L46.745,80.116 L41.262,74.632 L28.118,74.632 C27.598,74.632 27.174,75.056 27.174,75.576 L27.174,84.942 L25.674,84.942 L25.674,75.576 C25.674,74.229 26.77,73.132 28.118,73.132 L41.883,73.132 L48.245,79.495 L48.245,100.482 C48.245,101.83 47.148,102.927 45.801,102.927" id="Fill-431" fill="#0073AA"></path>
<path d="M42.943,103.676 L14.569,103.676 C13.889,103.676 13.337,103.124 13.337,102.444 L13.337,84.829 C13.337,84.26 13.798,83.8 14.366,83.8 L19.216,83.8 C19.569,83.8 19.898,83.982 20.086,84.281 L21.338,86.27 L39.383,86.27 C40.034,86.27 40.562,86.798 40.562,87.449 L40.562,101.296 C40.562,102.407 41.358,103.676 42.943,103.676" id="Fill-432" fill="#FFFFFF"></path>
<path d="M40.562,97.998 L13.337,97.998 L13.337,102.081085 C13.337,102.961395 13.889,103.676 14.57,103.676 L42.943,103.676 C41.358,103.676 40.562,102.033186 40.562,100.59362 L40.562,97.998 Z" id="Fill-433" fill="#E7F7FF"></path>
<path d="M42.942,104.426 C40.909,104.426 39.813,102.814 39.813,101.296 L39.813,87.449 C39.813,87.212 39.62,87.02 39.384,87.02 L20.924,87.02 L19.452,84.68 C19.401,84.6 19.311,84.55 19.216,84.55 L14.366,84.55 C14.212,84.55 14.087,84.675 14.087,84.829 L14.087,103.571 L12.587,103.571 L12.587,84.829 C12.587,83.848 13.385,83.05 14.366,83.05 L19.216,83.05 C19.83,83.05 20.394,83.36 20.722,83.88 L21.752,85.52 L39.384,85.52 C40.447,85.52 41.313,86.385 41.313,87.449 L41.313,101.296 C41.313,101.951 41.746,102.926 42.942,102.926 L42.942,104.426" id="Fill-434" fill="#0073AA"></path>
<polygon id="Fill-435" fill="#0073AA" points="5.332 104.426 164.92275 104.426 164.92275 102.926 5.332 102.926"></polygon>
<polygon id="Fill-436" fill="#0073AA" points="0.77 104.426 3.509 104.426 3.509 102.926 0.77 102.926"></polygon>
</g>
</g>
</g>
</symbol>
<symbol id="icon-done" width="177px" height="107px" viewBox="0 0 177 107" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M62.855,71.007 L62.759,71.007 C62.336,69.55 60.994,68.484 59.401,68.484 C58.705,68.484 58.059,68.689 57.514,69.039 C57.16,66.744 55.182,64.984 52.788,64.984 C50.638,64.984 48.82,66.4 48.215,68.35 C47.942,68.277 47.656,68.234 47.36,68.234 C45.72,68.234 44.364,69.436 44.116,71.007 L43.58,71.007 C42.23,71.007 41.125,72.112 41.125,73.462 L41.125,73.643 C41.125,74.993 42.23,76.098 43.58,76.098 L62.855,76.098 C64.205,76.098 65.31,74.993 65.31,73.643 L65.31,73.462 C65.31,72.112 64.205,71.007 62.855,71.007" id="Fill-447" fill="#FFFFFF"></path>
<path d="M52.734,76.848 L43.552,76.848 C41.603,76.848 40.017,75.262 40.017,73.313 L40.017,73.108 C40.017,71.159 41.603,69.573 43.552,69.573 L43.565,69.573 C44.185,67.566 46.144,66.208 48.328,66.456 C48.409,66.268 48.501,66.083 48.602,65.904 C48.789,65.567 49.014,65.243 49.268,64.941 C50.447,63.543 52.171,62.741 53.997,62.741 C57.065,62.741 59.632,64.943 60.1,67.977 L58.617,68.205 C58.263,65.908 56.32,64.241 53.997,64.241 C52.614,64.241 51.309,64.848 50.415,65.907 C50.223,66.136 50.053,66.381 49.911,66.636 C49.75,66.923 49.621,67.223 49.526,67.531 L49.314,68.222 L48.614,68.034 C46.835,67.552 45.17,68.734 44.9,70.44 L44.8,71.073 L43.552,71.073 C42.43,71.073 41.517,71.986 41.517,73.108 L41.517,73.313 C41.517,74.435 42.43,75.348 43.552,75.348 L52.734,75.348 L52.734,76.848" id="Fill-449" fill="#0073AA"></path>
<path d="M50.593,69.095 L49.093,69.095 C49.093,68.442 48.561,67.912 47.909,67.912 L47.909,66.412 C49.389,66.412 50.593,67.615 50.593,69.095" id="Fill-450" fill="#0073AA"></path>
<path d="M118.93,55.565 C118.058,54.222 116.583,53.42 114.986,53.42 C114.142,53.42 113.313,53.647 112.59,54.076 L111.824,52.786 C112.779,52.22 113.872,51.92 114.986,51.92 C117.094,51.92 119.038,52.978 120.188,54.749 L118.93,55.565" id="Fill-453" fill="#0073AA"></path>
<path d="M129.06,66.089 L116.041,66.089 L116.041,64.589 L129.06,64.589 C130.497,64.589 131.667,63.42 131.667,61.982 L131.667,61.735 C131.667,60.297 130.497,59.128 129.06,59.128 L128.365,59.128 L128.208,58.587 C127.712,56.874 126.119,55.679 124.337,55.679 C123.566,55.679 122.814,55.9 122.162,56.319 L121.191,56.941 L121.016,55.802 C120.577,52.958 118.172,50.894 115.295,50.894 C113.582,50.894 111.966,51.646 110.861,52.957 C110.624,53.238 110.414,53.542 110.235,53.861 C110.037,54.214 109.877,54.587 109.76,54.968 L109.546,55.658 L108.848,55.47 C108.52,55.382 108.193,55.337 107.875,55.337 L107.875,53.837 C108.102,53.837 108.33,53.853 108.559,53.884 C108.666,53.626 108.789,53.373 108.927,53.128 C109.151,52.727 109.416,52.344 109.715,51.99 C111.104,50.34 113.139,49.394 115.295,49.394 C118.572,49.394 121.364,51.521 122.274,54.579 C122.928,54.315 123.627,54.179 124.337,54.179 C126.598,54.179 128.635,55.581 129.469,57.648 C131.542,57.854 133.167,59.608 133.167,61.735 L133.167,61.982 C133.167,64.247 131.324,66.089 129.06,66.089" id="Fill-454" fill="#0073AA"></path>
<path d="M120.512,17.724 L116.237,17.724 C115.823,17.724 115.487,17.388 115.487,16.974 C115.487,16.56 115.823,16.224 116.237,16.224 L120.512,16.224 C120.926,16.224 121.262,16.56 121.262,16.974 C121.262,17.388 120.926,17.724 120.512,17.724" id="Fill-456" fill="#0073AA"></path>
<path d="M118.375,19.861 C117.961,19.861 117.625,19.525 117.625,19.111 L117.625,14.837 C117.625,14.423 117.961,14.087 118.375,14.087 C118.789,14.087 119.125,14.423 119.125,14.837 L119.125,19.111 C119.125,19.525 118.789,19.861 118.375,19.861" id="Fill-457" fill="#0073AA"></path>
<path d="M43.262,17.724 L38.987,17.724 C38.573,17.724 38.237,17.388 38.237,16.974 C38.237,16.56 38.573,16.224 38.987,16.224 L43.262,16.224 C43.676,16.224 44.012,16.56 44.012,16.974 C44.012,17.388 43.676,17.724 43.262,17.724" id="Fill-458" fill="#0073AA"></path>
<path d="M41.125,19.861 C40.711,19.861 40.375,19.525 40.375,19.111 L40.375,14.837 C40.375,14.423 40.711,14.087 41.125,14.087 C41.539,14.087 41.875,14.423 41.875,14.837 L41.875,19.111 C41.875,19.525 41.539,19.861 41.125,19.861" id="Fill-459" fill="#0073AA"></path>
<path d="M167.776,59.179 C167.584,59.179 167.393,59.106 167.246,58.96 C166.953,58.667 166.953,58.192 167.246,57.899 L170.269,54.877 C170.561,54.583 171.036,54.583 171.329,54.877 C171.622,55.169 171.622,55.644 171.329,55.937 L168.307,58.96 C168.16,59.106 167.969,59.179 167.776,59.179" id="Fill-460" fill="#0073AA"></path>
<path d="M170.799,59.179 C170.606,59.179 170.415,59.106 170.269,58.96 L167.246,55.937 C166.953,55.644 166.953,55.169 167.246,54.877 C167.539,54.583 168.014,54.583 168.307,54.877 L171.329,57.899 C171.622,58.192 171.622,58.667 171.329,58.96 C171.183,59.106 170.991,59.179 170.799,59.179" id="Fill-461" fill="#0073AA"></path>
<polygon id="Fill-466" fill="#0073AA" points="52.286 13.45 93.083 13.45 93.083 11.95 52.286 11.95"></polygon>
<polygon id="Fill-467" fill="#0073AA" points="9.023 59.856 34.619 59.856 34.619 58.356 9.023 58.356"></polygon>
<polygon id="Fill-468" fill="#0073AA" points="142.007 67.75 161.109 67.75 161.109 66.25 142.007 66.25"></polygon>
<polygon id="Fill-469" fill="#0073AA" points="163.403 67.75 166.009 67.75 166.009 66.25 163.403 66.25"></polygon>
<path d="M93.102,28.199 C93.102,28.939 92.502,29.538 91.763,29.538 C91.024,29.538 90.425,28.939 90.425,28.199 C90.425,27.46 91.024,26.861 91.763,26.861 C92.502,26.861 93.102,27.46 93.102,28.199" id="Fill-471" fill="#0073AA"></path>
<path d="M161.822,49.274 C161.822,50.147 161.114,50.855 160.241,50.855 C159.367,50.855 158.659,50.147 158.659,49.274 C158.659,48.401 159.367,47.693 160.241,47.693 C161.114,47.693 161.822,48.401 161.822,49.274" id="Fill-472" fill="#0073AA"></path>
<path d="M141.313,34.272 L147.564,34.272 L147.586,33.937 C147.722,31.815 150.68,28.247 152.484,26.167 C154.733,23.575 156.095,20.192 156.095,16.49 C156.095,8.326 149.477,1.708 141.313,1.708 L141.313,34.272" id="Fill-474" fill="#E7F7FF"></path>
<path d="M141.309,34.272 L135.058,34.272 L135.037,33.937 C134.9,31.815 131.941,28.247 130.137,26.167 C127.888,23.575 126.526,20.192 126.526,16.49 C126.526,8.326 133.144,1.708 141.308,1.708 L141.309,34.272" id="Fill-475" fill="#FFFFFF"></path>
<path d="M141.211,34.272 L144.772,34.272 L144.784,33.937 C144.86,31.815 146.513,28.247 147.541,26.167 C148.822,23.575 149.598,20.192 149.598,16.49 C149.598,8.326 145.827,1.708 141.176,1.708 L141.211,34.272" id="Fill-476" fill="#FFFFFF"></path>
<path d="M141.208,34.272 L137.647,34.272 L137.634,33.937 C137.554,31.815 135.835,28.247 134.807,26.167 C133.526,23.575 132.75,20.192 132.75,16.49 C132.75,8.326 136.521,1.708 141.173,1.708 L141.208,34.272" id="Fill-477" fill="#E7F7FF"></path>
<path d="M141.96,33.522 L144.062,33.522 C144.235,31.818 145.157,29.296 146.869,25.835 C148.146,23.251 148.849,19.933 148.849,16.49 C148.849,9.346 145.749,3.243 141.927,2.528 L141.96,33.522 Z M145.497,35.022 L140.462,35.022 L140.425,0.957 L141.176,0.957 C146.319,0.957 150.349,7.78 150.349,16.49 C150.349,20.158 149.59,23.713 148.214,26.5 C146.064,30.848 145.569,32.959 145.534,33.964 L145.497,35.022 L145.497,35.022 Z" id="Fill-478" fill="#0073AA"></path>
<path d="M138.354,33.522 L140.457,33.522 L140.424,2.524 C136.543,3.215 133.5,9.215 133.5,16.49 C133.5,19.935 134.203,23.253 135.479,25.835 C136.788,28.483 138.133,31.436 138.354,33.522 Z M141.959,35.022 L136.925,35.022 L136.885,33.966 C136.807,31.907 134.935,28.118 134.135,26.5 C132.759,23.715 132,20.161 132,16.49 C132,7.78 136.029,0.957 141.173,0.957 L141.922,0.957 L141.959,35.022 L141.959,35.022 Z" id="Fill-479" fill="#0073AA"></path>
<path d="M135.731,33.522 L146.881,33.522 C147.175,31.771 148.781,29.27 151.891,25.706 C154.118,23.154 155.345,19.881 155.345,16.49 C155.345,12.573 153.771,8.939 150.914,6.257 C148.06,3.578 144.325,2.24 140.399,2.487 C133.477,2.925 127.855,8.467 127.324,15.378 C127.031,19.185 128.244,22.855 130.74,25.712 C133.7,29.1 135.375,31.723 135.731,33.522 Z M147.354,35.022 L135.273,35.022 C134.758,35.022 134.33,34.618 134.3,34.103 C134.21,32.708 132.501,30.007 129.611,26.699 C126.848,23.536 125.504,19.475 125.828,15.263 C126.417,7.611 132.641,1.475 140.306,0.99 C144.632,0.714 148.781,2.197 151.941,5.163 C155.058,8.089 156.845,12.217 156.845,16.49 C156.845,20.243 155.487,23.867 153.021,26.693 C149.258,31.005 148.396,33.039 148.334,33.986 L148.326,34.107 C148.293,34.621 147.865,35.022 147.354,35.022 L147.354,35.022 Z" id="Fill-480" fill="#0073AA"></path>
<path d="M142.856,53.914 L140.071,53.914 C137.876,53.914 136.08,52.118 136.08,49.923 L136.08,44.851 L146.847,44.851 L146.847,49.923 C146.847,52.118 145.051,53.914 142.856,53.914" id="Fill-481" fill="#8AC4E6"></path>
<path d="M137.51,49.923 L137.51,44.851 L136.08,44.851 L136.08,49.923 C136.08,52.118 137.876,53.914 140.071,53.914 L141.501,53.914 C139.306,53.914 137.51,52.118 137.51,49.923" id="Fill-482" fill="#E7F7FF"></path>
<path d="M144.395,44.851 L144.395,49.923 C144.395,52.118 142.599,53.914 140.404,53.914 L142.856,53.914 C145.051,53.914 146.847,52.118 146.847,49.923 L146.847,44.851 L144.395,44.851" id="Fill-483" fill="#E7F7FF"></path>
<polyline id="Fill-484" fill="#468EBF" points="137.51 47.072 137.51 44.851 144.395 44.851 144.395 46.106 137.51 47.072"></polyline>
<path d="M136.829,45.601 L136.829,49.923 C136.829,51.71 138.283,53.164 140.07,53.164 L142.856,53.164 C144.644,53.164 146.098,51.71 146.098,49.923 L146.098,45.601 L136.829,45.601 Z M142.856,54.664 L140.07,54.664 C137.456,54.664 135.329,52.537 135.329,49.923 L135.329,44.101 L147.598,44.101 L147.598,49.923 C147.598,52.537 145.471,54.664 142.856,54.664 L142.856,54.664 Z" id="Fill-487" fill="#0073AA"></path>
<path d="M147.265,44.788 L135.662,44.788 C135.113,44.788 134.664,44.339 134.664,43.79 L134.664,43.789 C134.664,43.24 135.113,42.791 135.662,42.791 L147.265,42.791 C147.814,42.791 148.263,43.24 148.263,43.789 L148.263,43.79 C148.263,44.339 147.814,44.788 147.265,44.788" id="Fill-488" fill="#62A6CF"></path>
<path d="M135.662,43.541 C135.527,43.541 135.414,43.655 135.414,43.79 C135.414,43.924 135.527,44.038 135.662,44.038 L147.265,44.038 C147.399,44.038 147.514,43.924 147.514,43.79 C147.514,43.655 147.399,43.541 147.265,43.541 L135.662,43.541 Z M147.265,45.538 L135.662,45.538 C134.698,45.538 133.914,44.754 133.914,43.79 C133.914,42.825 134.698,42.041 135.662,42.041 L147.265,42.041 C148.229,42.041 149.014,42.825 149.014,43.79 C149.014,44.754 148.229,45.538 147.265,45.538 L147.265,45.538 Z" id="Fill-489" fill="#0073AA"></path>
<polyline id="Fill-492" fill="#0073AA" points="137.017 43.04 134.32 34.334 135.754 33.891 138.45 42.596 137.017 43.04"></polyline>
<polyline id="Fill-493" fill="#0073AA" points="145.721 43.024 144.279 42.611 146.729 34.065 148.17 34.479 145.721 43.024"></polyline>
<path d="M104.043,65.339 L102.706,65.339 C100.86,65.339 99.349,63.829 99.349,61.982 C99.349,63.829 100.86,65.339 102.706,65.339 L104.043,65.339" id="Fill-494" fill="#96BCD2"></path>
<path d="M117.288,66.089 L116.041,66.089 L116.041,64.589 L116.179,64.589 L117.288,66.089 M108.372,55.373 C108.205,55.349 108.039,55.337 107.875,55.337 L107.875,53.837 L107.958,53.843 L107.958,55.318 C108.101,55.318 108.24,55.337 108.372,55.373" id="Fill-497" fill="#2871AB"></path>
<path d="M66.57,62.586 L97.489,105.676 L34.296,105.676 L66.149,62.583 C66.254,62.441 66.467,62.443 66.57,62.586" id="Fill-499" fill="#E7F7FF"></path>
<path d="M33.692,105.23 L65.546,62.137 C65.735,61.881 66.039,61.727 66.358,61.727 C66.685,61.729 66.989,61.886 67.178,62.147 L77.6536458,76.6425781 L76.6692708,78.1077474 L66.35,63.574 L34.899,106.122 L33.692,105.23 Z" id="Fill-500" fill="#0073AA"></path>
<polyline id="Fill-501" fill="#96BCD2" points="85.855 89.463 75.321 74.782 85.855 89.463"></polyline>
<polygon id="Fill-502" fill="#8AC4E6" points="52.948 105.181 75.134847 75.8929036 97.134 105.181"></polygon>
<polyline id="Fill-504" fill="#ADDDF8" points="143.251 105.181 56.448 105.181 99.205 45.594 143.251 105.181"></polyline>
<polyline id="Fill-505" fill="#FFFFFF" points="109.61 59.649 107.731 57.282 107.841 57.133 99.193 45.594 91.22 57.133 90.913 57.133 88.916 59.649 92.121 64.116 94.206 61.295 99.407 68.543 104.542 61.596 106.405 64.116 109.61 59.649"></polyline>
<polyline id="Fill-506" fill="#0073AA" points="142.641188 106.122 98.9618386 46.8885729 56.5677088 106.112927 55.3418229 105.231871 98.9437369 44.32 143.854 105.222798 142.641188 106.122"></polyline>
<polygon id="Fill-507" fill="#0073AA" points="98.455 47.073 99.955 47.073 99.955 32.087 98.455 32.087"></polygon>
<polyline id="Fill-508" fill="#8AC4E6" points="115.521 45.057 105.174 45.057 105.174 37.006 115.521 37.006 113.909 41.031 115.521 45.057"></polyline>
<path d="M105.924,44.307 L114.412,44.307 L113.102,41.031 L114.412,37.756 L105.924,37.756 L105.924,44.307 Z M116.629,45.807 L104.424,45.807 L104.424,36.256 L116.629,36.256 L114.717,41.031 L116.629,45.807 L116.629,45.807 Z" id="Fill-509" fill="#0073AA"></path>
<polygon id="Fill-510" fill="#ADDDF8" points="99.263 42.37 109.61 42.37 109.61 34.319 99.263 34.319"></polygon>
<path d="M100.013,41.62 L108.86,41.62 L108.86,35.069 L100.013,35.069 L100.013,41.62 Z M98.513,43.12 L110.36,43.12 L110.36,33.569 L98.513,33.569 L98.513,43.12 Z" id="Fill-511" fill="#0073AA"></path>
<path d="M21.023,89.476 C12.061,87.325 13.217,80.28 13.229,80.209 L13.341,79.591 L19.459,79.591 L19.459,81.091 L14.659,81.091 C14.643,82.622 15.225,86.542 21.373,88.017 L21.023,89.476" id="Fill-512" fill="#0073AA"></path>
<path d="M36.928,89.476 L36.578,88.017 C42.766,86.532 43.325,82.62 43.297,81.091 L38.491,81.091 L38.491,79.591 L44.611,79.591 L44.722,80.209 C44.734,80.28 45.89,87.325 36.928,89.476" id="Fill-513" fill="#0073AA"></path>
<path d="M38.565,81.6647988 C38.565,80.3060295 38.334,79.0116845 37.931,77.813 L19.997,77.813 C19.594,79.0116845 19.363,80.3060295 19.363,81.6647988 C19.363,87.2404387 23.08,92.8209593 27.864,93.5784342 L27.864,101.038 L30.064,101.038 L30.064,93.5784342 C34.848,92.8209593 38.565,87.2404387 38.565,81.6647988" id="Fill-514" fill="#E7F7FF"></path>
<path d="M28.39,93.958 C23.949,92.768 22.961,87.189 22.961,81.759 C22.961,80.367 23.191,79.041 23.594,77.813 L21.436,77.813 C21.033,79.041 20.802,80.367 20.802,81.759 C20.802,87.471 23.08,93.188 27.864,93.964 C27.864,93.964 28.159,94.02 28.39,93.958" id="Fill-515" fill="#FFFFFF"></path>
<polygon id="Fill-516" fill="#0073AA" points="24.596 78.563 26.353 78.563 26.353 77.063 24.596 77.063"></polygon>
<path d="M30.815,100.856 L27.114,100.856 L27.114,93.5373925 C22.372,92.4292537 18.613,87.1764125 18.613,81.480504 C18.613,80.1465984 18.839,78.8239811 19.284,77.5483986 L19.454,77.063 L22.971,77.063 L22.971,78.4740426 L20.553,78.4740426 C20.261,79.4551875 20.113,80.4645533 20.113,81.480504 C20.113,86.6750221 23.718,91.6146118 27.984,92.2655728 L28.614,92.3615237 L28.614,99.4449574 L29.315,99.4449574 L29.315,92.3615237 L29.944,92.2655728 C34.21,91.6146118 37.815,86.6750221 37.815,81.480504 C37.815,80.465494 37.667,79.4561282 37.376,78.4740426 L27.461,78.4740426 L27.461,77.063 L38.476,77.063 L38.645,77.5483986 C39.089,78.8249218 39.315,80.1484798 39.315,81.480504 C39.315,87.1764125 35.556,92.4292537 30.815,93.5373925 L30.815,100.856" id="Fill-517" fill="#0073AA"></path>
<polygon id="Fill-518" fill="#0073AA" points="26.758 96.12 31.287 96.12 31.287 94.62 26.758 94.62"></polygon>
<polygon id="Fill-519" fill="#0073AA" points="26.758 98.803 31.287 98.803 31.287 97.303 26.758 97.303"></polygon>
<path d="M24.061,105.007 L33.771,105.007 L33.771,101.861 C33.771,101.674 33.618,101.522 33.432,101.522 L24.399,101.522 C24.213,101.522 24.061,101.674 24.061,101.861 L24.061,105.007 Z M35.271,106.507 L22.561,106.507 L22.561,101.861 C22.561,100.847 23.386,100.022 24.399,100.022 L33.432,100.022 C34.445,100.022 35.271,100.847 35.271,101.861 L35.271,106.507 L35.271,106.507 Z" id="Fill-521" fill="#0073AA"></path>
<polygon id="Fill-522" fill="#FFFFFF" points="25.909 104.025 31.923 104.025 31.923 101.625 25.909 101.625"></polygon>
<polygon id="Fill-523" fill="#0073AA" points="27.461 103.575 30.289 103.575 30.289 102.075 27.461 102.075"></polygon>
<polygon id="Fill-524" fill="#0073AA" points="21.198 106.507 36.418 106.507 36.418 105.007 21.198 105.007"></polygon>
<polygon id="Fill-525" fill="#0073AA" points="0.294 106.426 3.033 106.426 3.033 104.926 0.294 104.926"></polygon>
<polygon id="Fill-526" fill="#0073AA" points="170.241 106.426 176.29 106.426 176.29 104.926 170.241 104.926"></polygon>
<polygon id="Fill-527" fill="#0073AA" points="4.856 106.426 168.679 106.426 168.679 104.926 4.856 104.926"></polygon>
<path d="M80.646,41.035 C80.646,45.432 77.082,48.996 72.685,48.996 C68.288,48.996 64.723,45.432 64.723,41.035 C64.723,36.638 68.288,33.073 72.685,33.073 C77.082,33.073 80.646,36.638 80.646,41.035" id="Fill-528" fill="#E7F7FF"></path>
<g class="spinning">
<path d="M72.685,33.823 C68.708,33.823 65.473,37.058 65.473,41.035 C65.473,45.011 68.708,48.246 72.685,48.246 C76.661,48.246 79.896,45.011 79.896,41.035 C79.896,37.058 76.661,33.823 72.685,33.823 Z M72.685,49.746 C67.881,49.746 63.973,45.838 63.973,41.035 C63.973,36.231 67.881,32.323 72.685,32.323 C77.488,32.323 81.396,36.231 81.396,41.035 C81.396,45.838 77.488,49.746 72.685,49.746 L72.685,49.746 Z" id="Fill-529" fill="#0073AA"></path>
<polygon id="Fill-530" fill="#0073AA" points="71.935 31.075 73.435 31.075 73.435 27.179 71.935 27.179"></polygon>
<polyline id="Fill-531" fill="#0073AA" points="78.357 32.814 77.063 32.057 78.045 30.375 79.34 31.132 78.357 32.814"></polyline>
<polyline id="Fill-532" fill="#0073AA" points="81.73 36.799 80.994 35.493 84.388 33.581 85.124 34.887 81.73 36.799"></polyline>
<polyline id="Fill-533" fill="#0073AA" points="84.567 42.003 82.619 41.942 82.666 40.443 84.614 40.504 84.567 42.003"></polyline>
<polyline id="Fill-534" fill="#0073AA" points="84.146 48.855 80.815 46.837 81.592 45.554 84.924 47.572 84.146 48.855"></polyline>
<polyline id="Fill-535" fill="#0073AA" points="77.517 51.807 76.751 50.094 78.12 49.482 78.886 51.194 77.517 51.807"></polyline>
<polyline id="Fill-536" fill="#0073AA" points="72.996 54.907 71.496 54.859 71.62 50.965 73.12 51.013 72.996 54.907"></polyline>
<polyline id="Fill-537" fill="#0073AA" points="66.99 51.519 65.719 50.722 66.754 49.072 68.025 49.869 66.99 51.519"></polyline>
<polyline id="Fill-538" fill="#0073AA" points="60.752 48.115 60.057 46.786 63.509 44.982 64.204 46.311 60.752 48.115"></polyline>
<polygon id="Fill-539" fill="#0073AA" points="60.79 41.313 62.736 41.313 62.736 39.813 60.79 39.813"></polygon>
<polyline id="Fill-540" fill="#0073AA" points="63.926 36.236 60.658 34.113 61.475 32.855 64.742 34.978 63.926 36.236"></polyline>
<polyline id="Fill-541" fill="#0073AA" points="67.543 32.472 66.72 30.835 68.06 30.161 68.883 31.798 67.543 32.472"></polyline>
<path d="M156.536,105.296 L157.412,100.276 C157.587,99.271 156.814,98.351 155.794,98.351 L138.854,98.351 C137.834,98.351 137.061,99.271 137.236,100.276 L138.112,105.296 L156.536,105.296" id="Fill-542" fill="#FFFFFF"></path>
</g>
<path d="M157.274,105.425 L155.796,105.167 L156.694,100.023 C156.735,99.79 156.674,99.563 156.523,99.382 C156.37,99.201 156.156,99.102 155.92,99.102 L138.729,99.102 C138.492,99.102 138.278,99.201 138.126,99.382 C137.975,99.563 137.913,99.79 137.954,100.023 L138.852,105.167 L137.373,105.425 L136.476,100.281 C136.359,99.614 136.543,98.935 136.979,98.417 C137.413,97.898 138.052,97.602 138.729,97.602 L155.92,97.602 C156.597,97.602 157.234,97.898 157.67,98.417 C158.105,98.935 158.289,99.614 158.173,100.281 L157.274,105.425" id="Fill-543" fill="#0073AA"></path>
<polygon id="Fill-544" fill="#0073AA" points="146.314 98.834 148.06 98.834 148.06 93.06 146.314 93.06"></polygon>
<path d="M157.033,86.407 C157.033,86.407 157.34,90.568 154.964,92.945 C152.587,95.321 148.426,95.014 148.426,95.014 C148.426,95.014 148.118,90.852 150.495,88.475 C152.871,86.099 157.033,86.407 157.033,86.407" id="Fill-550" fill="#E7F7FF"></path>
<path d="M156.292,87.148 C155.09,87.181 152.584,87.446 151.024,89.006 C149.467,90.564 149.2,93.069 149.168,94.272 C150.37,94.239 152.875,93.973 154.434,92.414 C155.999,90.849 156.262,88.349 156.292,87.148 Z M148.918,95.778 C148.604,95.778 148.406,95.764 148.37,95.762 L147.726,95.714 L147.678,95.069 C147.664,94.885 147.375,90.536 149.964,87.945 C152.555,85.355 156.905,85.646 157.088,85.659 L157.732,85.707 L157.78,86.351 C157.794,86.535 158.084,90.885 155.494,93.475 C153.414,95.555 150.198,95.778 148.918,95.778 L148.918,95.778 Z" id="Fill-551" fill="#0073AA"></path>
<path d="M146.488,95.014 C146.488,95.014 146.796,90.852 144.419,88.475 C142.042,86.099 137.881,86.407 137.881,86.407 C137.881,86.407 137.573,90.568 139.95,92.945 C142.327,95.321 146.488,95.014 146.488,95.014" id="Fill-552" fill="#E7F7FF"></path>
<path d="M138.623,87.147 C138.655,88.349 138.921,90.855 140.48,92.414 C142.039,93.973 144.544,94.239 145.746,94.272 C145.714,93.069 145.447,90.564 143.889,89.006 C142.323,87.44 139.823,87.177 138.623,87.147 Z M145.996,95.778 C144.716,95.778 141.5,95.555 139.42,93.475 C136.83,90.885 137.119,86.535 137.133,86.352 L137.181,85.707 L137.825,85.659 C138.009,85.646 142.359,85.354 144.949,87.945 C147.539,90.535 147.25,94.885 147.236,95.069 L147.189,95.714 L146.544,95.762 C146.508,95.764 146.31,95.778 145.996,95.778 L145.996,95.778 Z" id="Fill-553" fill="#0073AA"></path>
</g>
</symbol>
<symbol id="icon-plugins" width="179px" height="95px" viewBox="0 0 179 95" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M156.632,94.094 L156.632,20.501 C156.632,18.283 155.034,16.485 153.064,16.485 L100.33,16.485 C98.36,16.485 96.762,18.283 96.762,20.501 L96.762,94.094 L156.632,94.094" id="Fill-183" fill="#FFFFFF"></path>
<path d="M96.762,19.486 C96.762,17.828 98.105,16.485 99.763,16.485 L153.631,16.485 C155.288,16.485 156.632,17.828 156.632,19.486 L156.632,25.734 L96.762,25.734 L96.762,19.486 Z" id="Fill-184" fill="#FFFFFF"></path>
<polygon id="Fill-185" fill="#0073AA" points="97.073 26.647 156.632 26.647 156.632 25.147 97.073 25.147"></polygon>
<polygon id="Fill-186" fill="#0073AA" points="103.638 22.368 105.036 22.368 105.036 20.868 103.638 20.868"></polygon>
<polygon id="Fill-187" fill="#0073AA" points="107.687 22.368 109.086 22.368 109.086 20.868 107.687 20.868"></polygon>
<polygon id="Fill-188" fill="#0073AA" points="111.738 22.368 113.136 22.368 113.136 20.868 111.738 20.868"></polygon>
<polygon id="Fill-189" fill="#E7F7FF" points="104.782 50.091 148.611 50.091 148.611 32.803 104.782 32.803"></polygon>
<polygon id="Fill-190" fill="#9CC9E1" points="104.787 78.355 118.113 78.355 118.113 61.068 104.787 61.068"></polygon>
<polygon id="Fill-191" fill="#D1E8F0" points="120.038 78.355 133.364 78.355 133.364 61.068 120.038 61.068"></polygon>
<path d="M157.382,48.843 L155.882,48.843 L155.882,20.501 C155.882,18.7 154.618,17.235 153.064,17.235 L100.33,17.235 C98.776,17.235 97.512,18.7 97.512,20.501 L97.512,34.594 L96.012,34.594 L96.012,20.501 C96.012,17.873 97.949,15.735 100.33,15.735 L153.064,15.735 C155.445,15.735 157.382,17.873 157.382,20.501 L157.382,48.843" id="Fill-194" fill="#0073AA"></path>
<polygon id="Fill-195" fill="#0073AA" points="155.882 94.094 157.382 94.094 157.382 68.271 155.882 68.271"></polygon>
<path d="M81.917,3.637 L77.643,3.637 C77.229,3.637 76.893,3.301 76.893,2.887 C76.893,2.473 77.229,2.137 77.643,2.137 L81.917,2.137 C82.331,2.137 82.667,2.473 82.667,2.887 C82.667,3.301 82.331,3.637 81.917,3.637" id="Fill-196" fill="#0073AA"></path>
<path d="M79.78,5.774 C79.366,5.774 79.03,5.439 79.03,5.024 L79.03,0.75 C79.03,0.336 79.366,0 79.78,0 C80.194,0 80.53,0.336 80.53,0.75 L80.53,5.024 C80.53,5.439 80.194,5.774 79.78,5.774" id="Fill-197" fill="#0073AA"></path>
<path d="M8.165,57.95 L3.891,57.95 C3.477,57.95 3.141,57.614 3.141,57.2 C3.141,56.786 3.477,56.45 3.891,56.45 L8.165,56.45 C8.579,56.45 8.915,56.786 8.915,57.2 C8.915,57.614 8.579,57.95 8.165,57.95" id="Fill-198" fill="#0073AA"></path>
<path d="M6.028,60.087 C5.614,60.087 5.278,59.751 5.278,59.337 L5.278,55.063 C5.278,54.648 5.614,54.313 6.028,54.313 C6.442,54.313 6.778,54.648 6.778,55.063 L6.778,59.337 C6.778,59.751 6.442,60.087 6.028,60.087" id="Fill-199" fill="#0073AA"></path>
<path d="M167.204,14.301 C167.013,14.301 166.821,14.228 166.674,14.081 C166.381,13.788 166.381,13.314 166.674,13.021 L169.696,9.999 C169.989,9.706 170.464,9.706 170.757,9.999 C171.05,10.292 171.05,10.767 170.757,11.06 L167.735,14.081 C167.588,14.228 167.396,14.301 167.204,14.301" id="Fill-200" fill="#0073AA"></path>
<path d="M170.226,14.301 C170.035,14.301 169.843,14.228 169.696,14.081 L166.674,11.06 C166.381,10.767 166.381,10.292 166.674,9.999 C166.967,9.706 167.442,9.706 167.735,9.999 L170.757,13.021 C171.05,13.314 171.05,13.788 170.757,14.081 C170.61,14.228 170.418,14.301 170.226,14.301" id="Fill-201" fill="#0073AA"></path>
<polygon id="Fill-204" fill="#0073AA" points="21.566 3.637 72.286 3.637 72.286 2.137 21.566 2.137"></polygon>
<polygon id="Fill-206" fill="#0073AA" points="0 40.018 25.157 40.018 25.157 38.518 0 38.518"></polygon>
<path d="M8.698,32.416 C8.698,33.16 8.095,33.763 7.351,33.763 C6.608,33.763 6.005,33.16 6.005,32.416 C6.005,31.672 6.608,31.069 7.351,31.069 C8.095,31.069 8.698,31.672 8.698,32.416" id="Fill-210" fill="#0073AA"></path>
<path d="M117.815,2.887 C117.815,3.631 117.212,4.234 116.468,4.234 C115.725,4.234 115.122,3.631 115.122,2.887 C115.122,2.143 115.725,1.54 116.468,1.54 C117.212,1.54 117.815,2.143 117.815,2.887" id="Fill-211" fill="#0073AA"></path>
<path d="M172.52,56.45 C172.52,57.194 171.917,57.797 171.173,57.797 C170.43,57.797 169.827,57.194 169.827,56.45 C169.827,55.707 170.43,55.104 171.173,55.104 C171.917,55.104 172.52,55.707 172.52,56.45" id="Fill-212" fill="#0073AA"></path>
<path d="M38.479,94.094 L34.696,94.094 C34.411,94.094 34.18,93.863 34.18,93.578 L34.18,39.087 L34.227,38.871 L36.119,34.773 C36.303,34.373 36.872,34.373 37.056,34.773 L38.947,38.871 L38.995,39.087 L38.995,93.578 C38.995,93.863 38.764,94.094 38.479,94.094" id="Fill-214" fill="#FFFFFF"></path>
<polygon id="Fill-215" fill="#ADDDF8" points="34.18 88.878 38.995 88.878 38.995 40.826 33.4855957 40.826"></polygon>
<path d="M38.245,94.094 L38.245,39.139 L36.588,35.548 L34.93,39.139 L34.93,58.3605957 L33.43,58.3605957 L33.43,38.809 L35.627,34.048 C35.802,33.669 36.17,33.434 36.588,33.434 C37.005,33.434 37.373,33.669 37.548,34.048 L39.745,38.809 L39.745,94.094 L38.245,94.094 Z" id="Fill-216" fill="#0073AA"></path>
<polygon id="Fill-217" fill="#0073AA" points="33.43 93.919 34.93 93.919 34.93 74.236 33.43 74.236"></polygon>
<polygon id="Fill-218" fill="#0073AA" points="34.014 41.575 38.995 41.575 38.995 40.075 34.014 40.075"></polygon>
<path d="M47.926,94.182 L47.926,22 L42.8954316,22 C42.5364316,22 42.2454316,22.325 42.2454316,22.728 L42.2454316,93.454 C42.2454316,93.856 42.5364316,94.182 42.8954316,94.182 L47.926,94.182 Z" id="Fill-220" fill="#E7F7FF"></path>
<path d="M43.451,93.302 L41.951,93.302 L41.951,22.703 C41.951,21.853 42.643,21.161 43.493,21.161 L53.255,21.161 C54.105,21.161 54.797,21.853 54.797,22.703 L54.797,34.302 L53.297,34.302 L53.297,22.703 L43.493,22.661 L43.451,93.302" id="Fill-221" fill="#0073AA"></path>
<polygon id="Fill-223" fill="#0073AA" points="42.701 81.363 47.926 81.363 47.926 79.863 42.701 79.863"></polygon>
<polygon id="Fill-224" fill="#0073AA" points="42.701 73.772 47.926 73.772 47.926 72.272 42.701 72.272"></polygon>
<polygon id="Fill-225" fill="#0073AA" points="42.701 66.182 47.926 66.182 47.926 64.682 42.701 64.682"></polygon>
<polygon id="Fill-226" fill="#0073AA" points="42.701 58.591 47.926 58.591 47.926 57.091 42.701 57.091"></polygon>
<polygon id="Fill-227" fill="#0073AA" points="42.701 51.001 47.926 51.001 47.926 49.501 42.701 49.501"></polygon>
<polygon id="Fill-228" fill="#0073AA" points="42.701 43.41 47.926 43.41 47.926 41.91 42.701 41.91"></polygon>
<polygon id="Fill-229" fill="#0073AA" points="42.701 35.819 47.926 35.819 47.926 34.319 42.701 34.319"></polygon>
<polygon id="Fill-230" fill="#0073AA" points="42.701 28.34 47.926 28.34 47.926 26.84 42.701 26.84"></polygon>
<path d="M123.044,91.052 L48.836,91.052 C48.286,91.052 47.836,90.602 47.836,90.052 L47.836,37.275 C47.836,36.725 48.286,36.275 48.836,36.275 L123.044,36.275 C123.594,36.275 124.044,36.725 124.044,37.275 L124.044,90.052 C124.044,90.602 123.594,91.052 123.044,91.052" id="Fill-231" fill="#8AC4E6"></path>
<polygon id="Fill-232" fill="#0073AA" points="102.77 37.025 108.033 37.025 108.033 35.525 102.77 35.525"></polygon>
<path d="M122.151,91.803 L49.73,91.803 C48.272,91.803 47.086,90.617 47.086,89.159 L47.086,38.169 C47.086,36.711 48.272,35.525 49.73,35.525 L101.397,35.525 L101.397,37.025 L49.73,37.025 C49.099,37.025 48.586,37.538 48.586,38.169 L48.586,89.159 C48.586,89.79 49.099,90.303 49.73,90.303 L122.151,90.303 C122.781,90.303 123.294,89.79 123.294,89.159 L124.794,89.159 C124.794,90.617 123.608,91.803 122.151,91.803" id="Fill-233" fill="#0073AA"></path>
<polygon id="Fill-234" fill="#0073AA" points="109.752 37.025 119.93 37.025 119.93 35.525 109.752 35.525"></polygon>
<polygon id="Fill-235" fill="#FFFFFF" points="51.537 85.036 119.93 85.036 119.93 40.585 51.537 40.585"></polygon>
<path d="M122.712,94.094 L48.328,94.094 C46.542,94.094 45.019,92.8 44.73,91.038 L44.2,87.806 L78.676,87.806 L79.995,89.7 L89.522,89.7 L90.948,87.806 L127.267,87.806 L126.806,90.617 C126.477,92.622 124.744,94.094 122.712,94.094" id="Fill-236" fill="#FFFFFF"></path>
<path d="M45.083,88.557 L45.47,90.917 C45.701,92.323 46.903,93.344 48.328,93.344 L122.712,93.344 C124.385,93.344 125.795,92.147 126.065,90.496 L126.384,88.557 L91.322,88.557 L89.895,90.45 L79.604,90.45 L78.285,88.557 L45.083,88.557 M122.712,94.844 L48.328,94.844 C46.164,94.844 44.34,93.294 43.99,91.159 L43.317,87.057 L79.068,87.057 L80.387,88.95 L89.147,88.95 L90.574,87.057 L128.149,87.057 L127.546,90.738 C127.156,93.117 125.123,94.844 122.712,94.844" id="Fill-237" fill="#0073AA"></path>
<polyline id="Fill-239" fill="#B4D7E7" points="128.149 87.057 124.044 87.057 124.044 78.355 124.044 87.056 128.149 87.056 128.149 87.057"></polyline>
<polyline id="Fill-241" fill="#0073AA" points="119.93 37.025 119.627 37.025 119.144 35.525 119.93 35.525 119.93 37.025"></polyline>
<polygon id="Fill-242" fill="#B4D7E7" points="119.821 85.036 119.93 85.036 119.93 45.915 119.821 45.915"></polygon>
<path d="M125.308,94.094 L119.821,94.094 L119.821,93.344 L122.712,93.344 C124.385,93.344 125.795,92.147 126.065,90.496 L126.384,88.557 L119.821,88.557 L119.821,87.057 L128.149,87.057 L127.546,90.738 C127.31,92.177 126.467,93.37 125.308,94.094" id="Fill-243" fill="#0073AA"></path>
<path d="M128.529,47.577 L128.569,47.583 L131.29,47.591 L135.421,35.076 L129.776,25.121 L128.894,25.121 L128.811,34.648 C129.013,34.738 129.735,35.046 129.899,35.209 C130.612,35.919 130.614,37.073 129.905,37.785 C129.196,38.496 128.041,38.499 127.329,37.788 C126.616,37.079 126.615,35.925 127.324,35.213 C127.478,35.06 128.022,34.709 128.208,34.623 L128.428,25.121 L127.56,25.121 L121.77,35.023 L125.808,47.568 L128.529,47.577" id="Fill-244" fill="#FFFFFF"></path>
<path d="M128.484,35.324 C128.323,35.411 127.943,35.66 127.848,35.749 C127.653,35.944 127.542,36.214 127.543,36.5 C127.543,36.786 127.655,37.055 127.857,37.257 C128.06,37.459 128.329,37.57 128.615,37.57 L128.617,37.57 C128.903,37.569 129.172,37.458 129.373,37.256 C129.789,36.837 129.788,36.157 129.369,35.74 C129.348,35.729 129.171,35.624 128.564,35.358 L128.484,35.324 Z M128.625,46.833 L130.748,46.84 L134.605,35.157 L129.633,26.388 L129.566,34.165 C130.047,34.39 130.282,34.532 130.428,34.677 C131.432,35.678 131.436,37.309 130.436,38.314 C129.951,38.801 129.306,39.068 128.619,39.07 L128.615,39.07 C127.929,39.07 127.285,38.804 126.799,38.319 C126.313,37.835 126.044,37.19 126.043,36.503 C126.042,35.816 126.308,35.17 126.792,34.684 C126.936,34.541 127.217,34.345 127.468,34.186 L127.647,26.458 L122.587,35.111 L126.354,46.82 L128.625,46.833 Z M131.831,48.344 L125.261,48.316 L120.954,34.934 L127.13,24.371 L130.213,24.371 L136.238,34.995 L131.831,48.344 L131.831,48.344 Z" id="Fill-245" fill="#0073AA"></path>
<polygon id="Fill-246" fill="#E7F7FF" points="122.571 94.094 134.621 94.094 134.621 46.665 122.571 46.665"></polygon>
<polygon id="Fill-247" fill="#8AC4E6" points="128.596 94.094 134.467 94.094 134.467 46.665 128.596 46.665"></polygon>
<polygon id="Fill-248" fill="#0073AA" points="127.846 34.623 129.346 34.623 129.346 25.121 127.846 25.121"></polygon>
<path d="M128.596,35.222 C127.836,35.222 127.219,35.84 127.219,36.599 C127.219,37.358 127.836,37.976 128.596,37.976 C129.355,37.976 129.972,37.358 129.972,36.599 C129.972,35.84 129.355,35.222 128.596,35.222 Z M128.596,39.476 C127.009,39.476 125.719,38.185 125.719,36.599 C125.719,35.013 127.009,33.722 128.596,33.722 C130.182,33.722 131.472,35.013 131.472,36.599 C131.472,38.185 130.182,39.476 128.596,39.476 L128.596,39.476 Z" id="Fill-249" fill="#0073AA"></path>
<polygon id="Fill-250" fill="#0073AA" points="133.87 94.094 135.37 94.094 135.37 79.333 133.87 79.333"></polygon>
<polyline id="Fill-251" fill="#0073AA" points="123.321 94.094 121.821 94.094 121.821 45.915 135.37 45.915 135.37 49.952 133.87 49.952 133.87 47.415 123.321 47.415 123.321 94.094"></polyline>
<path d="M147.043,49.952 L150.585,49.952 L161.415,52.531 C162.41,52.768 163.112,53.657 163.112,54.679 L163.112,55.356 L147.043,55.356 L147.043,49.952" id="Fill-252" fill="#E7F7FF"></path>
<path d="M147.043,67.248 L150.585,67.248 L161.415,64.668 C162.41,64.432 163.112,63.543 163.112,62.521 L163.112,61.843 L147.043,61.843 L147.043,67.248" id="Fill-253" fill="#E7F7FF"></path>
<path d="M147.793,54.607 L162.36,54.607 C162.328,53.96 161.877,53.412 161.241,53.261 L150.497,50.702 L147.793,50.702 L147.793,54.607 Z M163.862,56.107 L146.293,56.107 L146.293,49.202 L150.759,49.223 L161.589,51.802 C162.927,52.121 163.862,53.304 163.862,54.679 L163.862,56.107 L163.862,56.107 Z" id="Fill-254" fill="#0073AA"></path>
<path d="M147.793,66.498 L150.497,66.498 L161.242,63.939 C161.878,63.787 162.328,63.239 162.36,62.593 L147.793,62.593 L147.793,66.498 Z M150.673,67.998 L146.293,67.998 L146.293,61.093 L163.862,61.093 L163.862,62.521 C163.862,63.897 162.927,65.079 161.589,65.397 L150.673,67.998 L150.673,67.998 Z" id="Fill-255" fill="#0073AA"></path>
<path d="M138.74,60.795 L136.212,60.795 L128.485,62.636 C127.776,62.805 127.275,63.439 127.275,64.168 L127.275,64.651 L138.74,64.651 L138.74,60.795" id="Fill-256" fill="#ADDDF8"></path>
<path d="M138.74,55.514 L136.212,55.514 L128.485,53.674 C127.776,53.505 127.275,52.871 127.275,52.141 L127.275,51.658 L138.74,51.658 L138.74,55.514" id="Fill-257" fill="#ADDDF8"></path>
<path d="M128.069,63.901 L137.99,63.901 L137.99,61.545 L136.3,61.545 L128.659,63.365 C128.376,63.433 128.158,63.639 128.069,63.901 Z M139.49,65.401 L126.525,65.401 L126.525,64.168 C126.525,63.087 127.26,62.156 128.311,61.906 L136.212,60.045 L139.49,60.045 L139.49,65.401 L139.49,65.401 Z" id="Fill-258" fill="#0073AA"></path>
<path d="M136.3,54.764 L137.99,54.764 L137.99,52.408 L128.069,52.408 C128.158,52.671 128.376,52.877 128.659,52.944 L136.3,54.764 Z M139.49,56.264 L136.038,56.243 L128.311,54.403 C127.26,54.153 126.525,53.223 126.525,52.142 L126.525,50.908 L139.49,50.908 L139.49,56.264 L139.49,56.264 Z" id="Fill-259" fill="#0073AA"></path>
<polygon id="Fill-260" fill="#FFFFFF" points="138.188 94.094 147.043 94.094 147.043 49.952 138.188 49.952"></polygon>
<polyline id="Fill-261" fill="#0073AA" points="147.793 94.094 146.293 94.094 146.293 50.702 138.938 50.702 138.938 94.094 137.438 94.094 137.438 49.202 147.793 49.202 147.793 94.094"></polyline>
<polygon id="Fill-262" fill="#0073AA" points="138.188 55.509 142.201 55.509 142.201 54.009 138.188 54.009"></polygon>
<polygon id="Fill-263" fill="#0073AA" points="138.188 59.226 142.201 59.226 142.201 57.726 138.188 57.726"></polygon>
<polygon id="Fill-264" fill="#0073AA" points="138.188 62.941 142.201 62.941 142.201 61.441 138.188 61.441"></polygon>
<polygon id="Fill-265" fill="#0073AA" points="138.188 85.237 142.201 85.237 142.201 83.737 138.188 83.737"></polygon>
<polygon id="Fill-266" fill="#0073AA" points="138.188 88.953 142.201 88.953 142.201 87.453 138.188 87.453"></polygon>
<polygon id="Fill-267" fill="#0073AA" points="138.188 92.669 142.201 92.669 142.201 91.169 138.188 91.169"></polygon>
<polygon id="Fill-268" fill="#8AC4E6" points="136.16 82.149 147.558 82.149 147.558 67.346 136.16 67.346"></polygon>
<path d="M136.91,81.398 L146.808,81.398 L146.808,68.096 L136.91,68.096 L136.91,81.398 Z M135.41,82.898 L148.308,82.898 L148.308,66.596 L135.41,66.596 L135.41,82.898 Z" id="Fill-269" fill="#0073AA"></path>
<polygon id="Fill-270" fill="#D1E8F0" points="132.496 77.619 135.003 77.619 135.003 71.657 132.496 71.657"></polygon>
<path d="M133.246,76.869 L134.253,76.869 L134.253,72.406 L133.246,72.406 L133.246,76.869 Z M131.746,78.369 L135.753,78.369 L135.753,70.906 L131.746,70.906 L131.746,78.369 Z" id="Fill-271" fill="#0073AA"></path>
<path d="M160.608,57.2 C159.905,57.2 159.333,57.772 159.333,58.476 C159.333,59.179 159.905,59.75 160.608,59.75 C161.311,59.75 161.883,59.179 161.883,58.476 C161.883,57.772 161.311,57.2 160.608,57.2 Z M160.608,61.25 C159.077,61.25 157.833,60.006 157.833,58.476 C157.833,56.945 159.077,55.7 160.608,55.7 C162.138,55.7 163.383,56.945 163.383,58.476 C163.383,60.006 162.138,61.25 160.608,61.25 L160.608,61.25 Z" id="Fill-272" fill="#0073AA"></path>
<path d="M25.87,94.094 L24.269,94.094 C23.719,94.094 23.269,93.644 23.269,93.094 L23.269,89.579 C23.269,89.029 23.719,88.579 24.269,88.579 L25.87,88.579 C26.42,88.579 26.87,89.029 26.87,89.579 L26.87,93.094 C26.87,93.644 26.42,94.094 25.87,94.094" id="Fill-273" fill="#62A6CF"></path>
<path d="M24.269,89.329 C24.133,89.329 24.019,89.443 24.019,89.579 L24.019,93.094 C24.019,93.229 24.133,93.344 24.269,93.344 L25.87,93.344 C26.006,93.344 26.12,93.229 26.12,93.094 L26.12,89.579 C26.12,89.443 26.006,89.329 25.87,89.329 L24.269,89.329 Z M25.87,94.844 L24.269,94.844 C23.304,94.844 22.519,94.059 22.519,93.094 L22.519,89.579 C22.519,88.614 23.304,87.829 24.269,87.829 L25.87,87.829 C26.835,87.829 27.62,88.614 27.62,89.579 L27.62,93.094 C27.62,94.059 26.835,94.844 25.87,94.844 L25.87,94.844 Z" id="Fill-274" fill="#0073AA"></path>
<path d="M37.189,66.18 C37.189,59.535 31.802,54.147 25.157,54.147 C18.511,54.147 13.125,59.535 13.125,66.18 C13.125,70.638 15.556,74.522 19.159,76.6 L19.159,88.464 L20.865,91.696 L29.13,91.696 L31.154,88.464 L31.154,76.6 C34.758,74.522 37.189,70.638 37.189,66.18" id="Fill-275" fill="#FFFFFF"></path>
<polyline id="Fill-276" fill="#ADDDF8" points="19.159 79.913 19.159 89.173 20.865 91.696 29.13 91.696 31.154 89.173 31.154 79.913 19.159 79.913"></polyline>
<polyline id="Fill-277" fill="#62A6CF" points="31.154 88.464 29.13 91.696 20.865 91.696 19.159 88.464 31.154 88.464"></polyline>
<path d="M29.546,92.446 L20.412,92.446 L18.41,88.649 L18.41,77.025 C14.676,74.697 12.375,70.581 12.375,66.18 C12.375,63.169 13.442,60.245 15.379,57.946 L16.526,58.913 C14.816,60.941 13.875,63.522 13.875,66.18 C13.875,70.192 16.043,73.937 19.535,75.951 L19.91,76.167 L19.91,88.278 L21.317,90.946 L28.715,90.946 L30.404,88.248 L30.404,76.167 L30.779,75.951 C34.27,73.937 36.439,70.192 36.439,66.18 C36.439,59.959 31.378,54.897 25.157,54.897 C23.691,54.897 22.265,55.174 20.92,55.72 L20.356,54.329 C21.882,53.711 23.497,53.397 25.157,53.397 C32.205,53.397 37.939,59.132 37.939,66.18 C37.939,70.581 35.638,74.697 31.904,77.025 L31.904,88.68 L29.546,92.446" id="Fill-278" fill="#0073AA"></path>
<path d="M17.682,57.728 L16.617,56.673 C17.379,55.902 17.772,55.677 18.556,55.228 L18.784,55.098 L19.533,56.397 L19.301,56.53 C18.554,56.958 18.302,57.103 17.682,57.728" id="Fill-279" fill="#0073AA"></path>
<polygon id="Fill-280" fill="#0073AA" points="17.35 81.029 32.964 81.029 32.964 79.529 17.35 79.529"></polygon>
<polygon id="Fill-281" fill="#0073AA" points="17.35 83.758 32.964 83.758 32.964 82.258 17.35 82.258"></polygon>
<polygon id="Fill-282" fill="#0073AA" points="17.35 86.485 32.964 86.485 32.964 84.985 17.35 84.985"></polygon>
<polygon id="Fill-283" fill="#0073AA" points="17.35 89.214 32.964 89.214 32.964 87.714 17.35 87.714"></polygon>
<path d="M31.488,66.188 C31.488,69.684 28.653,72.519 25.157,72.519 C21.66,72.519 18.826,69.684 18.826,66.188 C18.826,62.691 21.66,59.856 25.157,59.856 C28.653,59.856 31.488,62.691 31.488,66.188" id="Fill-284" fill="#E7F7FF"></path>
<polygon id="Fill-320" fill="#0073AA" points="7.351 94.844 171.173 94.844 171.173 93.344 7.351 93.344"></polygon>
<polygon id="Fill-321" fill="#0073AA" points="2.789 94.844 5.528 94.844 5.528 93.344 2.789 93.344"></polygon>
<polygon id="Fill-322" fill="#0073AA" points="172.736 94.844 178.785 94.844 178.785 93.344 172.736 93.344"></polygon>
</g>
</symbol>
<symbol id="icon-license" width="168px" height="104px" viewBox="0 0 168 104" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M159.557,102.076 L159.557,23.847 C159.557,21.722 157.834,20 155.71,20 L98.847,20 C96.722,20 95,21.722 95,23.847 L95,102.077 L159.557,102.076" id="Fill-16" fill="#ADDDF8"></path>
<path d="M153.735,103.076 L153.735,24.847 C153.735,22.722 152.274,21 150.473,21 L102.262,21 C100.46,21 99,22.722 99,24.847 L99,103.077 L153.735,103.076" id="Fill-17" fill="#FFFFFF"></path>
<path d="M95,29.973 L95,23.235 C95,21.448 96.448,20 98.235,20 L156.322,20 C158.108,20 159.557,21.448 159.557,23.235 L159.557,29.973 L95,29.973" id="Fill-18" fill="#ADDDF8"></path>
<path d="M95.5,33.4394531 L94,33.4394531 L94,22.986 C94,20.788 95.787,19 97.985,19 L106.557,19 L106.557,20.5 L97.985,20.5 C96.614,20.5 95.5,21.615 95.5,22.986 L95.5,30.986 L95.5,33.4394531 Z" id="Fill-19" fill="#0073AA"></path>
<path d="M160.284,102.85612 L158.784,101.859 L158.784,22.986 C158.784,21.615 157.669,20.5 156.299,20.5 L118,20.5 L118,19 L156.299,19 C158.496,19 160.284,20.788 160.284,22.986 L160.284,101.859 L160.284,102.85612 Z" id="Fill-20" fill="#0073AA"></path>
<polygon id="Fill-21" fill="#0073AA" points="110 20.5 114.435 20.5 114.435 19 110 19"></polygon>
<polygon id="Fill-22" fill="#0073AA" points="95 30.5 159.557 30.5 159.557 29 95 29"></polygon>
<polygon id="Fill-23" fill="#0073AA" points="102 25.5 103.502 25.5 103.502 24 102 24"></polygon>
<polygon id="Fill-24" fill="#0073AA" points="107 25.5 108.503 25.5 108.503 24 107 24"></polygon>
<polygon id="Fill-25" fill="#0073AA" points="111 25.5 112.503 25.5 112.503 24 111 24"></polygon>
<polygon id="Fill-26" fill="#E7F7FF" points="103 55.642 150.26 55.642 150.26 37 103 37"></polygon>
<polygon id="Fill-27" fill="#D1E8F0" points="103 86.641 117.37 86.641 117.37 68 103 68"></polygon>
<polygon id="Fill-28" fill="#E7F7FF" points="120 79.641 134.782 79.641 134.782 61 120 61"></polygon>
<polygon id="Fill-29" fill="#E7F7FF" points="136 79.641 150.369 79.641 150.369 61 136 61"></polygon>
<polygon id="Fill-28-Copy" fill="#E7F7FF" points="102.48 103.641 134.782 103.641 134.782 84.999 102.48 84.999"></polygon>
<polygon id="Fill-29-Copy" fill="#E7F7FF" points="136 103.641 150.369 103.641 150.369 85 136 85"></polygon>
<path d="M167.024,7.5 L162.75,7.5 C162.335,7.5 162,7.164 162,6.75 C162,6.335 162.335,6 162.75,6 L167.024,6 C167.438,6 167.774,6.335 167.774,6.75 C167.774,7.164 167.438,7.5 167.024,7.5" id="Fill-30" fill="#0073AA"></path>
<path d="M164.75,9.774 C164.336,9.774 164,9.438 164,9.024 L164,4.75 C164,4.336 164.336,4 164.75,4 C165.164,4 165.5,4.336 165.5,4.75 L165.5,9.024 C165.5,9.438 165.164,9.774 164.75,9.774" id="Fill-31" fill="#0073AA"></path>
<path d="M38.024,3.5 L33.75,3.5 C33.336,3.5 33,3.164 33,2.75 C33,2.336 33.336,2 33.75,2 L38.024,2 C38.438,2 38.774,2.336 38.774,2.75 C38.774,3.164 38.438,3.5 38.024,3.5" id="Fill-32" fill="#0073AA"></path>
<path d="M35.75,5.774 C35.336,5.774 35,5.438 35,5.024 L35,0.75 C35,0.336 35.336,0 35.75,0 C36.164,0 36.5,0.336 36.5,0.75 L36.5,5.024 C36.5,5.438 36.164,5.774 35.75,5.774" id="Fill-33" fill="#0073AA"></path>
<polygon id="Fill-42" fill="#0073AA" points="51 3.5 68.395 3.5 68.395 2 51 2"></polygon>
<polygon id="Fill-43" fill="#0073AA" points="45 3.5 48.57 3.5 48.57 2 45 2"></polygon>
<path d="M10.553,75.276 C10.553,75.981 9.982,76.553 9.277,76.553 C8.572,76.553 8,75.981 8,75.276 C8,74.571 8.572,74 9.277,74 C9.982,74 10.553,74.571 10.553,75.276" id="Fill-44" fill="#0073AA"></path>
<path d="M81.83,19.915 C81.83,20.421 81.421,20.83 80.915,20.83 C80.41,20.83 80,20.421 80,19.915 C80,19.409 80.41,19 80.915,19 C81.421,19 81.83,19.409 81.83,19.915" id="Fill-45" fill="#0073AA"></path>
<path d="M20.444,7.222 C20.444,7.897 19.897,8.444 19.222,8.444 C18.547,8.444 18,7.897 18,7.222 C18,6.547 18.547,6 19.222,6 C19.897,6 20.444,6.547 20.444,7.222" id="Fill-48" fill="#0073AA"></path>
<path d="M166.444,43.222 C166.444,43.896 165.897,44.443 165.222,44.443 C164.547,44.443 164,43.896 164,43.222 C164,42.547 164.547,42 165.222,42 C165.897,42 166.444,42.547 166.444,43.222" id="Fill-49" fill="#0073AA"></path>
<polyline id="Fill-50" fill="#A6CFE4" points="100.512 102 95 102 100.512 102"></polyline>
<path d="M94.8495,88 L98.8495,88 L95.9598297,88 L95.9598297,102.09423 L99.023112,102.09423 L99.023112,103 L94.8495,103 L94.8495,88 Z M94.8495,32.75 L99.3995,32.75 L99.3995,32 L94.8495,32 L94.8495,32.75 Z" id="Fill-51" fill="#3378B0"></path>
<path d="M41,79.577 L41,34.024801 C41,32.9360152 41.884,32.053 42.974,32.053 L117.087891,32.053 C118.177891,32.053 120.988,34.9388835 120.988,36.0276693 L120.988,79.577 L41,79.577 Z" id="Fill-55" fill="#8AC4E6"></path>
<polygon id="Fill-56" fill="#0073AA" points="41 58.71 42.5 58.71 42.5 50 41 50"></polygon>
<path d="M121.774,89.5021973 L120.274,89.5021973 L120.274,36.617 C120.274,34.898 118.875,33.5 117.156,33.5 L58,33.5 L58,32 L117.156,32 C119.702,32 121.774,34.071 121.774,36.617 L121.774,89.5021973 L121.34668,85.7150879" id="Fill-57" fill="#0073AA"></path>
<polygon id="Fill-58" fill="#FFFFFF" points="46 83.024 117.396 83.024 117.396 37 46 37"></polygon>
<polygon id="Fill-59" fill="#E7F7FF" points="69 101.266 93.099 101.266 93.099 87 69 87"></polygon>
<polygon id="Fill-60" fill="#8AC4E6" points="69 93.133 93.099 93.133 93.099 86 69 86"></polygon>
<path d="M69.5,101.266 L92.099,101.266 L92.099,88.5 L69.5,88.5 L69.5,101.266 Z M68,102.766 L93.599,102.766 L93.599,87 L68,87 L68,102.766 Z" id="Fill-61" fill="#0073AA"></path>
<path d="M120.988,82.317 L41,82.317 L41,87.6012964 C41,88.2646852 41.896,88.802 43,88.802 L118.989,88.802 C120.093,88.802 120.988,88.2646852 120.988,87.6012964 L120.988,82.317" id="Fill-62" fill="#FFFFFF"></path>
<polygon id="Fill-63" fill="#0073AA" points="53 89.5 115.489 89.5 115.489 88 53 88"></polygon>
<polygon id="Fill-64" fill="#0073AA" points="51.5465 89.5 121.3405 89.5 121.3405 78.709 51.5465 78.709"></polygon>
<polygon id="Fill-66" fill="#FFFFFE" points="136 82.189 138.052 82.189 138.052 68.082 136 68.082"></polygon>
<path d="M137.746,101.42 L136.566,101.42 C135.149,101.42 134,100.271 134,98.854 L134,82 L140.312,82 L140.312,98.854 C140.312,100.271 139.163,101.42 137.746,101.42" id="Fill-67" fill="#FFFFFF"></path>
<path d="M134.5,83.5 L134.5,99.604 C134.5,100.606 135.315,101.421 136.316,101.421 L137.495,101.421 C138.497,101.421 139.312,100.606 139.312,99.605 L139.312,83.5 L134.5,83.5 Z M137.495,102.921 L136.316,102.921 C134.488,102.921 133,101.434 133,99.604 L133,82 L140.812,82 L140.812,99.605 C140.812,101.434 139.325,102.921 137.495,102.921 L137.495,102.921 Z" id="Fill-68" fill="#0073AA"></path>
<polygon id="Fill-69" fill="#0073AA" points="132 83.5 142.066 83.5 142.066 82 132 82"></polygon>
<polyline id="Fill-70" fill="#FFFFFF" points="138.504 64.191 135 64.191 135.416 58 138.107 58 138.504 64.191"></polyline>
<path d="M136.283,81.086 L136.835,81.086 L136.835,63.191 L137.506,63.191 L137.205,58.5 L135.919,58.5 L135.604,63.191 L136.283,63.191 L136.283,81.086 Z M138.335,82.586 L134.783,82.586 L134.783,64.691 L134,64.691 L134.517,57 L138.612,57 L139.106,64.691 L138.335,64.691 L138.335,82.586 L138.335,82.586 Z" id="Fill-71" fill="#0073AA"></path>
<path d="M158.556,66.414 C158.556,63.16 156.675,60.353 153.944,59 L153.944,66.158 L150.278,68.274 L146.612,66.158 L146.612,59 C143.881,60.353 142,63.16 142,66.414 C142,69.82 144.057,72.742 146.996,74.013 L147.043,98.072 C147.043,99.885 148.513,101.354 150.325,101.354 C152.138,101.354 153.607,99.885 153.607,98.072 L153.56,74.013 C156.499,72.742 158.556,69.82 158.556,66.414" id="Fill-72" fill="#FFFFFF"></path>
<path d="M145.612,60.532 C143.666,61.934 142.5,64.172 142.5,66.622 C142.5,69.626 144.283,72.339 147.044,73.533 L147.495,73.728 L147.543,98.279 C147.543,99.677 148.679,100.812 150.075,100.812 C151.472,100.812 152.608,99.677 152.608,98.281 L152.559,73.729 L153.012,73.533 C155.772,72.339 157.556,69.626 157.556,66.622 C157.556,64.171 156.389,61.933 154.444,60.532 L154.444,66.799 L150.028,69.349 L145.612,66.799 L145.612,60.532 Z M150.075,102.312 C147.852,102.312 146.043,100.504 146.043,98.281 L145.997,74.7 C142.945,73.177 141,70.058 141,66.622 C141,63.172 142.927,60.074 146.029,58.536 L147.112,58 L147.112,65.932 L150.028,67.616 L152.944,65.932 L152.944,58 L154.027,58.536 C157.129,60.073 159.056,63.171 159.056,66.622 C159.056,70.057 157.111,73.175 154.061,74.699 L154.108,98.279 C154.108,100.504 152.298,102.312 150.075,102.312 L150.075,102.312 Z" id="Fill-73" fill="#0073AA"></path>
<path d="M53,90 L54.154,90" id="Fill-74" fill="#A6CFE4"></path>
<polygon id="Fill-78" fill="#0073AA" points="53 89.5 54.154 89.5 54.154 88 53 88"></polygon>
<path d="M17,103.21 L50.716,103.21 L50.716,64.224 C50.716,62.995 49.72,62 48.492,62 L19.224,62 C17.995,62 17,62.995 17,64.224 L17,103.21" id="Fill-80" fill="#FFFFFF"></path>
<path d="M18.5,99.476 L17,99.476 L17,65.686 C17,63.654 18.653,62 20.685,62 L24.907,62 L24.907,63.5 L20.685,63.5 C19.48,63.5 18.5,64.481 18.5,65.686 L18.5,99.476" id="Fill-81" fill="#0073AA"></path>
<polygon id="Fill-82" fill="#0073AA" points="27 63.5 31.912 63.5 31.912 62 27 62"></polygon>
<path d="M52.366,99.476 L50.866,99.476 L50.866,65.686 C50.866,64.481 49.886,63.5 48.681,63.5 L34,63.5 L34,62 L48.681,62 C50.712,62 52.366,63.654 52.366,65.686 L52.366,99.476" id="Fill-83" fill="#0073AA"></path>
<polygon id="Fill-84" fill="#E7F7FF" points="20 103.024 48.147 103.024 48.147 68 20 68"></polygon>
<polygon id="Fill-85" fill="#0073AA" points="31 66.5 38.152 66.5 38.152 65 31 65"></polygon>
<path d="M53.497,102.743 L53.497,101.63 C53.497,100.547 52.62,99.666 51.535,99.666 L48.487,99.654 C48.125,98.069 47.502,96.586 46.661,95.257 L48.819,93.097 C49.593,92.329 49.597,91.078 48.826,90.31 L47.211,88.693 C46.442,87.925 45.193,87.919 44.423,88.687 L42.261,90.847 C40.933,90.005 39.451,89.383 37.871,89.023 L37.871,85.973 C37.871,84.884 36.985,84 35.893,84 L33.614,84 C32.522,84 31.639,84.884 31.639,85.973 L31.639,89.023 C30.08,89.374 28.626,89.989 27.31,90.804 L25.164,88.639 C24.396,87.867 23.145,87.852 22.374,88.626 L20.75,90.231 C19.98,90.993 19.971,92.246 20.736,93.018 L22.88,95.189 C22.035,96.513 21.404,97.995 21.04,99.575 L17.991,99.562 C16.899,99.553 16.015,100.438 16.015,101.533 L16,102.743 L28.493,102.743 C28.493,99.288 31.296,96.495 34.756,96.495 C38.211,96.495 41.01,99.288 41.01,102.743 L53.497,102.743" id="Fill-86" fill="#8AC4E6"></path>
<path d="M41.511,102.493 L40.011,102.493 C40.011,99.462 37.541,96.995 34.506,96.995 C31.466,96.995 28.993,99.462 28.993,102.493 L27.493,102.493 C27.493,98.634 30.639,95.495 34.506,95.495 C38.368,95.495 41.511,98.634 41.511,102.493 Z M16.5,102.503 L15,102.484 L15.015,101.273 C15.015,100.549 15.302,99.862 15.821,99.346 C16.336,98.835 17.001,98.568 17.747,98.563 L20.206,98.573 C20.544,97.337 21.039,96.155 21.684,95.049 L19.952,93.295 C19.443,92.78 19.163,92.095 19.167,91.366 C19.172,90.639 19.457,89.958 19.972,89.448 L21.596,87.842 C22.615,86.82 24.415,86.824 25.446,87.86 L27.18,89.611 C28.283,88.986 29.442,88.508 30.639,88.186 L30.639,85.723 C30.639,84.222 31.861,83 33.364,83 L35.643,83 C37.147,83 38.371,84.222 38.371,85.723 L38.371,88.187 C39.61,88.522 40.792,89.012 41.897,89.651 L43.643,87.906 C44.704,86.849 46.431,86.852 47.491,87.913 L49.106,89.53 C49.619,90.041 49.902,90.724 49.902,91.451 C49.901,92.18 49.615,92.865 49.098,93.38 L47.357,95.121 C47.996,96.231 48.488,97.415 48.824,98.656 L51.288,98.666 C52.781,98.666 53.997,99.883 53.997,101.38 L53.997,102.493 L52.497,102.493 L52.497,101.38 C52.497,100.711 51.954,100.166 51.285,100.166 L47.638,100.151 L47.506,99.571 C47.167,98.087 46.586,96.686 45.778,95.407 L45.457,94.901 L48.039,92.317 C48.273,92.084 48.401,91.776 48.402,91.449 C48.402,91.125 48.276,90.82 48.046,90.591 L46.43,88.974 C45.953,88.495 45.177,88.494 44.703,88.968 L42.116,91.551 L41.61,91.23 C40.336,90.423 38.937,89.842 37.454,89.505 L36.871,89.371 L36.871,85.723 C36.871,85.049 36.32,84.5 35.643,84.5 L33.364,84.5 C32.688,84.5 32.139,85.049 32.139,85.723 L32.139,89.373 L31.553,89.505 C30.122,89.827 28.742,90.394 27.454,91.192 L26.947,91.506 L24.382,88.917 C23.921,88.454 23.115,88.445 22.655,88.905 L21.027,90.514 C20.797,90.742 20.669,91.047 20.667,91.375 C20.666,91.702 20.79,92.01 21.019,92.24 L23.585,94.838 L23.262,95.343 C22.447,96.62 21.861,98.016 21.521,99.494 L21.386,100.078 L17.738,100.063 C17.365,100.068 17.106,100.184 16.877,100.412 C16.644,100.643 16.515,100.952 16.515,101.282 L16.5,102.503 L16.5,102.503 Z" id="Fill-87" fill="#0073AA"></path>
<path d="M41,34.935 L41,34.935 C41,33.977 41.683,33.179 42.588,33 C41.683,33.179 41,33.978 41,34.936" id="Fill-97" fill="#B4D7E7"></path>
<path d="M41.75,50.13 L41.75,49.867 L41,49.867 L41,33.973 C41,33.016 41.683,32.217 42.588,32.038 C42.713,32.013 42.842,32 42.974,32 L53.999,32 C54.184,33.539 54.164,35.105 53.921,36.652 L45.341,36.652 L45.341,48.704 C44.19,49.324 42.985,49.799 41.75,50.13" id="Fill-98" fill="#4B97C6"></path>
<path d="M41,50.593 L41,50 L42.5,50 L42.5,50.263 C42.005,50.396 41.504,50.506 41,50.593" id="Fill-99" fill="#0073AA"></path>
<path d="M57.996,51.046 C57.881,51.046 57.765,51.027 57.652,50.987 L48.182,47.647 C47.483,48.178 46.753,48.646 46,49.052 L46,37 L54.58,37 C54.365,38.372 53.975,39.729 53.4,41.036 L58.863,49.448 C59.105,49.82 59.083,50.303 58.809,50.652 C58.608,50.906 58.307,51.046 57.996,51.046" id="Fill-100" fill="#E7F7FF"></path>
<path d="M26.1809572,45.6108842 C19.2089572,40.1958842 17.9459572,30.1538842 23.3599572,23.1818842 C28.7739572,16.2088842 38.8159572,14.9458842 45.7889572,20.3598842 C51.8099572,25.0348842 53.5689572,33.1588842 50.4529572,39.7798842 L56.1399572,48.5368842 C56.2879572,48.7638842 56.0629572,49.0498842 55.8069572,48.9598842 L45.9509572,45.4828842 C40.3129572,49.9858842 32.1309572,50.2308842 26.1809572,45.6108842" id="Fill-101" fill="#FFFFFF"></path>
<path d="M35.7222614,49.4751973 C32.1062614,49.4751973 28.4892614,48.2991973 25.4552614,45.9421973 C18.5682614,40.5951973 16.9512614,30.7251973 21.7722614,23.4741973 L23.0212614,24.3041973 C18.6322614,30.9051973 20.1062614,39.8901973 26.3752614,44.7581973 C31.9552614,49.0911973 39.7022614,49.0401973 45.2152614,44.6361973 L45.5412614,44.3761973 L54.4992614,47.5361973 L49.3302614,39.5781973 L49.5072614,39.2001973 C52.5142614,32.8101973 50.6452614,25.0271973 45.0622614,20.6911973 C40.8452614,17.4171973 35.1332614,16.5931973 30.1532614,18.5391973 L29.6082614,17.1431973 C35.0762614,15.0031973 41.3502614,15.9101973 45.9822614,19.5071973 C52.0032614,24.1821973 54.0922614,32.5111973 51.0392614,39.4551973 L56.5022614,47.8671973 C56.7442614,48.2381973 56.7222614,48.7221973 56.4482614,49.0701973 C56.1742614,49.4191973 55.7082614,49.5531973 55.2912614,49.4061973 L45.8212614,46.0661973 C42.8302614,48.3371973 39.2762614,49.4751973 35.7222614,49.4751973" id="Fill-102" fill="#0073AA"></path>
<path d="M25.127,22.451 L24,21.462 C24.279,21.143 24.568,20.838 24.866,20.546 C25.873,19.559 27.002,18.702 28.224,18 L28.971,19.301 C27.86,19.94 26.832,20.719 25.916,21.617 C25.645,21.884 25.382,22.161 25.127,22.451" id="Fill-103" fill="#0073AA"></path>
<polyline id="Fill-112" fill="#0073AA" points="54.009 33.51 54 32.01 55.647 32 55.656 33.5 54.009 33.51"></polyline>
<polygon id="Fill-125" fill="#0073AA" points="0 103.5 2.739 103.5 2.739 102 0 102"></polygon>
<polygon id="Fill-127" fill="#3378B0" points="4 103.5 167.822 103.5 167.822 102 4 102"></polygon>
<g id="Group" transform="translate(25.000000, 22.000000)">
<path d="M4.50393103,3.19910345 L2.73937931,4.96289655 L2.16206897,4.38634483 C1.84193103,4.0662069 1.84193103,3.54731034 2.16206897,3.22717241 L2.76744828,2.6217931 C3.08758621,2.30165517 3.60648276,2.30165517 3.92662069,2.6217931 L4.50393103,3.19910345" id="Fill-132" fill="#9CC9E1"></path>
<path d="M3.34703448,2.95103448 L3.17027586,3.02386207 L2.56489655,3.62924138 L2.49131034,3.80675862 L2.56489655,3.98427586 L2.73937931,4.15875862 L3.69903448,3.19910345 L3.52455172,3.02462069 L3.34703448,2.95103448 Z M2.73937931,5.7677931 L1.76,4.78841379 C1.49751724,4.52668966 1.35337931,4.17772414 1.35337931,3.80675862 C1.35337931,3.4357931 1.49827586,3.08682759 1.76075862,2.82434483 L2.36537931,2.21972414 C2.88882759,1.69551724 3.80448276,1.694 4.32944828,2.21972414 L5.30806897,3.19910345 L2.73937931,5.7677931 L2.73937931,5.7677931 Z" id="Fill-133" fill="#0073AA"></path>
<path d="M18.8752414,4.77855172 L17.1129655,3.01248276 L17.6902759,2.43593103 C18.0111724,2.11655172 18.530069,2.11731034 18.8502069,2.43744828 L19.454069,3.04282759 C19.7742069,3.36372414 19.7734483,3.88262069 19.4525517,4.20275862 L18.8752414,4.77855172" id="Fill-134" fill="#9CC9E1"></path>
<path d="M17.9171034,3.01324138 L18.876,3.97441379 L19.0504828,3.79993103 L19.124069,3.62241379 L19.0512414,3.44489655 L18.4473793,2.83951724 C18.3487586,2.74089655 18.1902069,2.74165517 18.0923448,2.83875862 L17.9171034,3.01324138 Z M18.8744828,5.58344828 L16.308069,3.01172414 L17.2889655,2.03310345 C17.8306207,1.49296552 18.7113793,1.49372414 19.2530345,2.03537931 L19.8568966,2.64151724 C20.1193793,2.904 20.2627586,3.25296552 20.262,3.62393103 C20.262,3.99489655 20.1171034,4.34386207 19.8538621,4.60558621 L18.8744828,5.58344828 L18.8744828,5.58344828 Z" id="Fill-135" fill="#0073AA"></path>
<path d="M17.2783448,19.1483448 L19.0466897,17.3883448 L19.6224828,17.9664138 C19.9418621,18.2873103 19.9403448,18.8062069 19.6194483,19.1255862 L19.0133103,19.7294483 C18.6924138,20.0488276 18.1727586,20.0473103 17.8533793,19.7264138 L17.2783448,19.1483448" id="Fill-136" fill="#9CC9E1"></path>
<path d="M18.0824828,19.1506207 L18.2569655,19.3251034 L18.4344828,19.3994483 L18.6112414,19.3258621 L19.2181379,18.7227586 L19.2917241,18.5452414 L19.2188966,18.3677241 L19.0451724,18.1924828 L18.0824828,19.1506207 Z M18.4344828,20.5373793 L18.4314483,20.5373793 C18.0604828,20.5366207 17.7115172,20.3909655 17.4505517,20.1277241 L16.4734483,19.1468276 L19.0482069,16.5834483 L20.0253103,17.5651034 C20.2870345,17.8283448 20.4311724,18.1773103 20.4296552,18.5482759 C20.4288966,18.9192414 20.2832414,19.2674483 20.0207586,19.5291724 L19.4146207,20.1322759 C19.1521379,20.394 18.8046897,20.5373793 18.4344828,20.5373793 L18.4344828,20.5373793 Z" id="Fill-137" fill="#0073AA"></path>
<path d="M2.91006897,17.534 L4.66855172,19.3046207 L4.08972414,19.8796552 C3.76806897,20.1990345 3.24917241,20.1967586 2.93055172,19.8758621 L2.32744828,19.2682069 C2.00806897,18.9473103 2.01034483,18.4284138 2.33124138,18.1090345 L2.91006897,17.534" id="Fill-138" fill="#9CC9E1"></path>
<path d="M2.9077931,18.3388966 L2.73255172,18.5126207 L2.6582069,18.6901379 L2.73103448,18.8668966 L3.33413793,19.4745517 L3.51089655,19.5488966 L3.51165517,19.5488966 L3.68841379,19.476069 L3.86365517,19.3023448 L2.9077931,18.3388966 Z M3.51165517,20.6868276 L3.50710345,20.6868276 C3.13613793,20.6853103 2.78717241,20.5396552 2.5262069,20.2764138 L1.92386207,19.6695172 C1.66213793,19.4062759 1.51875862,19.0565517 1.52027586,18.6863448 C1.52103448,18.3146207 1.66744828,17.9664138 1.93068966,17.7046897 L2.91310345,16.7298621 L5.47268966,19.3084138 L4.49027586,20.2832414 C4.22855172,20.5434483 3.88110345,20.6868276 3.51165517,20.6868276 L3.51165517,20.6868276 Z" id="Fill-139" fill="#0073AA"></path>
<path d="M10.9006207,16.8975172 C7.58772414,16.8975172 4.90296552,14.2127586 4.90296552,10.9006207 C4.90296552,7.58848276 7.58772414,4.90296552 10.9006207,4.90296552 C14.2127586,4.90296552 16.8975172,7.58848276 16.8975172,10.9006207 C16.8975172,14.2127586 14.2127586,16.8975172 10.9006207,16.8975172 Z M10.9006207,0.568965517 C5.19427586,0.568965517 0.568965517,5.19427586 0.568965517,10.9006207 C0.568965517,16.6062069 5.19427586,21.2322759 10.9006207,21.2322759 C16.6062069,21.2322759 21.2315172,16.6062069 21.2315172,10.9006207 C21.2315172,5.19427586 16.6062069,0.568965517 10.9006207,0.568965517 L10.9006207,0.568965517 Z" id="Fill-140" fill="#FFFFFF"></path>
<path d="M10.9006207,5.47193103 C7.90710345,5.47193103 5.47193103,7.90710345 5.47193103,10.9006207 C5.47193103,13.8933793 7.90710345,16.3285517 10.9006207,16.3285517 C13.8933793,16.3285517 16.3285517,13.8933793 16.3285517,10.9006207 C16.3285517,7.90710345 13.8933793,5.47193103 10.9006207,5.47193103 Z M10.9006207,17.4664828 C7.27972414,17.4664828 4.334,14.5207586 4.334,10.9006207 C4.334,7.27972414 7.27972414,4.334 10.9006207,4.334 C14.5207586,4.334 17.4664828,7.27972414 17.4664828,10.9006207 C17.4664828,14.5207586 14.5207586,17.4664828 10.9006207,17.4664828 Z M10.9006207,1.13793103 C5.51744828,1.13793103 1.13793103,5.51744828 1.13793103,10.9006207 C1.13793103,16.2837931 5.51744828,20.6633103 10.9006207,20.6633103 C16.2830345,20.6633103 20.6625517,16.2837931 20.6625517,10.9006207 C20.6625517,5.51744828 16.2830345,1.13793103 10.9006207,1.13793103 Z M10.9006207,21.8012414 C4.89006897,21.8012414 0,16.9111724 0,10.9006207 C0,4.89006897 4.89006897,0 10.9006207,0 C16.9111724,0 21.8004828,4.89006897 21.8004828,10.9006207 C21.8004828,16.9111724 16.9111724,21.8012414 10.9006207,21.8012414 L10.9006207,21.8012414 Z" id="Fill-141" fill="#0073AA"></path>
<path d="M1.59386207,6.43613793 L5.45524138,8.39868966 C6.00144828,7.2122069 6.91862069,6.23206897 8.06413793,5.61606897 L6.09324138,1.75924138 C4.13448276,2.79172414 2.5557931,4.43565517 1.59386207,6.43613793" id="Fill-142" fill="#D1E8F0"></path>
<path d="M15.6867586,1.74937931 L13.7242069,5.61 C14.8712414,6.22372414 15.7906897,7.20158621 16.339931,8.38655172 L20.1967586,6.41565517 C19.2310345,4.41744828 17.6477931,2.77731034 15.6867586,1.74937931" id="Fill-143" fill="#D1E8F0"></path>
<path d="M20.0010345,15.7914483 L16.1335862,13.8258621 C15.5357931,14.8917241 14.6216552,15.7527586 13.5186207,16.2898621 L15.4895172,20.1466897 C17.4057931,19.1931034 18.9882759,17.672069 20.0010345,15.7914483" id="Fill-144" fill="#D1E8F0"></path>
<path d="M6.33068966,20.1557931 L8.29324138,16.2951724 C7.18944828,15.7603448 6.2737931,14.9015862 5.67372414,13.8364828 L1.80931034,15.811931 C2.82586207,17.6895172 4.41289655,19.2067586 6.33068966,20.1557931" id="Fill-145" fill="#D1E8F0"></path>
<path d="M2.36234483,6.18806897 L5.20944828,7.63551724 C5.72606897,6.73731034 6.45206897,5.96655172 7.31537931,5.40062069 L5.85731034,2.54668966 C4.40455172,3.4282069 3.18924138,4.69510345 2.36234483,6.18806897 Z M5.72455172,9.174 L0.840551724,6.69103448 L1.0817931,6.18882759 C2.09075862,4.08896552 3.77717241,2.33731034 5.82848276,1.25627586 L6.33827586,0.987724138 L8.82427586,5.85351724 L8.33344828,6.11751724 C7.30324138,6.67131034 6.4642069,7.56572414 5.97186207,8.63613793 L5.72455172,9.174 L5.72455172,9.174 Z" id="Fill-146" fill="#0073AA"></path>
<path d="M14.4729655,5.39303448 C15.3370345,5.95668966 16.0653103,6.72593103 16.5834483,7.62337931 L19.4275172,6.16986207 C18.5983448,4.67841379 17.3792414,3.41455172 15.9249655,2.53682759 L14.4729655,5.39303448 Z M16.0721379,9.16262069 L15.8233103,8.62551724 C15.3286897,7.55662069 14.4881379,6.66372414 13.4556552,6.11144828 L12.964069,5.84896552 L15.4402069,0.977862069 L15.9507586,1.24565517 C18.0051034,2.32213793 19.6945517,4.07075862 20.7088276,6.16834483 L20.9508276,6.66903448 L16.0721379,9.16262069 L16.0721379,9.16262069 Z" id="Fill-147" fill="#0073AA"></path>
<path d="M14.278,16.5250345 L15.7330345,19.3728966 C17.1630345,18.5588966 18.3495172,17.4156552 19.2082759,16.0266207 L16.3422069,14.570069 C15.8058621,15.3658621 15.1041379,16.0304138 14.278,16.5250345 Z M15.2391724,20.9068276 L12.7478621,16.0326897 L13.2697931,15.7785517 C14.2749655,15.2892414 15.0935172,14.5177241 15.6366897,13.5474483 L15.9037241,13.0710345 L20.7778621,15.547931 L20.5017241,16.0615172 C19.4297931,18.0521379 17.7835862,19.6406897 15.7428966,20.6557241 L15.2391724,20.9068276 L15.2391724,20.9068276 Z" id="Fill-148" fill="#0073AA"></path>
<path d="M2.60282759,16.0455862 C3.46310345,17.4315862 4.65337931,18.5710345 6.08565517,19.3827586 L7.53462069,16.5318621 C6.70772414,16.0387586 6.00524138,15.3764828 5.46662069,14.5822069 L2.60282759,16.0455862 Z M6.58331034,20.9151724 L6.07882759,20.6663448 C4.03358621,19.6543448 2.38510345,18.0695862 1.30937931,16.0827586 L1.03172414,15.569931 L5.90206897,13.0816552 L6.16986207,13.5573103 C6.71531034,14.5268276 7.53537931,15.296069 8.54131034,15.7823448 L9.06324138,16.0357241 L6.58331034,20.9151724 L6.58331034,20.9151724 Z" id="Fill-149" fill="#0073AA"></path>
</g>
</g>
</symbol>
<symbol id="icon-content" width="175px" height="110px" viewBox="0 0 175 110" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group-2" transform="translate(55.000000, 43.000000)">
<polygon id="Fill-148" fill="#0073AA" points="0.559516803 47.1133533 7.64844949 47.1133533 7.64844949 45.8015 0.559516803 45.8015"></polygon>
<path d="M53.8269776,52.3664477 L53.8269776,4.41821002 C53.8269776,2.9725477 52.6187393,1.8015 51.1291798,1.8015 L11.2573146,1.8015 C9.76775514,1.8015 8.5595168,2.9725477 8.5595168,4.41821002 L8.5595168,52.3664477 L53.8269776,52.3664477" id="Fill-159" fill="#FFFFFF"></path>
<path d="M8,10.1133533 L8,4.49781548 C8,3.00819763 9.01618254,1.8015 10.2686027,1.8015 L50.9988581,1.8015 C52.2512783,1.8015 53.2674608,3.00819763 53.2674608,4.49781548 L53.2674608,10.1133533 L8,10.1133533" id="Fill-160" fill="#E7F7FF"></path>
<polygon id="Fill-161" fill="#0073AA" points="8.5595168 10.1133533 53.8269776 10.1133533 53.8269776 8.8015 8.5595168 8.8015"></polygon>
<polygon id="Fill-165" fill="#E7F7FF" points="12 32.1133533 39.6977074 32.1133533 39.6977074 12.8015 12 12.8015"></polygon>
<polygon id="Fill-165" fill="#E7F7FF" points="41 25.4801247 49.6977074 25.4801247 49.6977074 12.8015 41 12.8015"></polygon>
<polygon id="Fill-166" fill="#D1E8F0" points="15.5595168 46.4801247 25.6356835 46.4801247 25.6356835 33.8015 15.5595168 33.8015"></polygon>
<path d="M7.5595168,52.0232489 L7.5595168,4.07501124 C7.5595168,2.26990111 9.07342141,0.8015 10.9335674,0.8015 L50.8054326,0.8015 C52.6655786,0.8015 54.1794832,2.26990111 54.1794832,4.07501124 L54.1794832,31.0259048 L52.8269776,31.0259048 L52.8269776,4.07501124 C52.8269776,2.99316956 51.9198972,2.11335329 50.8054326,2.11335329 L10.9335674,2.11335329 C9.81910283,2.11335329 8.9120224,2.99316956 8.9120224,4.07501124 L8.9120224,52.0232489 L7.5595168,52.0232489 Z" id="Fill-168" fill="#0073AA"></path>
</g>
<path d="M61.309,91.654 C61.309,95.881 57.882,99.308 53.655,99.308 C49.427,99.308 46,95.881 46,91.654 C46,87.427 49.427,84 53.655,84 C57.882,84 61.309,87.427 61.309,91.654" id="Fill-406" fill="#FFFFFF"></path>
<path d="M118.91,95.455 C118.91,100.125 115.124,103.91 110.455,103.91 C105.785,103.91 102,100.125 102,95.455 C102,90.786 105.785,87 110.455,87 C115.124,87 118.91,90.786 118.91,95.455" id="Fill-407" fill="#FFFFFF"></path>
<path d="M139.063,95.531 C139.063,102.452 133.452,108.062 126.532,108.062 C119.611,108.062 114,102.452 114,95.531 C114,88.61 119.611,83 126.532,83 C133.452,83 139.063,88.61 139.063,95.531" id="Fill-408" fill="#FFFFFF"></path>
<path d="M150.687,100.844 C150.687,105.175 147.175,108.687 142.843,108.687 C138.512,108.687 135,105.175 135,100.844 C135,96.512 138.512,93 142.843,93 C147.175,93 150.687,96.512 150.687,100.844" id="Fill-409" fill="#FFFFFF"></path>
<path d="M168.34,95.67 C168.34,102.667 162.668,108.34 155.67,108.34 C148.673,108.34 143,102.667 143,95.67 C143,88.672 148.673,83 155.67,83 C162.668,83 168.34,88.672 168.34,95.67" id="Fill-410" fill="#FFFFFF"></path>
<path d="M48.999,92 C48.999,97.522 44.522,101.999 39,101.999 C33.477,101.999 29,97.522 29,92 C29,86.477 33.477,82 39,82 C44.522,82 48.999,86.477 48.999,92" id="Fill-411" fill="#FFFFFF"></path>
<path d="M63.999,99 C63.999,104.522 59.522,108.999 54,108.999 C48.477,108.999 44,104.522 44,99 C44,93.477 48.477,89 54,89 C59.522,89 63.999,93.477 63.999,99" id="Fill-412" fill="#FFFFFF"></path>
<path d="M26.76,100.88 C26.76,105.785 22.784,109.76 17.88,109.76 C12.976,109.76 9,105.785 9,100.88 C9,95.976 12.976,92 17.88,92 C22.784,92 26.76,95.976 26.76,100.88" id="Fill-413" fill="#FFFFFF"></path>
<path d="M38.02,100.01 C38.02,103.882 34.881,107.02 31.01,107.02 C27.138,107.02 24,103.882 24,100.01 C24,96.139 27.138,93 31.01,93 C34.881,93 38.02,96.139 38.02,100.01" id="Fill-415" fill="#FFFFFF"></path>
<path d="M61.807,91.405 L60.307,91.405 C60.307,87.598 57.211,84.5 53.404,84.5 C49.597,84.5 46.5,87.598 46.5,91.405 L45,91.405 C45,86.77 48.77,83 53.404,83 C58.038,83 61.807,86.77 61.807,91.405" id="Fill-416" fill="#0073AA"></path>
<path d="M120.41,96.205 L118.91,96.205 C118.91,91.957 115.454,88.5 111.205,88.5 C106.956,88.5 103.5,91.957 103.5,96.205 L102,96.205 C102,91.13 106.129,87 111.205,87 C116.281,87 120.41,91.13 120.41,96.205 L131.820312,88.6435547" id="Fill-417" fill="#0073AA"></path>
<path d="M158.3625,49.5 L154.0885,49.5 C153.6735,49.5 153.3385,49.164 153.3385,48.75 C153.3385,48.336 153.6735,48 154.0885,48 L158.3625,48 C158.7765,48 159.1125,48.336 159.1125,48.75 C159.1125,49.164 158.7765,49.5 158.3625,49.5" id="Fill-418" fill="#0073AA"></path>
<path d="M156.0885,51.774 C155.6745,51.774 155.3385,51.438 155.3385,51.024 L155.3385,46.75 C155.3385,46.336 155.6745,46 156.0885,46 C156.5025,46 156.8385,46.336 156.8385,46.75 L156.8385,51.024 C156.8385,51.438 156.5025,51.774 156.0885,51.774" id="Fill-419" fill="#0073AA"></path>
<path d="M25.025,7.5 L20.75,7.5 C20.336,7.5 20,7.164 20,6.75 C20,6.336 20.336,6 20.75,6 L25.025,6 C25.439,6 25.775,6.336 25.775,6.75 C25.775,7.164 25.439,7.5 25.025,7.5" id="Fill-420" fill="#0073AA"></path>
<path d="M22.75,9.774 C22.336,9.774 22,9.438 22,9.024 L22,4.75 C22,4.336 22.336,4 22.75,4 C23.164,4 23.5,4.336 23.5,4.75 L23.5,9.024 C23.5,9.438 23.164,9.774 22.75,9.774" id="Fill-421" fill="#0073AA"></path>
<path d="M100.74975,4.52275 C100.55775,4.52275 100.36575,4.44875 100.21975,4.30275 C99.92675,4.00975 99.92675,3.53475 100.21975,3.24175 L103.24175,0.21975 C103.53475,-0.07325 104.00975,-0.07325 104.30275,0.21975 C104.59575,0.51275 104.59575,0.98675 104.30275,1.27975 L101.27975,4.30275 C101.13375,4.44875 100.94175,4.52275 100.74975,4.52275" id="Fill-424" fill="#0073AA"></path>
<path d="M103.77175,4.52275 C103.57975,4.52275 103.38875,4.44875 103.24175,4.30275 L100.21975,1.27975 C99.92675,0.98675 99.92675,0.51275 100.21975,0.21975 C100.51275,-0.07325 100.98675,-0.07325 101.27975,0.21975 L104.30275,3.24175 C104.59575,3.53475 104.59575,4.00975 104.30275,4.30275 C104.15575,4.44875 103.96475,4.52275 103.77175,4.52275" id="Fill-425" fill="#0073AA"></path>
<path d="M10.74975,61.52275 C10.55775,61.52275 10.36575,61.44875 10.21975,61.30275 C9.92675,61.00975 9.92675,60.53475 10.21975,60.24175 L13.24175,57.21975 C13.53475,56.92675 14.00975,56.92675 14.30275,57.21975 C14.59575,57.51275 14.59575,57.98675 14.30275,58.27975 L11.27975,61.30275 C11.13375,61.44875 10.94175,61.52275 10.74975,61.52275" id="Fill-424" fill="#0073AA"></path>
<path d="M13.77175,61.52275 C13.57975,61.52275 13.38875,61.44875 13.24175,61.30275 L10.21975,58.27975 C9.92675,57.98675 9.92675,57.51275 10.21975,57.21975 C10.51275,56.92675 10.98675,56.92675 11.27975,57.21975 L14.30275,60.24175 C14.59575,60.53475 14.59575,61.00975 14.30275,61.30275 C14.15575,61.44875 13.96475,61.52275 13.77175,61.52275" id="Fill-425" fill="#0073AA"></path>
<polygon id="Fill-426" fill="#0073AA" points="142 69.5 163.817 69.5 163.817 68 142 68"></polygon>
<polygon id="Fill-428" fill="#0073AA" points="32 7.5 72.796 7.5 72.796 6 32 6"></polygon>
<polygon id="Fill-429" fill="#0073AA" points="125 8.5 146.817 8.5 146.817 7 125 7"></polygon>
<polygon id="Fill-430" fill="#0073AA" points="119 8.5 122.912 8.5 122.912 7 119 7"></polygon>
<path d="M139.677,74.338 C139.677,75.077 139.078,75.676 138.339,75.676 C137.599,75.676 137,75.077 137,74.338 C137,73.599 137.599,73 138.339,73 C139.078,73 139.677,73.599 139.677,74.338" id="Fill-433" fill="#0073AA"></path>
<path d="M14.677,18.338 C14.677,19.077 14.078,19.676 13.339,19.676 C12.6,19.676 12,19.077 12,18.338 C12,17.599 12.6,17 13.339,17 C14.078,17 14.677,17.599 14.677,18.338" id="Fill-434" fill="#0073AA"></path>
<path d="M164.677,26.339 C164.677,27.078 164.078,27.677 163.339,27.677 C162.6,27.677 162,27.078 162,26.339 C162,25.599 162.6,25 163.339,25 C164.078,25 164.677,25.599 164.677,26.339" id="Fill-435" fill="#0073AA"></path>
<path d="M64.696,28.848 C64.696,29.317 64.317,29.696 63.848,29.696 C63.38,29.696 63,29.317 63,28.848 C63,28.38 63.38,28 63.848,28 C64.317,28 64.696,28.38 64.696,28.848" id="Fill-438" fill="#0073AA"></path>
<polygon id="Fill-478" fill="#8AC4E6" points="60 109.352 67.384 109.352 67.384 75 60 75"></polygon>
<path d="M60.5,75.5 L60.5,107.257 C60.5,107.861 60.991,108.352 61.595,108.352 L66.384,108.352 L66.384,75.5 L60.5,75.5 Z M67.884,109.467529 L61.5241699,109.467529 C60.0931699,109.467529 59,108.688 59,107.257 L59,74 L67.884,74 L67.884,109.852 L67.884,109.467529 Z" id="Fill-479" fill="#0073AA"></path>
<polyline id="Fill-480" fill="#E7F7FF" points="56 79.993 63.384 79.993 65.78 75 58.396 75 56 79.993"></polyline>
<path d="M57.383,78.992 L63.104,78.992 L64.78,75.5 L59.059,75.5 L57.383,78.992 Z M64.047,80.492 L55,80.492 L58.115,74 L67.164,74 L64.047,80.492 L64.047,80.492 Z" id="Fill-481" fill="#0073AA"></path>
<path d="M109.971,109.352 L68,109.352 L68,75 L112.58,75 L112.58,106.742 C112.58,108.183 111.412,109.352 109.971,109.352" id="Fill-482" fill="#0073AA"></path>
<polygon id="Fill-483" fill="#0073AA" points="111 99.565 112.5 99.565 112.5 94 111 94"></polygon>
<path d="M67,109.434082 L67,74 L113.081,74 L113.081,90.582 L111.581,90.582 L111.581,75.5 L68.5,75.5 L68.5,108.352 L109.398,108.352 C110.601,108.352 111.581,107.373 111.581,106.17 L111.581,103.128 L113.081,103.128 L113.081,106.17 C113.081,108.2 111.732416,109.434082 109.701416,109.434082 L67,109.434082 Z" id="Fill-484" fill="#0073AA"></path>
<polyline id="Fill-485" fill="#E7F7FF" points="115.976 79.993 71.396 79.993 69 75 113.58 75 115.976 79.993"></polyline>
<path d="M72.059,78.992 L114.976,78.992 L113.3,75.5 L70.383,75.5 L72.059,78.992 Z M117.361,80.492 L71.116,80.492 L68,74 L114.244,74 L117.361,80.492 L117.361,80.492 Z" id="Fill-486" fill="#0073AA"></path>
<polyline id="Fill-513" fill="#D1E8F0" points="132.7655 45.13 119.8855 45.13 117.7395 48.848 126.3255 58.614 134.9125 48.848 132.7655 45.13"></polyline>
<polyline id="Fill-514" fill="#9CC9E1" points="122.8485 48.848 120.7015 45.13 118.7395 48.875 122.8485 48.848"></polyline>
<polyline id="Fill-515" fill="#9CC9E1" points="126.8965 48.733 124.8155 45.13 122.7395 48.727 126.8965 48.733"></polyline>
<polyline id="Fill-516" fill="#9CC9E1" points="130.8655 48.721 128.6355 45.13 126.7395 48.748 130.8655 48.721"></polyline>
<polyline id="Fill-517" fill="#9CC9E1" points="126.7395 49.13 126.7395 58.246 130.9345 49.13 126.7395 49.13"></polyline>
<polyline id="Fill-518" fill="#62A6CF" points="118.7395 49.13 118.7395 49.221 126.9455 58.451 122.6585 49.13 118.7395 49.13"></polyline>
<polyline id="Fill-519" fill="#62A6CF" points="134.9465 49.13 134.9465 49.221 126.7395 58.451 131.0275 49.13 134.9465 49.13"></polyline>
<polyline id="Fill-520" fill="#0073AA" points="126.2435 59.5 116.7395 48.69 119.3715 44.13 129.2805 44.13 129.2805 45.63 120.2375 45.63 118.5765 48.507 126.2435 57.229 133.9115 48.507 132.2515 45.63 131.4435 45.63 131.4435 44.13 133.1165 44.13 135.7495 48.69 126.2435 59.5"></polyline>
<polygon id="Fill-521" fill="#0073AA" points="129.7395 45.63 131.1395 45.63 131.1395 44.13 129.7395 44.13"></polygon>
<polygon id="Fill-522" fill="#0073AA" points="117.7395 49.63 134.9125 49.63 134.9125 48.13 117.7395 48.13"></polygon>
<polyline id="Fill-523" fill="#0073AA" points="126.0315 58.5 121.7395 48.734 123.1125 48.13 127.4045 57.897 126.0315 58.5"></polyline>
<polyline id="Fill-524" fill="#0073AA" points="127.1125 58.5 125.7395 57.896 130.0315 48.13 131.4045 48.734 127.1125 58.5"></polyline>
<polygon id="Fill-525" fill="#0073AA" points="125.7395 58.673 127.2395 58.673 127.2395 49.13 125.7395 49.13"></polygon>
<polyline id="Fill-526" fill="#0073AA" points="121.8865 49.598 119.7395 45.88 121.0375 45.13 123.1855 48.848 121.8865 49.598"></polyline>
<polyline id="Fill-527" fill="#0073AA" points="125.8865 49.598 123.7395 45.88 125.0375 45.13 127.1855 48.848 125.8865 49.598"></polyline>
<polyline id="Fill-528" fill="#0073AA" points="129.8875 49.598 127.7395 45.88 129.0385 45.13 131.1865 48.848 129.8875 49.598"></polyline>
<polyline id="Fill-529" fill="#0073AA" points="132.0385 49.598 130.7395 48.848 132.8865 45.13 134.1855 45.88 132.0385 49.598"></polyline>
<polyline id="Fill-530" fill="#0073AA" points="127.0385 49.598 125.7395 48.848 127.8865 45.13 129.1855 45.88 127.0385 49.598"></polyline>
<polyline id="Fill-531" fill="#0073AA" points="123.0385 49.598 121.7395 48.848 123.8875 45.13 125.1855 45.88 123.0385 49.598"></polyline>
<path d="M88.6770083,14.236 C87.2610083,14.236 86.1140083,13.09 86.1140083,11.672 C86.1140083,10.257 87.2610083,9.114 88.6770083,9.114 C90.0920083,9.114 91.2380083,10.257 91.2380083,11.672 C91.2380083,13.09 90.0920083,14.236 88.6770083,14.236 Z M95.5460083,10.412 L94.2980083,10.407 C94.1500083,9.758 93.8950083,9.151 93.5510083,8.607 L94.4340083,7.723 C94.7510083,7.409 94.7530083,6.897 94.4370083,6.582 L93.7760083,5.921 C93.4610083,5.606 92.9500083,5.604 92.6350083,5.918 L91.7500083,6.802 C91.2060083,6.458 90.5990083,6.203 89.9530083,6.056 L89.9530083,4.807 C89.9530083,4.362 89.5900083,4 89.1430083,4 L88.2100083,4 C87.7630083,4 87.4020083,4.362 87.4020083,4.807 L87.4020083,6.056 C86.7640083,6.199 86.1680083,6.451 85.6300083,6.785 L84.7510083,5.898 C84.4370083,5.583 83.9250083,5.576 83.6090083,5.893 L82.9440083,6.55 C82.6290083,6.862 82.6260083,7.375 82.9390083,7.691 L83.8160083,8.58 C83.4700083,9.121 83.2120083,9.728 83.0630083,10.375 L81.8150083,10.37 C81.3680083,10.366 81.0060083,10.728 81.0060083,11.176 L81.0000083,12.11 C80.9980083,12.554 81.3580083,12.918 81.8040083,12.921 L83.0500083,12.925 C83.1970083,13.576 83.4490083,14.181 83.7930083,14.724 L82.9040083,15.609 C82.5900083,15.921 82.5880083,16.432 82.9030083,16.749 L83.5590083,17.411 C83.8750083,17.73 84.3870083,17.73 84.7010083,17.416 L85.5880083,16.537 C86.1320083,16.881 86.7360083,17.141 87.3870083,17.29 L87.3820083,18.535 C87.3800083,18.981 87.7410083,19.346 88.1870083,19.346 L89.1210083,19.349 C89.5670083,19.349 89.9300083,18.991 89.9300083,18.546 L89.9340083,17.297 C90.5830083,17.151 91.1900083,16.895 91.7360083,16.553 L92.6170083,17.44 C92.9310083,17.755 93.4440083,17.755 93.7620083,17.44 L94.4210083,16.781 C94.7370083,16.468 94.7390083,15.955 94.4210083,15.642 L93.5450083,14.755 C93.8890083,14.21 94.1460083,13.606 94.2930083,12.955 L95.5400083,12.959 C95.9870083,12.959 96.3470083,12.598 96.3490083,12.153 L96.3490083,11.216 C96.3490083,10.773 95.9900083,10.412 95.5460083,10.412 L95.5460083,10.412 Z" id="Fill-532" fill="#FFFFFF"></path>
<path d="M88.4280386,9.614 C87.4270386,9.614 86.6130386,10.425 86.6130386,11.422 C86.6130386,12.422 87.4270386,13.236 88.4280386,13.236 C89.4260386,13.236 90.2370386,12.422 90.2370386,11.422 C90.2370386,10.425 89.4260386,9.614 88.4280386,9.614 Z M88.4280386,14.736 C86.6010386,14.736 85.1130386,13.249 85.1130386,11.422 C85.1130386,9.598 86.6010386,8.114 88.4280386,8.114 C90.2530386,8.114 91.7370386,9.598 91.7370386,11.422 C91.7370386,13.249 90.2530386,14.736 88.4280386,14.736 Z M85.2350386,15.333 L85.7390386,15.653 C86.2280386,15.962 86.7540386,16.183 87.3040386,16.309 L87.8890386,16.442 L87.8820386,18.288 L88.8740386,18.349 L88.9360386,16.446 L89.5200386,16.315 C90.0730386,16.191 90.6010386,15.973 91.0870386,15.668 L91.5950386,15.349 L92.8990386,16.662 L93.6400386,16.002 L92.3430386,14.609 L92.6600386,14.106 C92.9680386,13.618 93.1870386,13.091 93.3120386,12.539 L93.4440386,11.953 L95.2920386,11.959 L95.3490386,10.966 L95.3470386,10.966 L95.2930386,10.912 L93.4480386,10.905 L93.3170386,10.323 C93.1910386,9.767 92.9720386,9.24 92.6670386,8.758 L92.3470386,8.251 L93.6520386,6.943 L92.9950386,6.201 L91.6040386,7.507 L91.0970386,7.186 C90.6190386,6.882 90.0940386,6.664 89.5360386,6.537 L88.9520386,6.404 L88.9520386,4.557 L87.9600386,4.5 L87.9020386,6.406 L87.3170386,6.538 C86.7780386,6.659 86.2590386,6.872 85.7740386,7.172 L85.2660386,7.486 L83.9680386,6.176 L83.2220386,6.833 L84.5210386,8.229 L84.1980386,8.733 C83.8920386,9.213 83.6720386,9.738 83.5430386,10.294 L83.4080386,10.877 L81.5630386,10.87 L81.5000386,11.865 L83.4010386,11.927 L83.5310386,12.511 C83.6580386,13.072 83.8750386,13.597 84.1760386,14.073 L84.4970386,14.581 L83.1840386,15.89 L83.8410386,16.633 L85.2350386,15.333 Z M88.8710386,19.849 L87.9340386,19.845 C87.5210386,19.845 87.1300386,19.683 86.8360386,19.387 C86.5410386,19.091 86.3800386,18.699 86.3820386,18.282 L86.3850386,17.607 C86.0690386,17.502 85.7600386,17.374 85.4610386,17.22 L84.9800386,17.698 C84.6890386,17.989 84.2980386,18.151 83.8830386,18.151 L83.8800386,18.151 C83.4620386,18.15 83.0700386,17.986 82.7760386,17.688 L82.1200386,17.026 C81.5160386,16.417 81.5180386,15.431 82.1260386,14.827 L82.6080386,14.346 C82.4580386,14.05 82.3310386,13.742 82.2280386,13.423 L81.5520386,13.421 C80.6890386,13.414 79.9940386,12.711 80.0000386,11.856 L80.0060386,10.921 C80.0060386,10.507 80.1700386,10.114 80.4670386,9.819 C80.7640386,9.525 81.1360386,9.404 81.5720386,9.37 L82.2450386,9.373 C82.3510386,9.056 82.4800386,8.748 82.6330386,8.451 L82.1550386,7.968 C81.8630386,7.674 81.7040386,7.283 81.7060386,6.866 C81.7070386,6.45 81.8710386,6.059 82.1670386,5.767 L82.8320386,5.109 C83.4110386,4.528 84.4430386,4.528 85.0320386,5.119 L85.5110386,5.603 C85.8010386,5.458 86.0980386,5.335 86.4020386,5.235 L86.4020386,4.557 C86.4020386,3.698 87.1010386,3 87.9600386,3 L88.8930386,3 C89.7530386,3 90.4520386,3.698 90.4520386,4.557 L90.4520386,5.236 C90.7700386,5.339 91.0770386,5.467 91.3730386,5.618 L91.8560386,5.138 C92.1480386,4.845 92.5370386,4.684 92.9530386,4.684 C93.3870386,4.639 93.7620386,4.846 94.0560386,5.14 L94.7170386,5.802 C95.0120386,6.096 95.1730386,6.487 95.1720386,6.904 C95.1720386,7.321 95.0080386,7.712 94.7120386,8.006 L94.2350386,8.484 C94.3870386,8.782 94.5150386,9.091 94.6180386,9.41 L95.2960386,9.412 C96.1520386,9.413 96.8490386,10.109 96.8490386,10.966 L96.8490386,11.902 C96.8450386,12.762 96.1460386,13.459 95.2900386,13.459 L94.6120386,13.457 C94.5080386,13.774 94.3800386,14.083 94.2280386,14.381 L94.7040386,14.864 C94.9940386,15.149 95.1580386,15.541 95.1590386,15.958 C95.1590386,16.377 94.9950386,16.77 94.6970386,17.065 L94.0420386,17.72 C93.4280386,18.327 92.4410386,18.324 91.8360386,17.721 L91.3560386,17.237 C91.0580386,17.388 90.7490386,17.516 90.4320386,17.619 L90.4300386,18.299 C90.4300386,19.152 89.7310386,19.849 88.8710386,19.849 L88.8710386,19.849 Z" id="Fill-533" fill="#0073AA"></path>
<path d="M38.749,102.498 C32.822,102.498 28,97.676 28,91.749 C28,85.821 32.822,81 38.749,81 C42.75,81 46.394,83.199 48.261,86.739 L46.935,87.439 C45.328,84.392 42.191,82.5 38.749,82.5 C33.649,82.5 29.5,86.649 29.5,91.749 C29.5,96.849 33.649,100.998 38.749,100.998 L38.749,102.498" id="Fill-534" fill="#0073AA"></path>
<path d="M24.5,99.523 L23,99.523 C23,97.89 23.501,96.328 24.45,95.005 C25.519,93.516 27.08,92.448 28.848,92 L29.217,93.454 C27.791,93.815 26.531,94.677 25.669,95.879 C24.905,96.946 24.5,98.205 24.5,99.523" id="Fill-535" fill="#0073AA"></path>
<path d="M48.5,102.521 L47,102.521 C47,100.889 47.501,99.327 48.45,98.005 C49.518,96.515 51.079,95.448 52.848,95 L53.217,96.454 C51.79,96.815 50.531,97.676 49.669,98.879 C48.904,99.945 48.5,101.205 48.5,102.521" id="Fill-536" fill="#0073AA"></path>
<path d="M17.63,109.76 C12.14,109.76 8,105.835 8,100.63 C8,95.32 12.32,91 17.63,91 C20.88,91 23.89,92.623 25.679,95.342 L24.427,96.167 C22.915,93.871 20.374,92.5 17.63,92.5 C13.147,92.5 9.5,96.148 9.5,100.63 C9.5,104.98 12.995,108.26 17.63,108.26 L17.63,109.76" id="Fill-537" fill="#0073AA"></path>
<path d="M137.825,93.872 C137.12,87.959 132.091,83.5 126.126,83.5 C122.134,83.5 118.444,85.498 116.256,88.846 L115,88.025 C117.466,84.252 121.625,82 126.126,82 C132.851,82 138.52,87.027 139.315,93.695 L137.825,93.872" id="Fill-538" fill="#0073AA"></path>
<path d="M135.5,100.593 L134,100.593 C134,95.854 137.855,92 142.593,92 L142.593,93.5 C138.682,93.5 135.5,96.682 135.5,100.593" id="Fill-539" fill="#0073AA"></path>
<path d="M154.428955,108.394775 C161.000955,108.394775 167.34,101.993 167.34,95.42 C167.34,88.847 161.992,83.5 155.42,83.5 C149.87,83.5 145.101,87.266 143.821,92.658 C143.609,93.556 143.5,94.485 143.5,95.42 L142,95.42 C142,94.369 142.122,93.323 142.362,92.311 C143.803,86.24 149.172,82 155.42,82 C162.82,82 168.84,88.02 168.84,95.42 C168.84,102.82 162.82,109.290283 155.42,109.290283 L154.428955,108.394775 Z" id="Fill-540" fill="#0073AA"></path>
<path d="M118.996,102.234 C118.996,106.229 113.848,109.468 107.498,109.468 C101.148,109.468 96,106.229 96,102.234 C96,98.239 101.148,95 107.498,95 C113.848,95 118.996,98.239 118.996,102.234" id="Fill-541" fill="#FFFFFF"></path>
<path d="M118.468,99.233 C118.468,103.229 115.23,106.467 111.234,106.467 C107.239,106.467 104,103.229 104,99.233 C104,95.238 107.239,92 111.234,92 C115.23,92 118.468,95.238 118.468,99.233" id="Fill-542" fill="#FFFFFF"></path>
<path d="M108.565,100.283 C108.565,105.409 104.409,109.565 99.283,109.565 C94.156,109.565 90,105.409 90,100.283 C90,95.156 94.156,91 99.283,91 C104.409,91 108.565,95.156 108.565,100.283" id="Fill-543" fill="#FFFFFF"></path>
<path d="M105.5,98.985 L104,98.985 C104,94.582 107.581,91 111.983,91 C113.608,91 115.172,91.486 116.507,92.405 L115.656,93.64 C114.573,92.894 113.303,92.5 111.983,92.5 C108.408,92.5 105.5,95.409 105.5,98.985" id="Fill-544" fill="#0073AA"></path>
<path d="M154.109,103.905 L152.927,102.981 C153.429,102.339 153.72,101.568 153.769,100.751 C153.903,98.539 152.211,96.631 150,96.497 L150.09,95 C153.127,95.183 155.449,97.804 155.267,100.841 C155.198,101.963 154.798,103.022 154.109,103.905" id="Fill-545" fill="#0073AA"></path>
<path d="M89,100.033 C89,94.5 93.501,90 99.033,90 C102.414,90 105.546,91.686 107.41,94.511 L106.158,95.337 C104.572,92.934 101.909,91.5 99.033,91.5 C94.328,91.5 90.5,95.328 90.5,100.033 C90.5,104.738 92.9743652,108.234863 98.5073242,108.234863 L99.2976074,109.354492 C93.7656074,109.354492 89,105.565 89,100.033 Z" id="Fill-546" fill="#0073AA"></path>
<polygon id="Fill-547" points="0 109.5 2.739 109.5 2.739 108 0 108"></polygon>
<polygon id="Fill-548" fill="#0073AA" points="168 109.5 174.049 109.5 174.049 108 168 108"></polygon>
<polygon id="Fill-549" fill="#0073AA" points="3 109.842041 166.823 109.5 166.823 108 3 108.284424"></polygon>
<polygon id="Path" fill="#0073AA" points="69 49.5 70.398 49.5 70.398 48 69 48"></polygon>
<polygon id="Path" fill="#0073AA" points="73 49.5 74.399 49.5 74.399 48 73 48"></polygon>
<polygon id="Path" fill="#0073AA" points="77 49.5 78.398 49.5 78.398 48 77 48"></polygon>
<path d="M113.541,26.747 L112.949,28.113 L114.125,28.113 L113.541,26.747 Z M114.96,30.049 L114.522,29.034 L112.552,29.034 L112.115,30.049 L111,30.049 L113.03,25.349 L114.044,25.349 L116.076,30.049 L114.96,30.049 L114.96,30.049 Z" id="Fill-375" fill="#0073AA"></path>
<path d="M118.049,29.161 L118.768,29.161 C119.068,29.161 119.294,29.124 119.444,29.051 C119.594,28.977 119.669,28.831 119.669,28.614 C119.669,28.396 119.59,28.253 119.43,28.183 C119.271,28.114 119.017,28.079 118.668,28.079 L118.049,28.079 L118.049,29.161 Z M118.049,27.252 L118.547,27.252 C118.838,27.252 119.053,27.221 119.195,27.158 C119.337,27.095 119.407,26.959 119.407,26.751 C119.407,26.543 119.341,26.405 119.212,26.338 C119.082,26.271 118.862,26.237 118.553,26.237 L118.049,26.237 L118.049,27.252 Z M119.057,30.049 L117,30.049 L117,25.349 L118.829,25.349 C119.147,25.349 119.422,25.387 119.652,25.463 C119.884,25.54 120.056,25.643 120.174,25.773 C120.384,26.015 120.489,26.288 120.489,26.593 C120.489,26.961 120.371,27.235 120.133,27.413 L119.965,27.532 L119.797,27.609 C120.088,27.671 120.32,27.802 120.493,28.002 C120.665,28.202 120.752,28.449 120.752,28.745 C120.752,29.072 120.639,29.361 120.416,29.612 C120.155,29.904 119.703,30.049 119.057,30.049 L119.057,30.049 Z" id="Fill-376" fill="#0073AA"></path>
<path d="M123.441,29.228 C123.956,29.228 124.377,29.024 124.705,28.616 L125.377,29.309 C124.844,29.91 124.215,30.21 123.491,30.21 C122.767,30.21 122.171,29.981 121.702,29.524 C121.235,29.067 121,28.49 121,27.793 C121,27.096 121.238,26.515 121.716,26.048 C122.194,25.582 122.778,25.349 123.468,25.349 C124.238,25.349 124.884,25.643 125.405,26.229 L124.752,26.97 C124.42,26.557 124.008,26.351 123.515,26.351 C123.12,26.351 122.784,26.479 122.503,26.737 C122.223,26.995 122.082,27.343 122.082,27.779 C122.082,28.217 122.215,28.567 122.48,28.832 C122.743,29.097 123.065,29.228 123.441,29.228" id="Fill-377" fill="#0073AA"></path>
<polygon id="Fill-378" fill="#0073AA" points="98 25.849 107.862 25.849 107.862 24.349 98 24.349"></polygon>
<polygon id="Fill-379" fill="#0073AA" points="94 28.849 108.16 28.849 108.16 27.349 94 27.349"></polygon>
<polygon id="Fill-380" fill="#0073AA" points="90 31.849 107.974 31.849 107.974 30.349 90 30.349"></polygon>
<g id="Fill-455" transform="translate(31.435000, 49.500000)">
<path d="M21.582,12.02 C21.582,12.019 21.582,12.016 21.582,12.014 C21.582,12.016 21.582,12.018 21.582,12.02 M21.582,11.98 C21.582,11.978 21.581,11.976 21.581,11.973 C21.581,11.976 21.582,11.978 21.582,11.98 M21.581,11.895 C21.581,11.892 21.581,11.889 21.581,11.886 C21.581,11.889 21.581,11.892 21.581,11.895 M21.581,11.856 C21.581,11.853 21.581,11.848 21.581,11.844 C21.581,11.849 21.581,11.852 21.581,11.856 M21.58,11.817 C21.58,11.813 21.58,11.81 21.58,11.806 C21.58,11.81 21.58,11.813 21.58,11.817 M21.58,11.767 C21.58,11.757 21.58,11.747 21.58,11.737 C21.58,11.747 21.58,11.757 21.58,11.767" fill="#98C7E1"></path>
<path d="M31.179,28.396 L31.248,28.291 C31.253,28.295 31.259,28.298 31.265,28.302 C31.236,28.333 31.208,28.365 31.179,28.396" id="Fill-459" fill="#0073AA"></path>
<path d="M35.184,9.973 L32.05,16.551 C32.343,18.381 32.275,20.299 31.764,22.206 C29.821,29.455 22.37,33.756 15.122,31.813 C7.873,29.87 3.572,22.419 5.515,15.17 C7.458,7.922 14.909,3.621 22.157,5.564 C24.717,6.25 26.898,7.632 28.577,9.441 L35.184,9.973" id="Fill-460" fill="#8AC4E6"></path>
<path d="M18.619,5.845 C16.394,5.845 14.2,6.428 12.221,7.57 C9.252,9.285 7.128,12.053 6.24,15.365 C5.352,18.677 5.807,22.136 7.521,25.106 C9.236,28.076 12.004,30.2 15.316,31.088 C18.629,31.977 22.088,31.521 25.057,29.807 C28.027,28.092 30.151,25.324 31.039,22.012 C31.507,20.268 31.598,18.471 31.31,16.669 L31.273,16.439 L34.039,10.633 L28.227,10.165 L28.028,9.951 C26.381,8.177 24.283,6.91 21.963,6.288 C20.857,5.992 19.734,5.845 18.619,5.845 Z M18.663,33.032 C17.417,33.032 16.163,32.868 14.927,32.537 C11.228,31.545 8.136,29.173 6.222,25.856 C4.307,22.539 3.799,18.675 4.791,14.976 C5.782,11.277 8.155,8.185 11.471,6.271 C14.788,4.356 18.65,3.848 22.352,4.839 C24.85,5.509 27.116,6.847 28.924,8.716 L36.33,9.313 L32.827,16.666 C33.104,18.599 32.991,20.527 32.488,22.401 C31.497,26.1 29.124,29.191 25.807,31.105 C23.598,32.381 21.147,33.032 18.663,33.032 L18.663,33.032 Z" id="Fill-461" fill="#0073AA"></path>
<path d="M27.49,21.061 C28.76,16.322 25.857,11.426 21.006,10.125 C16.154,8.825 11.191,11.612 9.921,16.351 C8.651,21.09 11.554,25.986 16.405,27.287 C21.257,28.587 26.22,25.8 27.49,21.061" id="Fill-462" fill="#FFFFFF"></path>
<polyline id="Fill-463" fill="#0073AA" points="16.535 24.278 11.858 19.602 14.686 16.773 16.535 18.622 17.4762865 17.6807135 22.046 13.111 24.874 15.939 16.535 24.278 20.3554688 19.3138021"></polyline>
<polygon id="Fill-464" fill="#0073AA" points="18.243 3.306 19.743 3.306 19.743 0 18.243 0"></polygon>
<polyline id="Fill-465" fill="#0073AA" points="2.721 28.434 1.973 27.133 4.84 25.485 5.588 26.786 2.721 28.434"></polyline>
<polyline id="Fill-466" fill="#0073AA" points="0.749 23.964 0.362 22.515 3.556 21.664 3.943 23.113 0.749 23.964"></polyline>
<polyline id="Fill-467" fill="#0073AA" points="3.305 19.141 0 19.135 0.002 17.635 3.307 17.641 3.305 19.141"></polyline>
<polyline id="Fill-468" fill="#0073AA" points="3.717 15.139 0.526 14.277 0.917 12.829 4.108 13.69 3.717 15.139"></polyline>
<polyline id="Fill-469" fill="#0073AA" points="5.152 11.38 2.293 9.721 3.045 8.423 5.904 10.082 5.152 11.38"></polyline>
<polyline id="Fill-470" fill="#0073AA" points="7.511 8.121 5.178 5.778 6.241 4.719 8.574 7.062 7.511 8.121"></polyline>
<polyline id="Fill-471" fill="#0073AA" points="10.633 5.583 8.987 2.716 10.288 1.969 11.934 4.836 10.633 5.583"></polyline>
<polyline id="Fill-472" fill="#0073AA" points="14.307 3.94 13.459 0.745 14.908 0.36 15.756 3.556 14.307 3.94"></polyline>
</g>
<path d="M44.98,20.5 C40.855,20.5 37.5,23.855 37.5,27.979 C37.5,32.103 40.855,35.459 44.98,35.459 C49.104,35.459 52.46,32.103 52.46,27.979 C52.46,23.855 49.104,20.5 44.98,20.5 Z M44.98,36.959 C40.028,36.959 36,32.931 36,27.979 C36,23.028 40.028,19 44.98,19 C49.932,19 53.96,23.028 53.96,27.979 C53.96,32.931 49.932,36.959 44.98,36.959 L44.98,36.959 Z" id="Fill-699" fill="#0073AA"></path>
<path d="M48.272,36.215 L47,35.42 C48.243,33.432 48.928,30.825 48.928,28.078 C48.928,25.355 48.255,22.766 47.034,20.788 L48.31,20 C49.676,22.211 50.428,25.08 50.428,28.078 C50.428,31.102 49.662,33.992 48.272,36.215" id="Fill-700" fill="#0073AA"></path>
<path d="M42.158,36.216 C40.767,33.992 40,31.102 40,28.078 C40,25.084 40.753,22.215 42.119,20 L43.396,20.788 C42.174,22.769 41.5,25.358 41.5,28.078 C41.5,30.824 42.186,33.431 43.43,35.42 L42.158,36.216" id="Fill-701" fill="#0073AA"></path>
<polygon id="Fill-702" fill="#0073AA" points="44 36.459 45.5 36.459 45.5 20 44 20"></polygon>
<polygon id="Fill-703" fill="#0073AA" points="37 28.5 53.46 28.5 53.46 27 37 27"></polygon>
<polygon id="Fill-704" fill="#0073AA" points="38 24.5 52.212 24.5 52.212 23 38 23"></polygon>
<polygon id="Fill-705" fill="#0073AA" points="38 32.5 52.34 32.5 52.34 31 38 31"></polygon>
</g>
</symbol>
<symbol id="icon-child" width="199px" height="100px" viewBox="0 0 199 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M162.238416,41.0256956 L161.782416,38.7086956 C161.569416,37.0636956 160.061416,35.9026956 158.416416,36.1156956 L123.618416,40.6336956 C121.972416,40.8476956 120.811416,42.3546956 121.025416,43.9996956 L127.394416,93.0586956 L166.852416,80.9746956 L162.238416,41.0256956" id="Fill-261" fill="#FFFFFF"></path>
<path d="M161.071,41.9176994 L160.615,39.6016994 C160.53,38.9556994 160.224,38.4246994 159.748,38.0576994 C159.27,37.6906994 158.681,37.5276994 158.082,37.6086994 L134.193,40.7096994 L134,39.2216994 L157.888,36.1196994 C159.944,35.8646994 161.828,37.3076994 162.095,39.3606994 L162.543,41.6286994 L161.071,41.9176994" id="Fill-262" fill="#0073AA"></path>
<path d="M131.89,98.5695 L169.208,98.5695 C170.252,98.5695 171.099,97.7225 171.099,96.6785 L171.099,44.9815 C171.099,43.9375 170.252,43.0905 169.208,43.0905 L131.89,43.0905 C130.846,43.0905 130,43.9375 130,44.9815 L130,96.6785 C130,97.7225 130.846,98.5695 131.89,98.5695" id="Fill-263" fill="#8AC4E6"></path>
<path d="M167.689,99.0695 L167.689,97.5695 C169.105,97.5695 170.256,96.4165 170.256,95.0005 L170.256,46.1595 C170.256,44.7435 169.105,43.5905 167.689,43.5905 L142,43.5905 L142,42.0905 L167.689,42.0905 C169.932,42.0905 171.756,43.9165 171.756,46.1595 L171.756,95.0005 C171.756,97.2445 169.932,99.0695 167.689,99.0695" id="Fill-264" fill="#0073AA"></path>
<polygon id="Fill-265" fill="#0073AA" points="168 49.5905 175.601 49.5905 175.601 48.0905 168 48.0905"></polygon>
<polygon id="Fill-266" fill="#0073AA" points="168 55.5905 175.601 55.5905 175.601 54.0905 168 54.0905"></polygon>
<polygon id="Fill-267" fill="#0073AA" points="168 60.5905 175.601 60.5905 175.601 59.0905 168 59.0905"></polygon>
<polygon id="Fill-268" fill="#0073AA" points="168 65.5905 175.601 65.5905 175.601 64.0905 168 64.0905"></polygon>
<polygon id="Fill-269" fill="#0073AA" points="168 71.5905 175.601 71.5905 175.601 70.0905 168 70.0905"></polygon>
<polygon id="Fill-270" fill="#0073AA" points="168 76.5905 175.601 76.5905 175.601 75.0905 168 75.0905"></polygon>
<polygon id="Fill-271" fill="#0073AA" points="168 82.5905 175.391 82.5905 175.391 81.0905 168 81.0905"></polygon>
<polygon id="Fill-272" fill="#0073AA" points="168 87.5905 175.391 87.5905 175.391 86.0905 168 86.0905"></polygon>
<polygon id="Fill-273" fill="#0073AA" points="168 92.5905 175.391 92.5905 175.391 91.0905 168 91.0905"></polygon>
<path d="M142,65.0595 C142,70.0125 146.016,74.0285 150.969,74.0285 C155.922,74.0285 159.938,70.0125 159.938,65.0595 C159.938,60.1065 155.922,56.0905 150.969,56.0905 C146.016,56.0905 142,60.1065 142,65.0595" id="Fill-274" fill="#FFFFFF"></path>
<path d="M150.718,56.5905 C146.186,56.5905 142.5,60.2775 142.5,64.8095 C142.5,69.3415 146.186,73.0285 150.718,73.0285 C155.25,73.0285 158.937,69.3415 158.937,64.8095 C158.937,60.2775 155.25,56.5905 150.718,56.5905 Z M150.718,74.5285 C145.359,74.5285 141,70.1695 141,64.8095 C141,59.4505 145.359,55.0905 150.718,55.0905 C156.078,55.0905 160.437,59.4505 160.437,64.8095 C160.437,70.1695 156.078,74.5285 150.718,74.5285 L150.718,74.5285 Z" id="Fill-275" fill="#0073AA"></path>
<path d="M43.984,22.8825 L43.984,97.5515 C43.984,97.9885 43.63,98.3435 43.193,98.3435 L32.792,98.3435 C32.354,98.3435 32,97.9885 32,97.5515 L32,22.8825 C32,22.4455 32.354,22.0905 32.792,22.0905 L43.193,22.0905 C43.63,22.0905 43.984,22.4455 43.984,22.8825" id="Fill-276" fill="#8AC4E6"></path>
<path d="M32.687,98.3435 L36.932,98.3435 L36.932,22.0905 L32.687,22.0905 C32.308,22.0905 32,22.4355 32,22.8605 L32,97.5745 C32,97.9995 32.308,98.3435 32.687,98.3435" id="Fill-277" fill="#E7F7FF"></path>
<path d="M32.5,98.2565 L31,98.2565 L31,23.6765 C31,22.8025 31.712,22.0905 32.586,22.0905 L42.898,22.0905 C43.773,22.0905 44.485,22.8025 44.485,23.6765 L44.485,29.9295 L42.985,29.9295 L42.985,23.6765 L42.898,23.5905 L32.586,23.5905 L32.5,23.6765 L32.5,98.2565" id="Fill-278" fill="#0073AA"></path>
<polygon id="Fill-279" fill="#0073AA" points="32 93.5905 37.519 93.5905 37.519 92.0905 32 92.0905"></polygon>
<polygon id="Fill-280" fill="#0073AA" points="32 85.5905 37.519 85.5905 37.519 84.0905 32 84.0905"></polygon>
<polygon id="Fill-281" fill="#0073AA" points="32 77.5905 37.519 77.5905 37.519 76.0905 32 76.0905"></polygon>
<polygon id="Fill-282" fill="#0073AA" points="32 69.5905 37.519 69.5905 37.519 68.0905 32 68.0905"></polygon>
<polygon id="Fill-283" fill="#0073AA" points="32 61.5905 37.519 61.5905 37.519 60.0905 32 60.0905"></polygon>
<polygon id="Fill-284" fill="#0073AA" points="32 53.5905 37.519 53.5905 37.519 52.0905 32 52.0905"></polygon>
<polygon id="Fill-285" fill="#0073AA" points="32 45.5905 37.519 45.5905 37.519 44.0905 32 44.0905"></polygon>
<polygon id="Fill-286" fill="#0073AA" points="32 37.5905 37.519 37.5905 37.519 36.0905 32 36.0905"></polygon>
<polygon id="Fill-287" fill="#0073AA" points="32 29.5905 37.519 29.5905 37.519 28.0905 32 28.0905"></polygon>
<path d="M183.024,39.5905 L178.75,39.5905 C178.336,39.5905 178,39.2545 178,38.8405 C178,38.4265 178.336,38.0905 178.75,38.0905 L183.024,38.0905 C183.438,38.0905 183.774,38.4265 183.774,38.8405 C183.774,39.2545 183.438,39.5905 183.024,39.5905" id="Fill-288" fill="#0073AA"></path>
<path d="M180.75,41.8645 C180.336,41.8645 180,41.5285 180,41.1145 L180,36.8405 C180,36.4255 180.336,36.0905 180.75,36.0905 C181.164,36.0905 181.5,36.4255 181.5,36.8405 L181.5,41.1145 C181.5,41.5285 181.164,41.8645 180.75,41.8645" id="Fill-289" fill="#0073AA"></path>
<path d="M77.024,20.5905 L72.75,20.5905 C72.336,20.5905 72,20.2545 72,19.8405 C72,19.4265 72.336,19.0905 72.75,19.0905 L77.024,19.0905 C77.438,19.0905 77.774,19.4265 77.774,19.8405 C77.774,20.2545 77.438,20.5905 77.024,20.5905" id="Fill-290" fill="#0073AA"></path>
<path d="M74.75,21.8645 C74.336,21.8645 74,21.5285 74,21.1145 L74,16.8405 C74,16.4265 74.336,16.0905 74.75,16.0905 C75.164,16.0905 75.5,16.4265 75.5,16.8405 L75.5,21.1145 C75.5,21.5285 75.164,21.8645 74.75,21.8645" id="Fill-291" fill="#0073AA"></path>
<path d="M136.74975,9.61225064 C136.55775,9.61225064 136.36575,9.53925064 136.21975,9.39225064 C135.92675,9.09925064 135.92675,8.62525064 136.21975,8.33225064 L139.24175,5.31025064 C139.53575,5.01625064 140.01075,5.01825064 140.30275,5.31025064 C140.59575,5.60325064 140.59575,6.07825064 140.30275,6.37125064 L137.28075,9.39225064 C137.13375,9.53925064 136.94275,9.61225064 136.74975,9.61225064" id="Fill-292" fill="#0073AA"></path>
<path d="M139.77275,9.61225 C139.57975,9.61225 139.38875,9.53925 139.24175,9.39225 L136.21975,6.37125 C135.92675,6.07825 135.92675,5.60325 136.21975,5.31025 C136.51275,5.01725 136.98775,5.01725 137.28075,5.31025 L140.30275,8.33225 C140.59575,8.62525 140.59575,9.09925 140.30275,9.39225 C140.15575,9.53925 139.96475,9.61225 139.77275,9.61225" id="Fill-293" fill="#0073AA"></path>
<path d="M1.74975,35.61325 C1.55775,35.61325 1.36575,35.54025 1.21975,35.39325 C0.92675,35.10025 0.92675,34.62625 1.21975,34.33325 L4.24175,31.31025 C4.53475,31.01725 5.00975,31.01725 5.30275,31.31025 C5.59575,31.60325 5.59575,32.07825 5.30275,32.37125 L2.27975,35.39325 C2.13375,35.54025 1.94275,35.61325 1.74975,35.61325" id="Fill-294" fill="#0073AA"></path>
<path d="M4.77275,35.61325 C4.57975,35.61325 4.38875,35.54025 4.24175,35.39325 L1.21975,32.37125 C0.92675,32.07825 0.92675,31.60325 1.21975,31.31025 C1.51275,31.01725 1.98675,31.01725 2.27975,31.31025 L5.30275,34.33325 C5.59575,34.62625 5.59575,35.10025 5.30275,35.39325 C5.15575,35.54025 4.96475,35.61325 4.77275,35.61325" id="Fill-295" fill="#0073AA"></path>
<polygon id="Fill-296" fill="#0073AA" points="60 1.5905 89.39 1.5905 89.39 0.0905 60 0.0905"></polygon>
<polygon id="Fill-297" fill="#0073AA" points="112 23.5905 141.39 23.5905 141.39 22.0905 112 22.0905"></polygon>
<polygon id="Fill-298" fill="#0073AA" points="76 7.5905 130.342 7.5905 130.342 6.0905 76 6.0905"></polygon>
<polygon id="Fill-299" fill="#0073AA" points="0.557 45.5905 16.48 45.5905 16.48 44.0905 0.557 44.0905"></polygon>
<polygon id="Fill-300" fill="#0073AA" points="179 46.5905 198.352 46.5905 198.352 45.0905 179 45.0905"></polygon>
<polygon id="Fill-301" fill="#0073AA" points="91 1.5905 94.57 1.5905 94.57 0.0905 91 0.0905"></polygon>
<path d="M105.693,31.4375 C105.693,32.1815 105.09,32.7845 104.347,32.7845 C103.603,32.7845 103,32.1815 103,31.4375 C103,30.6935 103.603,30.0905 104.347,30.0905 C105.09,30.0905 105.693,30.6935 105.693,31.4375" id="Fill-302" fill="#0073AA"></path>
<path d="M17.52,67.8505 C17.52,68.8215 16.732,69.6095 15.76,69.6095 C14.788,69.6095 14,68.8215 14,67.8505 C14,66.8785 14.788,66.0905 15.76,66.0905 C16.732,66.0905 17.52,66.8785 17.52,67.8505" id="Fill-303" fill="#0073AA"></path>
<path d="M39.52,14.8495 C39.52,15.8215 38.732,16.6095 37.76,16.6095 C36.788,16.6095 36,15.8215 36,14.8495 C36,13.8785 36.788,13.0905 37.76,13.0905 C38.732,13.0905 39.52,13.8785 39.52,14.8495" id="Fill-304" fill="#0073AA"></path>
<path d="M184.519,82.8505 C184.519,83.8225 183.731,84.6105 182.759,84.6105 C181.788,84.6105 181,83.8225 181,82.8505 C181,81.8785 181.788,81.0905 182.759,81.0905 C183.731,81.0905 184.519,81.8785 184.519,82.8505" id="Fill-305" fill="#0073AA"></path>
<path d="M132.248,98.2755 L131.95,45.3365 C131.95,41.3345 128.303,38.0905 123.804,38.0905 L48,38.0905 L48.298,98.3365 L132.248,98.2755" id="Fill-306" fill="#FFFFFF"></path>
<path d="M131.498,99.0295 L131.2,46.0905 C131.2,42.5045 127.882,39.5905 123.803,39.5905 L48,39.5905 L48,38.0905 L123.803,38.0905 C128.709,38.0905 132.7,41.6775 132.7,46.0865 L132.998,99.0215 L131.498,99.0295" id="Fill-307" fill="#0073AA"></path>
<path d="M124,98.4195 L132.457,98.4195 L132.457,35.9435 C132.457,32.7115 129.551,30.0905 125.966,30.0905 L124,30.0905 L124,98.4195" id="Fill-308" fill="#0073AA"></path>
<path d="M124.5,99.1685 L123,99.1685 L123,30.0905 L125.716,30.0905 C129.709,30.0905 132.957,33.3385 132.957,37.3305 L132.957,45.1685 L131.457,45.1685 L131.457,37.3305 C131.457,34.1655 128.881,31.5905 125.716,31.5905 L124.5,31.5905 L124.5,99.1685" id="Fill-309" fill="#0073AA"></path>
<path d="M50.457,98.4185 L42,98.4185 L42,35.9425 C42,32.7105 44.906,30.0905 48.49,30.0905 L50.457,30.0905 L50.457,98.4185" id="Fill-310" fill="#0073AA"></path>
<path d="M51.957,99.1685 L50.457,99.1685 L50.457,31.5905 L49.24,31.5905 C46.075,31.5905 43.5,34.1655 43.5,37.3305 L43.5,99.1685 L42,99.1685 L42,37.3305 C42,33.3385 45.248,30.0905 49.24,30.0905 L51.957,30.0905 L51.957,99.1685" id="Fill-311" fill="#0073AA"></path>
<path d="M102.63975,55.48925 C108.50475,61.35425 108.50475,70.86425 102.63975,76.73025 C96.77375,82.59525 87.26475,82.59525 81.39875,76.73025 C75.53375,70.86425 75.53375,61.35425 81.39875,55.48925 C87.26475,49.62425 96.77375,49.62425 102.63975,55.48925" id="Fill-312" fill="#E7F7FF"></path>
<path d="M86.597,59.7075 C85.994,59.6295 85.379,59.5905 84.77,59.5905 L84.733,58.0905 L84.77,58.0905 C85.443,58.0905 86.123,58.1345 86.789,58.2185 L86.597,59.7075 M81.042,60.0845 L80.652,58.6345 C81.316,58.4565 81.996,58.3205 82.674,58.2305 L82.871,59.7165 C82.258,59.7985 81.643,59.9215 81.042,60.0845 M90.198,60.6585 C89.625,60.4235 89.03,60.2235 88.427,60.0635 L88.812,58.6145 C89.477,58.7905 90.134,59.0105 90.766,59.2695 L90.198,60.6585 M77.605,61.5145 L76.851,60.2175 C77.444,59.8735 78.066,59.5645 78.7,59.3015 L79.276,60.6855 C78.703,60.9235 78.141,61.2025 77.605,61.5145 M93.423,62.5105 C92.932,62.1345 92.411,61.7875 91.871,61.4775 L92.62,60.1765 C93.214,60.5185 93.792,60.9035 94.335,61.3195 L93.423,62.5105 M74.68,63.7685 L73.595,62.7325 L73.62,62.7085 C74.094,62.2325 74.606,61.7825 75.139,61.3705 L76.057,62.5555 C75.574,62.9295 75.111,63.3385 74.68,63.7685 M96.092,65.1715 C95.717,64.6835 95.302,64.2115 94.86,63.7685 L95.889,62.6765 L95.92,62.7085 C96.409,63.1965 96.867,63.7175 97.282,64.2575 L96.092,65.1715 M72.395,66.7495 L71.094,66.0015 C71.435,65.4085 71.82,64.8315 72.239,64.2865 L73.428,65.1985 C73.05,65.6915 72.703,66.2135 72.395,66.7495 M97.955,68.3955 C97.719,67.8235 97.441,67.2595 97.13,66.7215 L98.429,65.9715 C98.773,66.5665 99.079,67.1885 99.342,67.8215 L97.955,68.3955 M70.977,70.1915 L69.528,69.8065 C69.703,69.1455 69.923,68.4875 70.185,67.8525 L71.572,68.4235 C71.336,68.9975 71.136,69.5915 70.977,70.1915 M98.918,71.9915 C98.837,71.3735 98.715,70.7585 98.555,70.1625 L100.004,69.7735 C100.181,70.4335 100.316,71.1135 100.405,71.7965 L98.918,71.9915 M69,73.8865 L69,73.8595 C69,73.1815 69.044,72.4985 69.13,71.8305 L70.619,72.0215 C70.54,72.6275 70.5,73.2455 70.5,73.8595 L69,73.8865 M100.408,75.9125 L98.919,75.7175 C98.999,75.1055 99.041,74.4805 99.041,73.8585 L100.54,73.8545 L100.54,73.8585 C100.54,74.5455 100.496,75.2355 100.408,75.9125 M69.542,77.9665 C69.365,77.3065 69.228,76.6275 69.138,75.9445 L70.624,75.7475 C70.706,76.3645 70.83,76.9805 70.991,77.5765 L69.542,77.9665 M99.347,79.8865 L97.96,79.3165 C98.198,78.7395 98.399,78.1445 98.558,77.5485 L100.007,77.9345 C99.831,78.5925 99.61,79.2505 99.347,79.8865 M71.124,81.7665 C70.778,81.1705 70.469,80.5475 70.207,79.9185 L71.592,79.3415 C71.829,79.9125 72.108,80.4745 72.42,81.0145 L71.124,81.7665 M97.289,83.4515 L96.099,82.5375 C96.48,82.0425 96.829,81.5205 97.136,80.9875 L98.435,81.7375 C98.095,82.3275 97.709,82.9035 97.289,83.4515 M73.636,85.0265 L73.62,85.0095 C73.139,84.5295 72.687,84.0155 72.274,83.4805 L73.461,82.5645 C73.835,83.0475 74.246,83.5135 74.68,83.9495 L73.636,85.0265 M94.382,86.3625 L93.466,85.1745 C93.955,84.7975 94.423,84.3855 94.86,83.9495 L95.929,85.0015 L95.92,85.0095 C95.439,85.4915 94.921,85.9465 94.382,86.3625 M76.902,87.5295 C76.307,87.1865 75.731,86.8015 75.188,86.3845 L76.1,85.1955 C76.591,85.5715 77.114,85.9205 77.652,86.2305 L76.902,87.5295 M90.818,88.4265 L90.244,87.0425 C90.817,86.8035 91.379,86.5255 91.916,86.2155 L92.668,87.5115 C92.075,87.8565 91.453,88.1645 90.818,88.4265 M80.706,89.0985 C80.04,88.9215 79.382,88.6995 78.751,88.4395 L79.324,87.0525 C79.894,87.2885 80.489,87.4885 81.092,87.6495 L80.706,89.0985 M86.843,89.4925 L86.646,88.0055 C87.262,87.9235 87.878,87.8015 88.477,87.6405 L88.866,89.0905 C88.204,89.2665 87.523,89.4025 86.843,89.4925 M84.786,89.6295 L84.77,89.6295 C84.089,89.6295 83.403,89.5845 82.73,89.4975 L82.923,88.0085 C83.533,88.0885 84.155,88.1295 84.77,88.1295 L84.786,89.6295" id="Fill-313" fill="#0073AA"></path>
<polygon id="Fill-314" fill="#0073AA" points="13 99.5905 176.821 99.5905 176.821 98.0905 13 98.0905"></polygon>
<polygon id="Fill-315" fill="#0073AA" points="8 99.5905 10.739 99.5905 10.739 98.0905 8 98.0905"></polygon>
<polygon id="Fill-316" fill="#0073AA" points="178 99.5905 184.05 99.5905 184.05 98.0905 178 98.0905"></polygon>
<path d="M123.193,97.8505 L79,97.8505 C79.009,97.4835 79.156,97.1265 79.427,96.8555 L87.587,88.6955 C88.03,88.6165 88.47,88.5185 88.902,88.4035 L88.685,87.5975 L95.741,80.5415 C96.952,80.2295 98.133,79.7635 99.25,79.1455 L99.383,79.1995 C99.406,79.1435 99.429,79.0885 99.451,79.0315 C100.578,78.3855 101.639,77.5805 102.601,76.6185 C104.569,74.6505 105.876,72.2725 106.524,69.7595 L123.193,53.0905 L123.193,66.3225 L99.522,89.9935 L123.193,89.9935 L123.193,97.8505 M91.952,85.5285 C91.415,85.8385 90.853,86.1165 90.28,86.3555 L90.854,87.7395 C91.489,87.4775 92.111,87.1695 92.704,86.8245 L91.952,85.5285 M94.896,83.2625 C94.459,83.6985 93.991,84.1105 93.502,84.4875 L94.418,85.6755 C94.957,85.2595 95.475,84.8045 95.956,84.3225 L95.965,84.3145 L94.896,83.2625 M97.172,80.3005 C96.865,80.8335 96.516,81.3555 96.135,81.8505 L97.325,82.7645 C97.745,82.2165 98.131,81.6405 98.471,81.0505 L97.172,80.3005" id="Fill-317" fill="#B4D7E7"></path>
<polyline id="Fill-318" fill="#0073AA" points="133.246 97.7965 133 97.7965 133 54.0905 133 54.0945 133.246 97.7965"></polyline>
<polyline id="Fill-319" fill="#4B97C6" points="132.707 98.3405 125 98.3405 125 90.4835 129.48 90.4835 129.48 60.8315 125 65.3125 125 52.0805 131.99 45.0905 132.707 45.0905 132.707 98.3405"></polyline>
<path d="M123,97.8075 L124.5,97.8075 L124.5,89.9505 L123,89.9505 L123,97.8075 Z M123,66.2795 L123,53.0475 L124.5,51.5475 L124.5,64.7795 L123,66.2795 L123,66.2795 Z M132.957,44.5575 L131.49,44.5575 L132.957,43.0905 L132.957,44.5575 L132.957,44.5575 Z" id="Fill-320" fill="#0073AA"></path>
<path d="M96,80.8725 L98.472,78.4005 C98.404,78.5885 98.332,78.7755 98.255,78.9605 L99.509,79.4765 C98.392,80.0945 97.211,80.5605 96,80.8725 M99.71,79.3625 C99.943,78.7785 100.142,78.1795 100.302,77.5785 L99.506,77.3665 L106.783,70.0905 C106.135,72.6035 104.828,74.9815 102.86,76.9495 C101.898,77.9115 100.837,78.7165 99.71,79.3625" id="Fill-321" fill="#93C5E0"></path>
<path d="M88,88.7505 L89.098,87.6525 L89.315,88.4585 C88.883,88.5735 88.443,88.6715 88,88.7505 M91.267,87.7945 L90.693,86.4105 C91.266,86.1715 91.828,85.8935 92.365,85.5835 L93.117,86.8795 C92.524,87.2245 91.902,87.5325 91.267,87.7945 M94.831,85.7305 L93.915,84.5425 C94.404,84.1655 94.872,83.7535 95.309,83.3175 L96.378,84.3695 L96.369,84.3775 C95.888,84.8595 95.37,85.3145 94.831,85.7305 M97.738,82.8195 L96.548,81.9055 C96.929,81.4105 97.278,80.8885 97.585,80.3555 L98.884,81.1055 C98.544,81.6955 98.158,82.2715 97.738,82.8195 M99.796,79.2545 L99.663,79.2005 L98.409,78.6845 C98.486,78.4995 98.558,78.3125 98.626,78.1245 L99.66,77.0905 L100.456,77.3025 C100.296,77.9035 100.097,78.5025 99.864,79.0865 C99.842,79.1435 99.819,79.1985 99.796,79.2545" id="Fill-322" fill="#0073AA"></path>
<path d="M132.400456,98.8405 L123.193456,98.8405 L123.193456,98.7865 L79.1894564,98.8175 L79.1124564,98.6885 C79.0314564,98.4945 78.9954564,98.2915 79.0004564,98.0905 L132.646456,98.0905 L132.650456,98.7755 L132.400456,98.7775 L132.400456,98.8405" id="Fill-323" fill="#0073AA"></path>
<path d="M99.9627403,91.2652403 L132.17474,91.2652403 L132.17474,59.0532403 L99.9627403,91.2652403 Z M140.03174,99.1222403 L82.7137403,99.1222403 C82.0787403,99.1222403 81.7617403,98.3552403 82.2097403,97.9062403 L138.81674,41.3002403 C139.26474,40.8522403 140.03174,41.1692403 140.03174,41.8042403 L140.03174,99.1222403 L140.03174,99.1222403 Z" id="Fill-324" fill="#E7F7FF"></path>
<path d="M102.522778,90.2654277 L132.173778,90.2654277 L132.173778,60.6134277 L102.522778,90.2654277 Z M133.673778,91.7654277 L98.901778,91.7654277 L133.673778,56.9924277 L133.673778,91.7654277 Z M140.031778,41.5534277 L83.489778,98.1864277 L140.031778,98.1234277 L140.031778,41.5534277 Z M141.531778,99.6224277 L83.462778,99.6224277 C82.868778,99.6224277 82.339778,99.2684277 82.112778,98.7204277 C81.884778,98.1724277 82.008778,97.5474277 82.427778,97.1274277 L139.034778,40.5204277 C139.453778,40.1004277 140.079778,39.9744277 140.627778,40.2034277 C141.176778,40.4294277 141.531778,40.9604277 141.531778,41.5534277 L141.531778,99.6224277 L141.531778,99.6224277 Z" id="Fill-325" fill="#0073AA"></path>
<polygon id="Fill-326" fill="#0073AA" points="90 98.9135 91.5 98.9135 91.5 95.0905 90 95.0905"></polygon>
<polygon id="Fill-327" fill="#0073AA" points="96 98.9135 97.5 98.9135 97.5 95.0905 96 95.0905"></polygon>
<polygon id="Fill-328" fill="#0073AA" points="103 98.9135 104.5 98.9135 104.5 95.0905 103 95.0905"></polygon>
<polygon id="Fill-329" fill="#0073AA" points="109 98.9135 110.5 98.9135 110.5 95.0905 109 95.0905"></polygon>
<polygon id="Fill-330" fill="#0073AA" points="115 98.9135 116.5 98.9135 116.5 95.0905 115 95.0905"></polygon>
<polygon id="Fill-331" fill="#0073AA" points="121 98.9135 122.5 98.9135 122.5 95.0905 121 95.0905"></polygon>
<polygon id="Fill-332" fill="#0073AA" points="128 98.9135 129.5 98.9135 129.5 95.0905 128 95.0905"></polygon>
<polygon id="Fill-333" fill="#0073AA" points="134 98.9135 135.5 98.9135 135.5 95.0905 134 95.0905"></polygon>
<polygon id="Fill-334" fill="#0073AA" points="137 92.5905 140.823 92.5905 140.823 91.0905 137 91.0905"></polygon>
<polygon id="Fill-335" fill="#0073AA" points="137 86.5905 140.823 86.5905 140.823 85.0905 137 85.0905"></polygon>
<polygon id="Fill-336" fill="#0073AA" points="137 79.5905 140.823 79.5905 140.823 78.0905 137 78.0905"></polygon>
<polygon id="Fill-337" fill="#0073AA" points="137 73.5905 140.823 73.5905 140.823 72.0905 137 72.0905"></polygon>
<polygon id="Fill-338" fill="#0073AA" points="137 67.5905 140.823 67.5905 140.823 66.0905 137 66.0905"></polygon>
<polygon id="Fill-339" fill="#0073AA" points="137 61.5905 140.823 61.5905 140.823 60.0905 137 60.0905"></polygon>
<polygon id="Fill-340" fill="#0073AA" points="137 54.5905 140.823 54.5905 140.823 53.0905 137 53.0905"></polygon>
<polygon id="Fill-341" fill="#0073AA" points="137 48.5905 140.823 48.5905 140.823 47.0905 137 47.0905"></polygon>
<path d="M22.578,98.8455 L26.813,98.8455 C27.132,98.8455 27.391,98.5865 27.391,98.2675 L27.391,37.2565 L27.338,37.0145 L25.22,32.4265 C25.013,31.9785 24.377,31.9785 24.171,32.4265 L22.053,37.0145 L22,37.2565 L22,98.2675 C22,98.5865 22.258,98.8455 22.578,98.8455" id="Fill-342" fill="#FFFFFF"></path>
<polygon id="Fill-343" fill="#62A6CF" points="22 92.8925 27.391 92.8925 27.391 39.0905 22 39.0905"></polygon>
<path d="M22.5,98.1695 L26.39,98.1695 L26.39,37.3685 L24.445,33.1535 L22.5,37.3685 L22.5,98.1695 Z M27.89,99.6695 L21,99.6695 L21,37.0385 L23.453,31.7255 C23.633,31.3345 24.014,31.0905 24.446,31.0905 C24.877,31.0905 25.258,31.3345 25.438,31.7255 L27.89,37.0385 L27.89,99.6695 L27.89,99.6695 Z" id="Fill-344" fill="#0073AA"></path>
<polygon id="Fill-345" fill="#0073AA" points="22 39.5905 27.577 39.5905 27.577 38.0905 22 38.0905"></polygon>
<polygon id="Fill-346" fill="#0073AA" points="22 93.5905 28.195 93.5905 28.195 92.0905 22 92.0905"></polygon>
</g>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 148 KiB

View File

@@ -0,0 +1,556 @@
var Merlin = (function($){
var t;
// callbacks from form button clicks.
var callbacks = {
install_child: function(btn) {
var installer = new ChildTheme();
installer.init(btn);
},
activate_license: function(btn) {
var license = new ActivateLicense();
license.init(btn);
},
install_plugins: function(btn){
var plugins = new PluginManager();
plugins.init(btn);
},
install_content: function(btn){
var content = new ContentManager();
content.init(btn);
}
};
function window_loaded(){
var
body = $('.merlin__body'),
body_loading = $('.merlin__body--loading'),
body_exiting = $('.merlin__body--exiting'),
drawer_trigger = $('#merlin__drawer-trigger'),
drawer_opening = 'merlin__drawer--opening';
drawer_opened = 'merlin__drawer--open';
setTimeout(function(){
body.addClass('loaded');
},100);
drawer_trigger.on('click', function(){
body.toggleClass( drawer_opened );
});
$('.merlin__button--proceed:not(.merlin__button--closer)').click(function (e) {
e.preventDefault();
var goTo = this.getAttribute("href");
body.addClass('exiting');
setTimeout(function(){
window.location = goTo;
},400);
});
$(".merlin__button--closer").on('click', function(e){
body.removeClass( drawer_opened );
e.preventDefault();
var goTo = this.getAttribute("href");
setTimeout(function(){
body.addClass('exiting');
},600);
setTimeout(function(){
window.location = goTo;
},1100);
});
$(".button-next").on( "click", function(e) {
e.preventDefault();
var loading_button = merlin_loading_button(this);
if ( ! loading_button ) {
return false;
}
var data_callback = $(this).data("callback");
if( data_callback && typeof callbacks[data_callback] !== "undefined"){
// We have to process a callback before continue with form submission.
callbacks[data_callback](this);
return false;
} else {
return true;
}
});
$( document ).on( 'change', '.js-merlin-demo-import-select', function() {
var selectedIndex = $( this ).val();
$( '.js-merlin-select-spinner' ).show();
$.post( merlin_params.ajaxurl, {
action: 'merlin_update_selected_import_data_info',
wpnonce: merlin_params.wpnonce,
selected_index: selectedIndex,
}, function( response ) {
if ( response.success ) {
$( '.js-merlin-drawer-import-content' ).html( response.data );
}
else {
alert( merlin_params.texts.something_went_wrong );
}
$( '.js-merlin-select-spinner' ).hide();
} )
.fail( function() {
$( '.js-merlin-select-spinner' ).hide();
alert( merlin_params.texts.something_went_wrong )
} );
} );
}
function ChildTheme() {
var body = $('.merlin__body');
var complete, notice = $("#child-theme-text");
function ajax_callback(r) {
if (typeof r.done !== "undefined") {
setTimeout(function(){
notice.addClass("lead");
},0);
setTimeout(function(){
notice.addClass("success");
notice.html(r.message);
},600);
complete();
} else {
notice.addClass("lead error");
notice.html(r.error);
}
}
function do_ajax() {
jQuery.post(merlin_params.ajaxurl, {
action: "merlin_child_theme",
wpnonce: merlin_params.wpnonce,
}, ajax_callback).fail(ajax_callback);
}
return {
init: function(btn) {
complete = function() {
setTimeout(function(){
$(".merlin__body").addClass('js--finished');
},1500);
body.removeClass( drawer_opened );
setTimeout(function(){
$('.merlin__body').addClass('exiting');
},3500);
setTimeout(function(){
window.location.href=btn.href;
},4000);
};
do_ajax();
}
}
}
function ActivateLicense() {
var body = $( '.merlin__body' );
var wrapper = $( '.merlin__content--license-key' );
var complete, notice = $( '#license-text' );
function ajax_callback(r) {
if (typeof r.success !== "undefined" && r.success) {
notice.siblings( '.error-message' ).remove();
setTimeout(function(){
notice.addClass("lead");
},0);
setTimeout(function(){
notice.addClass("success");
notice.html(r.message);
},600);
complete();
} else {
$( '.js-merlin-license-activate-button' ).removeClass( 'merlin__button--loading' ).data( 'done-loading', 'no' );
notice.siblings( '.error-message' ).remove();
wrapper.addClass('has-error');
notice.html(r.message);
notice.siblings( '.error-message' ).addClass("lead error");
}
}
function do_ajax() {
wrapper.removeClass('has-error');
jQuery.post(merlin_params.ajaxurl, {
action: "merlin_activate_license",
wpnonce: merlin_params.wpnonce,
license_key: $( '.js-license-key' ).val()
}, ajax_callback).fail(ajax_callback);
}
return {
init: function(btn) {
complete = function() {
setTimeout(function(){
$(".merlin__body").addClass('js--finished');
},1500);
body.removeClass( drawer_opened );
setTimeout(function(){
$('.merlin__body').addClass('exiting');
},3500);
setTimeout(function(){
window.location.href=btn.href;
},4000);
};
do_ajax();
}
}
}
function PluginManager(){
var body = $('.merlin__body');
var complete;
var items_completed = 0;
var current_item = "";
var $current_node;
var current_item_hash = "";
function ajax_callback(response){
var currentSpan = $current_node.find("label");
if(typeof response === "object" && typeof response.message !== "undefined"){
currentSpan.removeClass( 'installing success error' ).addClass(response.message.toLowerCase());
// The plugin is done (installed, updated and activated).
if(typeof response.done != "undefined" && response.done){
find_next();
}else if(typeof response.url != "undefined"){
// we have an ajax url action to perform.
if(response.hash == current_item_hash){
currentSpan.removeClass( 'installing success' ).addClass("error");
find_next();
}else {
current_item_hash = response.hash;
jQuery.post(response.url, response, ajax_callback).fail(ajax_callback);
}
}else{
// error processing this plugin
find_next();
}
}else{
// The TGMPA returns a whole page as response, so check, if this plugin is done.
process_current();
}
}
function process_current(){
if(current_item){
var $check = $current_node.find("input:checkbox");
if($check.is(":checked")) {
jQuery.post(merlin_params.ajaxurl, {
action: "merlin_plugins",
wpnonce: merlin_params.wpnonce,
slug: current_item,
}, ajax_callback).fail(ajax_callback);
}else{
$current_node.addClass("skipping");
setTimeout(find_next,300);
}
}
}
function find_next(){
if($current_node){
if(!$current_node.data("done_item")){
items_completed++;
$current_node.data("done_item",1);
}
$current_node.find(".spinner").css("visibility","hidden");
}
var $li = $(".merlin__drawer--install-plugins li");
$li.each(function(){
var $item = $(this);
if ( $item.data("done_item") ) {
return true;
}
current_item = $item.data("slug");
$current_node = $item;
process_current();
return false;
});
if(items_completed >= $li.length){
// finished all plugins!
complete();
}
}
return {
init: function(btn){
$(".merlin__drawer--install-plugins").addClass("installing");
$(".merlin__drawer--install-plugins").find("input").prop("disabled", true);
complete = function(){
setTimeout(function(){
$(".merlin__body").addClass('js--finished');
},1000);
body.removeClass( drawer_opened );
setTimeout(function(){
$('.merlin__body').addClass('exiting');
},3000);
setTimeout(function(){
window.location.href=btn.href;
},3500);
};
find_next();
}
}
}
function ContentManager(){
var body = $('.merlin__body');
var complete;
var items_completed = 0;
var current_item = "";
var $current_node;
var current_item_hash = "";
var current_content_import_items = 1;
var total_content_import_items = 0;
var progress_bar_interval;
function ajax_callback(response) {
var currentSpan = $current_node.find("label");
if(typeof response == "object" && typeof response.message !== "undefined"){
if(response.message) {
currentSpan.addClass(response.message.toLowerCase());
}
if( typeof response.num_of_imported_posts !== "undefined" && 0 < total_content_import_items ) {
current_content_import_items = 'all' === response.num_of_imported_posts ? total_content_import_items : response.num_of_imported_posts;
update_progress_bar();
}
if(typeof response.url !== "undefined"){
// we have an ajax url action to perform.
if(response.hash === current_item_hash){
currentSpan.addClass("status--failed");
find_next();
}else {
current_item_hash = response.hash;
// Fix the undefined selected_index issue on new AJAX calls.
if ( typeof response.selected_index === "undefined" ) {
response.selected_index = $( '.js-merlin-demo-import-select' ).val() || 0;
}
jQuery.post(response.url, response, ajax_callback).fail(ajax_callback); // recuurrssionnnnn
}
}else if(typeof response.done !== "undefined"){
// finished processing this plugin, move onto next
find_next();
}else{
// error processing this plugin
find_next();
}
}else{
console.log(response);
// error - try again with next plugin
currentSpan.addClass("status--error");
find_next();
}
}
function process_current(){
if(current_item){
var $check = $current_node.find("input:checkbox");
if($check.is(":checked")) {
jQuery.post(merlin_params.ajaxurl, {
action: "merlin_content",
wpnonce: merlin_params.wpnonce,
content: current_item,
selected_index: $( '.js-merlin-demo-import-select' ).val() || 0
}, ajax_callback).fail(ajax_callback);
}else{
$current_node.addClass("skipping");
setTimeout(find_next,300);
}
}
}
function find_next(){
var do_next = false;
if($current_node){
if(!$current_node.data("done_item")){
items_completed++;
$current_node.data("done_item",1);
}
$current_node.find(".spinner").css("visibility","hidden");
}
var $items = $(".merlin__drawer--import-content__list-item");
var $enabled_items = $(".merlin__drawer--import-content__list-item input:checked");
$items.each(function(){
if (current_item == "" || do_next) {
current_item = $(this).data("content");
$current_node = $(this);
process_current();
do_next = false;
} else if ($(this).data("content") == current_item) {
do_next = true;
}
});
if(items_completed >= $items.length){
complete();
}
}
function init_content_import_progress_bar() {
if( ! $(".merlin__drawer--import-content__list-item .checkbox-content").is( ':checked' ) ) {
return false;
}
jQuery.post(merlin_params.ajaxurl, {
action: "merlin_get_total_content_import_items",
wpnonce: merlin_params.wpnonce,
selected_index: $( '.js-merlin-demo-import-select' ).val() || 0
}, function( response ) {
total_content_import_items = response.data;
if ( 0 < total_content_import_items ) {
update_progress_bar();
// Change the value of the progress bar constantly for a small amount (0,2% per sec), to improve UX.
progress_bar_interval = setInterval( function() {
current_content_import_items = current_content_import_items + total_content_import_items/500;
update_progress_bar();
}, 1000 );
}
} );
}
function valBetween(v, min, max) {
return (Math.min(max, Math.max(min, v)));
}
function update_progress_bar() {
$('.js-merlin-progress-bar').css( 'width', (current_content_import_items/total_content_import_items) * 100 + '%' );
var $percentage = valBetween( ((current_content_import_items/total_content_import_items) * 100) , 0, 99);
$('.js-merlin-progress-bar-percentage').html( Math.round( $percentage ) + '%' );
if ( 1 === current_content_import_items/total_content_import_items ) {
clearInterval( progress_bar_interval );
}
}
return {
init: function(btn){
$(".merlin__drawer--import-content").addClass("installing");
$(".merlin__drawer--import-content").find("input").prop("disabled", true);
complete = function(){
$.post(merlin_params.ajaxurl, {
action: "merlin_import_finished",
wpnonce: merlin_params.wpnonce,
selected_index: $( '.js-merlin-demo-import-select' ).val() || 0
});
setTimeout(function(){
$('.js-merlin-progress-bar-percentage').html( '100%' );
},100);
setTimeout(function(){
body.removeClass( drawer_opened );
},500);
setTimeout(function(){
$(".merlin__body").addClass('js--finished');
},1500);
setTimeout(function(){
$('.merlin__body').addClass('exiting');
},3400);
setTimeout(function(){
window.location.href=btn.href;
},4000);
};
init_content_import_progress_bar();
find_next();
}
}
}
function merlin_loading_button( btn ){
var $button = jQuery(btn);
if ( $button.data( "done-loading" ) == "yes" ) {
return false;
}
var completed = false;
var _modifier = $button.is("input") || $button.is("button") ? "val" : "text";
$button.data("done-loading","yes");
$button.addClass("merlin__button--loading");
return {
done: function(){
completed = true;
$button.attr("disabled",false);
}
}
}
return {
init: function(){
t = this;
$(window_loaded);
},
callback: function(func){
console.log(func);
console.log(this);
}
}
})(jQuery);
Merlin.init();

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
<?php
// This folder should be writable. The setup wizard places content in here.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"default-sidebar":{"block-2":{"content":"<!-- wp:search \/-->"},"block-3":{"content":"<!-- wp:group --><div class=\"wp-block-group\"><!-- wp:heading --><h2>Recent Posts<\/h2><!-- \/wp:heading --><!-- wp:latest-posts \/--><\/div><!-- \/wp:group -->"},"block-4":{"content":"<!-- wp:group --><div class=\"wp-block-group\"><!-- wp:heading --><h2>Recent Comments<\/h2><!-- \/wp:heading --><!-- wp:latest-comments {\"displayAvatar\":false,\"displayDate\":false,\"displayExcerpt\":false} \/--><\/div><!-- \/wp:group -->"},"block-5":{"content":"<!-- wp:group --><div class=\"wp-block-group\"><!-- wp:heading --><h2>Archives<\/h2><!-- \/wp:heading --><!-- wp:archives \/--><\/div><!-- \/wp:group -->"},"block-6":{"content":"<!-- wp:group --><div class=\"wp-block-group\"><!-- wp:heading --><h2>Categories<\/h2><!-- \/wp:heading --><!-- wp:categories \/--><\/div><!-- \/wp:group -->"}},"footer-sidebar":{"nav_menu-3":{"title":"Company","nav_menu":46},"nav_menu-4":{"title":"Support","nav_menu":56},"thinkai_recent_posts-2":{"title":"Recent Post","number":"2","cat":"0","btn_title":"Read All News","btn_link":"https:\/\/wp1.themevibrant.com\/newwp\/thinkai\/list-large-image\/"},"thinkai_get_in_touch-2":{"title":"Get in Touch","footer_content":"Here to assist you with your queries about AI image generators. Feel free to ask our team.","footer_btn_title":"Send Your Queries","footer_btn_link":"#","show":"true"}},"footer-sidebar2":{"nav_menu-5":{"title":"Company","nav_menu":46},"nav_menu-6":{"title":"Support","nav_menu":56},"nav_menu-7":{"title":"Quick Link","nav_menu":57},"thinkai_have_any_question-2":{"footer_widget_title":"Have any Question <br> to Aks?","footer_widget_email":"info@example.com","footer_social_icon_title":"Social Connect","show_v2":"true"}},"footer-sidebar3":{"thinkai_about_us-2":{"widget_app_store_image":"https:\/\/wp1.themevibrant.com\/newwp\/thinkai\/wp-content\/uploads\/2024\/05\/app-store.png","footer_app_store_link":"#","widget_android_image":"https:\/\/wp1.themevibrant.com\/newwp\/thinkai\/wp-content\/uploads\/2024\/05\/android-user.png","footer_android_link":"#","footer_description_v3":"50k+ Downloads, Let\u2019s<br> Join With Us","widget_logo_image":"https:\/\/wp1.themevibrant.com\/newwp\/thinkai\/wp-content\/uploads\/2024\/05\/footer-logo-1.png","footer_about_desc":"The great explorer of the truth, the master builder of human happiness no one rejects, dislikes, or avoids pleasure this principle else he endures pains to avoid."},"nav_menu-8":{"title":"Industries","nav_menu":46},"nav_menu-9":{"title":"Company","nav_menu":56},"thinkai_newsletter-2":{"title":"Newsletter","form_description":"Latest news, AI models & fun memes from the community.","mailchimp_form_url":"[mc4wp_form id=44]"}},"footer-sidebar4":{"thinkai_about_us_v2-2":{"widget_logo_image_v2":"https:\/\/wp1.themevibrant.com\/newwp\/thinkai\/wp-content\/uploads\/2024\/05\/footer-logo-3.png","footer_widget_description":"Empowering Creativity through ThinkAI Powered Video.","show":"true"},"nav_menu-10":{"title":"Company","nav_menu":56},"nav_menu-11":{"title":"Industries","nav_menu":46},"nav_menu-12":{"title":"Support","nav_menu":57},"thinkai_copyright-2":{"footer_widget_privacy_menu":"<li><a href=\"#\">Privacy Policy<\/a><\/li>\r\n<li><a href=\"#\">Terms<\/a><\/li>","footer_widget_copyright":"\u00a9 copyrights 2024 <a href=\"#\">ThinkAi,<\/a> All <br> Rights Reserved."}},"shop-sidebar":{"woocommerce_product_search-2":{"title":""},"woocommerce_product_categories-2":{"title":"Product categories","orderby":"name","dropdown":0,"count":0,"hierarchical":1,"show_children_only":0,"hide_empty":0,"max_depth":""},"woocommerce_product_tag_cloud-2":{"title":"Product tags"},"woocommerce_top_rated_products-2":{"title":"Top rated products","number":5}},"service-sidebar":{"nav_menu-2":{"title":"Services","nav_menu":46},"thinkai_banner-3":{"bg_img":"https:\/\/wp1.themevibrant.com\/newwp\/thinkai\/wp-content\/uploads\/2024\/04\/sidebar-banner-box-img-bg.jpg","banner_title":"Customize<br> Your AI Images","banner_text":"Denouncing pleasure praising","btn_title":"Generate Now","btn_link":"#"}},"blog-sidebar":{"search-2":{"title":""},"categories-2":{"title":"Categories","count":0,"hierarchical":0,"dropdown":0},"thinkai_popular_posts-2":{"title":"Popular Post","number":"3","cat":"0"},"tag_cloud-2":{"title":"Popular Tags","count":0,"taxonomy":"post_tag"},"thinkai_banner-2":{"bg_img":"https:\/\/wp1.themevibrant.com\/newwp\/thinkai\/wp-content\/uploads\/2024\/04\/sidebar-banner-box-img-bg.jpg","banner_title":"Customize<br> Your AI Images","banner_text":"Denouncing pleasure praising","btn_title":"Generate Now","btn_link":"https:\/\/wp1.themevibrant.com\/newwp\/thinkai\/image-generation\/"}}}

View File

@@ -0,0 +1,632 @@
<?php
/**
* The main importer class, extending the slightly modified WP importer 2.0 class WXRImporter
*/
namespace ProteusThemes\WPContentImporter2;
use XMLReader;
class Importer extends WXRImporter {
/**
* Time in milliseconds, marking the beginning of the import.
*
* @var float
*/
private $start_time;
/**
* Importer constructor.
* Look at the parent constructor for the options parameters.
*
* @param array $options The importer options.
* @param object $logger The logger object.
*/
public function __construct( $options = array(), $logger = null ) {
parent::__construct( $options );
$this->set_logger( $logger );
// Check, if a new AJAX request is required.
add_filter( 'wxr_importer.pre_process.post', array( $this, 'new_ajax_request_maybe' ) );
// WooCommerce product attributes registration.
if ( class_exists( 'WooCommerce' ) ) {
add_filter( 'wxr_importer.pre_process.term', array( $this, 'woocommerce_product_attributes_registration' ), 10, 1 );
}
}
/**
* Get the XML reader for the file.
*
* @param string $file Path to the XML file.
*
* @return XMLReader|boolean Reader instance on success, false otherwise.
*/
protected function get_reader( $file ) {
// Avoid loading external entities for security
$old_value = null;
if ( function_exists( 'libxml_disable_entity_loader' ) ) {
// $old_value = libxml_disable_entity_loader( true );
}
if ( ! class_exists( 'XMLReader' ) ) {
$this->logger->critical( __( 'The XMLReader class is missing! Please install the XMLReader PHP extension on your server', 'thinkai' ) );
return false;
}
$reader = new XMLReader();
$status = $reader->open( $file );
if ( ! is_null( $old_value ) ) {
// libxml_disable_entity_loader( $old_value );
}
if ( ! $status ) {
$this->logger->error( __( 'Could not open the XML file for parsing!', 'thinkai' ) );
return false;
}
return $reader;
}
/**
* Get the basic import content data.
* Which elements are present in this import file (check possible elements in the $data variable)?
*
* @param $file
*
* @return array|bool
*/
public function get_basic_import_content_data( $file ) {
$data = array(
'users' => false,
'categories' => false,
'tags' => false,
'terms' => false,
'posts' => false,
);
// Get the XML reader and open the file.
$reader = $this->get_reader( $file );
if ( empty( $reader ) ) {
return false;
}
// Start parsing!
while ( $reader->read() ) {
// Only deal with element opens.
if ( $reader->nodeType !== XMLReader::ELEMENT ) {
continue;
}
switch ( $reader->name ) {
case 'wp:author':
// Skip, if the users were already detected.
if ( $data['users'] ) {
$reader->next();
break;
}
$node = $reader->expand();
$parsed = $this->parse_author_node( $node );
// Skip, if there was an error in parsing the author node.
if ( is_wp_error( $parsed ) ) {
$reader->next();
break;
}
$data['users'] = true;
// Handled everything in this node, move on to the next.
$reader->next();
break;
case 'item':
// Skip, if the posts were already detected.
if ( $data['posts'] ) {
$reader->next();
break;
}
$node = $reader->expand();
$parsed = $this->parse_post_node( $node );
// Skip, if there was an error in parsing the item node.
if ( is_wp_error( $parsed ) ) {
$reader->next();
break;
}
$data['posts'] = true;
// Handled everything in this node, move on to the next
$reader->next();
break;
case 'wp:category':
$data['categories'] = true;
// Handled everything in this node, move on to the next
$reader->next();
break;
case 'wp:tag':
$data['tags'] = true;
// Handled everything in this node, move on to the next
$reader->next();
break;
case 'wp:term':
$data['terms'] = true;
// Handled everything in this node, move on to the next
$reader->next();
break;
}
}
return $data;
}
/**
* Get the number of posts (posts, pages, CPT, attachments), that the import file has.
*
* @param $file
*
* @return int
*/
public function get_number_of_posts_to_import( $file ) {
$reader = $this->get_reader( $file );
$counter = 0;
if ( empty( $reader ) ) {
return $counter;
}
// Start parsing!
while ( $reader->read() ) {
// Only deal with element opens.
if ( $reader->nodeType !== XMLReader::ELEMENT ) {
continue;
}
if ( 'item' == $reader->name ) {
$node = $reader->expand();
$parsed = $this->parse_post_node( $node );
// Skip, if there was an error in parsing the item node.
if ( is_wp_error( $parsed ) ) {
$reader->next();
continue;
}
$counter++;
}
}
return $counter;
}
/**
* The main controller for the actual import stage.
*
* @param string $file Path to the WXR file for importing.
* @param array $options Import options (which parts to import).
*
* @return boolean
*/
public function import( $file, $options = array() ) {
add_filter( 'import_post_meta_key', array( $this, 'is_valid_meta_key' ) );
add_filter( 'http_request_timeout', array( &$this, 'bump_request_timeout' ) );
// Start the import timer.
$this->start_time = microtime( true );
// Set the existing import data, from previous AJAX call, if any.
$this->restore_import_data_transient();
// Set the import options defaults.
if ( empty( $options ) ) {
$options = array(
'users' => false,
'categories' => true,
'tags' => true,
'terms' => true,
'posts' => true,
);
}
$result = $this->import_start( $file );
if ( is_wp_error( $result ) ) {
$this->logger->error( __( 'Content import start error: ', 'thinkai' ) . $result->get_error_message() );
return false;
}
// Get the actual XML reader.
$reader = $this->get_reader( $file );
if ( empty( $reader ) ) {
return false;
}
// Set the version to compatibility mode first
$this->version = '1.0';
// Reset other variables
$this->base_url = '';
// Start parsing!
while ( $reader->read() ) {
// Only deal with element opens.
if ( $reader->nodeType !== XMLReader::ELEMENT ) {
continue;
}
switch ( $reader->name ) {
case 'wp:wxr_version':
// Upgrade to the correct version
$this->version = $reader->readString();
if ( version_compare( $this->version, self::MAX_WXR_VERSION, '>' ) ) {
$this->logger->warning( sprintf(
__( 'This WXR file (version %s) is newer than the importer (version %s) and may not be supported. Please consider updating.', 'thinkai' ),
$this->version,
self::MAX_WXR_VERSION
) );
}
// Handled everything in this node, move on to the next
$reader->next();
break;
case 'wp:base_site_url':
$this->base_url = $reader->readString();
// Handled everything in this node, move on to the next
$reader->next();
break;
case 'item':
if ( empty( $options['posts'] ) ) {
$reader->next();
break;
}
$node = $reader->expand();
$parsed = $this->parse_post_node( $node );
if ( is_wp_error( $parsed ) ) {
$this->log_error( $parsed );
// Skip the rest of this post
$reader->next();
break;
}
$this->process_post( $parsed['data'], $parsed['meta'], $parsed['comments'], $parsed['terms'] );
// Handled everything in this node, move on to the next
$reader->next();
break;
case 'wp:author':
if ( empty( $options['users'] ) ) {
$reader->next();
break;
}
$node = $reader->expand();
$parsed = $this->parse_author_node( $node );
if ( is_wp_error( $parsed ) ) {
$this->log_error( $parsed );
// Skip the rest of this post
$reader->next();
break;
}
$status = $this->process_author( $parsed['data'], $parsed['meta'] );
if ( is_wp_error( $status ) ) {
$this->log_error( $status );
}
// Handled everything in this node, move on to the next
$reader->next();
break;
case 'wp:category':
if ( empty( $options['categories'] ) ) {
$reader->next();
break;
}
$node = $reader->expand();
$parsed = $this->parse_term_node( $node, 'category' );
if ( is_wp_error( $parsed ) ) {
$this->log_error( $parsed );
// Skip the rest of this post
$reader->next();
break;
}
$status = $this->process_term( $parsed['data'], $parsed['meta'] );
// Handled everything in this node, move on to the next
$reader->next();
break;
case 'wp:tag':
if ( empty( $options['tags'] ) ) {
$reader->next();
break;
}
$node = $reader->expand();
$parsed = $this->parse_term_node( $node, 'tag' );
if ( is_wp_error( $parsed ) ) {
$this->log_error( $parsed );
// Skip the rest of this post
$reader->next();
break;
}
$status = $this->process_term( $parsed['data'], $parsed['meta'] );
// Handled everything in this node, move on to the next
$reader->next();
break;
case 'wp:term':
if ( empty( $options['terms'] ) ) {
$reader->next();
break;
}
$node = $reader->expand();
$parsed = $this->parse_term_node( $node );
if ( is_wp_error( $parsed ) ) {
$this->log_error( $parsed );
// Skip the rest of this post
$reader->next();
break;
}
$status = $this->process_term( $parsed['data'], $parsed['meta'] );
// Handled everything in this node, move on to the next
$reader->next();
break;
default:
// Skip this node, probably handled by something already
break;
}
}
// Now that we've done the main processing, do any required
// post-processing and remapping.
$this->post_process();
if ( $this->options['aggressive_url_search'] ) {
$this->replace_attachment_urls_in_content();
}
$this->remap_featured_images();
$this->import_end();
// Set the current importer state, so the data can be used on the next AJAX call.
$this->set_current_importer_data();
return true;
}
/**
* Import users only.
*
* @param string $file Path to the import file.
*/
public function import_users( $file ) {
return $this->import( $file, array( 'users' => true ) );
}
/**
* Import categories only.
*
* @param string $file Path to the import file.
*/
public function import_categories( $file ) {
return $this->import( $file, array( 'categories' => true ) );
}
/**
* Import tags only.
*
* @param string $file Path to the import file.
*/
public function import_tags( $file ) {
return $this->import( $file, array( 'tags' => true ) );
}
/**
* Import terms only.
*
* @param string $file Path to the import file.
*/
public function import_terms( $file ) {
return $this->import( $file, array( 'terms' => true ) );
}
/**
* Import posts only.
*
* @param string $file Path to the import file.
*/
public function import_posts( $file ) {
return $this->import( $file, array( 'posts' => true ) );
}
/**
* Check if we need to create a new AJAX request, so that server does not timeout.
* And fix the import warning for missing post author.
*
* @param array $data current post data.
* @return array
*/
public function new_ajax_request_maybe( $data ) {
$time = microtime( true ) - $this->start_time;
// We should make a new ajax call, if the time is right.
if ( $time > apply_filters( 'pt-importer/time_for_one_ajax_call', 20 ) ) {
$response = apply_filters( 'pt-importer/new_ajax_request_response_data', array(
'status' => 'newAJAX',
'log' => 'Time for new AJAX request!: ' . $time,
'num_of_imported_posts' => count( $this->mapping['post'] ),
) );
// Add message to log file.
$this->logger->info( __( 'New AJAX call!', 'thinkai' ) );
// Set the current importer state, so it can be continued on the next AJAX call.
$this->set_current_importer_data();
// Send the request for a new AJAX call.
wp_send_json( $response );
}
// Set importing author to the current user.
// Fixes the [WARNING] Could not find the author for ... log warning messages.
$current_user_obj = wp_get_current_user();
$data['post_author'] = $current_user_obj->user_login;
return $data;
}
/**
* Save current importer data to the DB, for later use.
*/
public function set_current_importer_data() {
$data = apply_filters( 'pt-importer/set_current_importer_data', array(
'options' => $this->options,
'mapping' => $this->mapping,
'requires_remapping' => $this->requires_remapping,
'exists' => $this->exists,
'user_slug_override' => $this->user_slug_override,
'url_remap' => $this->url_remap,
'featured_images' => $this->featured_images,
) );
$this->save_current_import_data_transient( $data );
}
/**
* Set the importer data to the transient.
*
* @param array $data Data to be saved to the transient.
*/
public function save_current_import_data_transient( $data ) {
set_transient( 'pt_importer_data', $data, MINUTE_IN_SECONDS );
}
/**
* Restore the importer data from the transient.
*
* @return boolean
*/
public function restore_import_data_transient() {
if ( $data = get_transient( 'pt_importer_data' ) ) {
$this->options = empty( $data['options'] ) ? array() : $data['options'];
$this->mapping = empty( $data['mapping'] ) ? array() : $data['mapping'];
$this->requires_remapping = empty( $data['requires_remapping'] ) ? array() : $data['requires_remapping'];
$this->exists = empty( $data['exists'] ) ? array() : $data['exists'];
$this->user_slug_override = empty( $data['user_slug_override'] ) ? array() : $data['user_slug_override'];
$this->url_remap = empty( $data['url_remap'] ) ? array() : $data['url_remap'];
$this->featured_images = empty( $data['featured_images'] ) ? array() : $data['featured_images'];
do_action( 'pt-importer/restore_import_data_transient' );
return true;
}
return false;
}
/**
* Get the importer mapping data.
*
* @return array An empty array or an array of mapping data.
*/
public function get_mapping() {
return $this->mapping;
}
/**
* Hook into the pre-process term filter of the content import and register the
* custom WooCommerce product attributes, so that the terms can then be imported normally.
*
* This should probably be removed once the WP importer 2.0 support is added in WooCommerce.
*
* Fixes: [WARNING] Failed to import pa_size L warnings in content import.
* Code from: woocommerce/includes/admin/class-wc-admin-importers.php (ver 2.6.9).
*
* Github issue: https://github.com/proteusthemes/one-click-demo-import/issues/71
*
* @param array $date The term data to import.
* @return array The unchanged term data.
*/
public function woocommerce_product_attributes_registration( $data ) {
global $wpdb;
if ( strstr( $data['taxonomy'], 'pa_' ) ) {
if ( ! taxonomy_exists( $data['taxonomy'] ) ) {
$attribute_name = wc_sanitize_taxonomy_name( str_replace( 'pa_', '', $data['taxonomy'] ) );
// Create the taxonomy
if ( ! in_array( $attribute_name, wc_get_attribute_taxonomies() ) ) {
$attribute = array(
'attribute_label' => $attribute_name,
'attribute_name' => $attribute_name,
'attribute_type' => 'select',
'attribute_orderby' => 'menu_order',
'attribute_public' => 0
);
$wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $attribute );
delete_transient( 'wc_attribute_taxonomies' );
}
if(function_exists('thinkai_taxonomy_regster') ) {
// Register the taxonomy now so that the import works!
thinkai_taxonomy_regster(
$data['taxonomy'],
apply_filters( 'woocommerce_taxonomy_objects_' . $data['taxonomy'], array( 'product' ) ),
apply_filters( 'woocommerce_taxonomy_args_' . $data['taxonomy'], array(
'hierarchical' => true,
'show_ui' => false,
'query_var' => true,
'rewrite' => false,
) )
);
}
}
}
return $data;
}
}

View File

@@ -0,0 +1,137 @@
<?php
namespace ProteusThemes\WPContentImporter2;
/**
* Describes a logger instance
*
* Based on PSR-3: http://www.php-fig.org/psr/psr-3/
*
* The message MUST be a string or object implementing __toString().
*
* The message MAY contain placeholders in the form: {foo} where foo
* will be replaced by the context data in key "foo".
*
* The context array can contain arbitrary data, the only assumption that
* can be made by implementors is that if an Exception instance is given
* to produce a stack trace, it MUST be in a key named "exception".
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
* for the full interface specification.
*/
class WPImporterLogger {
/**
* System is unusable.
*
* @param string $message
* @param array $context
* @return null
*/
public function emergency( $message, array $context = array() ) {
return $this->log( 'emergency', $message, $context );
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
* @return null
*/
public function alert( $message, array $context = array() ) {
return $this->log( 'alert', $message, $context );
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
* @return null
*/
public function critical( $message, array $context = array() ) {
return $this->log( 'critical', $message, $context );
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
* @return null
*/
public function error( $message, array $context = array()) {
return $this->log( 'error', $message, $context );
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
* @return null
*/
public function warning( $message, array $context = array() ) {
return $this->log( 'warning', $message, $context );
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
* @return null
*/
public function notice( $message, array $context = array() ) {
return $this->log( 'notice', $message, $context );
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
* @return null
*/
public function info( $message, array $context = array() ) {
return $this->log( 'info', $message, $context );
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
* @return null
*/
public function debug( $message, array $context = array() ) {
return $this->log( 'debug', $message, $context );
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
public function log( $level, $message, array $context = array() ) {
$this->messages[] = array(
'timestamp' => time(),
'level' => $level,
'message' => $message,
'context' => $context,
);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace ProteusThemes\WPContentImporter2;
class WPImporterLoggerCLI extends WPImporterLogger {
public $min_level = 'notice';
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
public function log( $level, $message, array $context = array() ) {
if ( $this->level_to_numeric( $level ) < $this->level_to_numeric( $this->min_level ) ) {
return;
}
printf(
'[%s] %s' . PHP_EOL,
strtoupper( $level ),
$message
);
}
public static function level_to_numeric( $level ) {
$levels = array(
'emergency' => 8,
'alert' => 7,
'critical' => 6,
'error' => 5,
'warning' => 4,
'notice' => 3,
'info' => 2,
'debug' => 1,
);
if ( ! isset( $levels[ $level ] ) ) {
return 0;
}
return $levels[ $level ];
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace ProteusThemes\WPContentImporter2;
class WXRImportInfo {
public $home;
public $siteurl;
public $title;
public $users = array();
public $post_count = 0;
public $media_count = 0;
public $comment_count = 0;
public $term_count = 0;
public $generator = '';
public $version;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,227 @@
<?php
/**
* Class for the customizer importer.
*
* Code is mostly from the Customizer Export/Import plugin.
*
* @see https://wordpress.org/plugins/customizer-export-import/
*
* @package Merlin WP
*/
class Merlin_Customizer_Importer {
/**
* Import customizer from a DAT file, generated by the Customizer Export/Import plugin.
*
* @param string $customizer_import_file_path path to the customizer import file.
*/
public static function import( $customizer_import_file_path ) {
// Try to import the customizer settings.
$results = self::import_customizer_options( $customizer_import_file_path );
// Check for errors, else write the results to the log file.
if ( is_wp_error( $results ) ) {
Merlin_Logger::get_instance()->error( $results->get_error_message() );
return false;
}
Merlin_Logger::get_instance()->info( __( 'The customizer import has finished successfully', 'thinkai' ) );
return true;
}
/**
* Imports uploaded mods and calls WordPress core customize_save actions so
* themes that hook into them can act before mods are saved to the database.
*
* Update: WP core customize_save actions were removed, because of some errors.
*
* @since 1.1.1
* @param string $import_file_path Path to the import file.
* @return WP_Error
*/
public static function import_customizer_options( $import_file_path ) {
// Setup global vars.
global $wp_customize;
// Setup internal vars.
$template = get_template();
// Make sure we have an import file.
if ( ! file_exists( $import_file_path ) ) {
return new \WP_Error(
'missing_cutomizer_import_file',
sprintf(
esc_html__( 'Error: The customizer import file is missing! File path: %s', 'thinkai' ),
$import_file_path
)
);
}
// Get the upload data.
$raw = thinkai_filesystem()->get_contents( $import_file_path );
// Make sure we got the data.
if ( empty( $raw ) ) {
return new \WP_Error(
'customizer_import_data_missing_content',
esc_html__( 'Error: The customizer import file does not have any content in it. Please make sure to use the correct customizer import file.', 'thinkai' )
);
}
$data = unserialize( $raw );
// Data checks.
if ( ! is_array( $data ) && ( ! isset( $data['template'] ) || ! isset( $data['mods'] ) ) ) {
return new \WP_Error(
'customizer_import_data_error',
esc_html__( 'Error: The customizer import file is not in a correct format. Please make sure to use the correct customizer import file.', 'thinkai' )
);
}
if ( $data['template'] !== $template ) {
return new \WP_Error(
'customizer_import_wrong_theme',
esc_html__( 'Error: The customizer import file is not suitable for current theme. You can only import customizer settings for the same theme or a child theme.', 'thinkai' )
);
}
// Import images.
if ( apply_filters( 'merlin_customizer_import_images', true ) ) {
$data['mods'] = self::import_customizer_images( $data['mods'] );
}
// Import custom options.
if ( isset( $data['options'] ) ) {
// Require modified customizer options class.
if ( ! class_exists( '\WP_Customize_Setting' ) ) {
require_once ABSPATH . 'wp-includes/class-wp-customize-setting.php';
}
foreach ( $data['options'] as $option_key => $option_value ) {
$option = new Merlin_Customizer_Option( $wp_customize, $option_key, array(
'default' => '',
'type' => 'option',
'capability' => 'edit_theme_options',
) );
$option->import( $option_value );
}
}
// Should the customizer import use the WP customize_save* hooks?
$use_wp_customize_save_hooks = apply_filters( 'merlin_enable_wp_customize_save_hooks', false );
if ( $use_wp_customize_save_hooks ) {
do_action( 'customize_save', $wp_customize );
}
// Loop through the mods and save the mods.
foreach ( $data['mods'] as $key => $val ) {
if ( $use_wp_customize_save_hooks ) {
do_action( 'customize_save_' . $key, $wp_customize );
}
set_theme_mod( $key, $val );
}
if ( $use_wp_customize_save_hooks ) {
do_action( 'customize_save_after', $wp_customize );
}
return true;
}
/**
* Helper function: Customizer import - imports images for settings saved as mods.
*
* @param array $mods An array of customizer mods.
* @return array The mods array with any new import data.
*/
private static function import_customizer_images( $mods ) {
foreach ( $mods as $key => $val ) {
if ( self::customizer_is_image_url( $val ) ) {
$data = self::customizer_sideload_image( $val );
if ( ! is_wp_error( $data ) ) {
$mods[ $key ] = $data->url;
// Handle header image controls.
if ( isset( $mods[ $key . '_data' ] ) ) {
$mods[ $key . '_data' ] = $data;
update_post_meta( $data->attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
}
}
}
}
return $mods;
}
/**
* Helper function: Customizer import
* Taken from the core media_sideload_image function and
* modified to return an array of data instead of html.
*
* @param string $file The image file path.
* @return array An array of image data.
*/
private static function customizer_sideload_image( $file ) {
$data = new \stdClass();
if ( ! function_exists( 'media_handle_sideload' ) ) {
require_once( ABSPATH . 'wp-admin/includes/media.php' );
require_once( ABSPATH . 'wp-admin/includes/file.php' );
require_once( ABSPATH . 'wp-admin/includes/image.php' );
}
if ( ! empty( $file ) ) {
// Set variables for storage, fix file filename for query strings.
preg_match( '/[^\?]+\.(jpe?g|jpe|gif|png)\b/i', $file, $matches );
$file_array = array();
$file_array['name'] = basename( $matches[0] );
// Download file to temp location.
$file_array['tmp_name'] = download_url( $file );
// If error storing temporarily, return the error.
if ( is_wp_error( $file_array['tmp_name'] ) ) {
return $file_array['tmp_name'];
}
// Do the validation and storage stuff.
$id = media_handle_sideload( $file_array, 0 );
// If error storing permanently, unlink.
if ( is_wp_error( $id ) ) {
unlink( $file_array['tmp_name'] );
return $id;
}
// Build the object to return.
$meta = wp_get_attachment_metadata( $id );
$data->attachment_id = $id;
$data->url = wp_get_attachment_url( $id );
$data->thumbnail_url = wp_get_attachment_thumb_url( $id );
$data->height = $meta['height'];
$data->width = $meta['width'];
}
return $data;
}
/**
* Checks to see whether a string is an image url or not.
*
* @param string $string The string to check.
* @return bool Whether the string is an image url or not.
*/
private static function customizer_is_image_url( $string = '' ) {
if ( is_string( $string ) ) {
if ( preg_match( '/\.(jpg|jpeg|png|gif)/i', $string ) ) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,22 @@
<?php
/**
* A class that extends WP_Customize_Setting so we can access
* the protected updated method when importing options.
*
* Used in the Customizer importer.
*
* @package Merlin WP
*/
final class Merlin_Customizer_Option extends \WP_Customize_Setting {
/**
* Import an option value for this setting.
*
* @since 1.1.1
* @param mixed $value The option value.
* @return void
*/
public function import( $value ) {
$this->update( $value );
}
}

View File

@@ -0,0 +1,160 @@
<?php
/**
* Class for downloading a file from a given URL.
*
* @package Merlin WP
*/
class Merlin_Downloader {
/**
* Holds full path to where the files will be saved.
*
* @var string
*/
private $download_directory_path = '';
/**
* Constructor method.
*
* @param string $download_directory_path Full path to where the files will be saved.
*/
public function __construct( $download_directory_path = '' ) {
$this->set_download_directory_path( $download_directory_path );
}
/**
* Download file from a given URL.
*
* @param string $url URL of file to download.
* @param string $filename Filename of the file to save.
* @return string|WP_Error Full path to the downloaded file or WP_Error object with error message.
*/
public function download_file( $url, $filename ) {
$content = $this->get_content_from_url( $url );
// Check if there was an error and break out.
if ( is_wp_error( $content ) ) {
Merlin_Logger::get_instance()->error( $content->get_error_message(), array( 'url' => $url, 'filename' => $filename ) );
return $content;
}
$saved_file = thinkai_filesystem()->put_contents( $this->download_directory_path . $filename, $content );
if ( ! empty( $saved_file ) ) {
return $this->download_directory_path . $filename;
}
Merlin_Logger::get_instance()->error( __( 'The file was not able to save to disk, while trying to download it', 'thinkai' ), array( 'url' => $url, 'filename' => $filename ) );
return false;
}
/**
* Helper function: get content from an URL.
*
* @param string $url URL to the content file.
* @return string|WP_Error, content from the URL or WP_Error object with error message.
*/
private function get_content_from_url( $url ) {
// Test if the URL to the file is defined.
if ( empty( $url ) ) {
return new \WP_Error(
'missing_url',
__( 'Missing URL for downloading a file!', 'thinkai' )
);
}
// Get file content from the server.
$response = wp_remote_get(
$url,
array( 'timeout' => apply_filters( 'merlin_timeout_for_downloading_import_file', 20 ) )
);
// Test if the get request was not successful.
if ( is_wp_error( $response ) || 200 !== $response['response']['code'] ) {
// Collect the right format of error data (array or WP_Error).
$response_error = $this->get_error_from_response( $response );
return new \WP_Error(
'download_error',
sprintf(
__( 'An error occurred while fetching file from: %1$s%2$s%3$s!%4$sReason: %5$s - %6$s.', 'thinkai' ),
'<strong>',
$url,
'</strong>',
'<br>',
$response_error['error_code'],
$response_error['error_message']
)
);
}
// Return content retrieved from the URL.
return wp_remote_retrieve_body( $response );
}
/**
* Helper function: get the right format of response errors.
*
* @param array|WP_Error $response Array or WP_Error or the response.
* @return array Error code and error message.
*/
private function get_error_from_response( $response ) {
$response_error = array();
if ( is_array( $response ) ) {
$response_error['error_code'] = $response['response']['code'];
$response_error['error_message'] = $response['response']['message'];
}
else {
$response_error['error_code'] = $response->get_error_code();
$response_error['error_message'] = $response->get_error_message();
}
return $response_error;
}
/**
* Get download_directory_path attribute.
*/
public function get_download_directory_path() {
return $this->download_directory_path;
}
/**
* Set download_directory_path attribute.
* If no valid path is specified, the default WP upload directory will be used.
*
* @param string $download_directory_path Path, where the files will be saved.
*/
public function set_download_directory_path( $download_directory_path ) {
if ( file_exists( $download_directory_path ) ) {
$this->download_directory_path = $download_directory_path;
}
else {
$upload_dir = wp_upload_dir();
$this->download_directory_path = apply_filters( 'merlin_upload_file_path', trailingslashit( $upload_dir['path'] ) );
}
}
/**
* Check, if the file already exists and return his full path.
*
* @param string $filename The name of the file.
*
* @return bool|string
*/
public function fetch_existing_file( $filename ) {
if ( file_exists( $this->download_directory_path . $filename ) ) {
return $this->download_directory_path . $filename;
}
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Class for the custom WP hooks.
*
* @package Merlin WP
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Merlin_Hooks {
/**
* The class constructor.
*/
public function __construct() {
add_action( 'merlin_widget_settings_array', array( $this, 'fix_custom_menu_widget_ids' ) );
add_action( 'import_start', array( $this, 'maybe_disable_creating_different_size_images_during_import' ) );
}
/**
* Change the menu IDs in the custom menu widgets in the widget import data.
* This solves the issue with custom menu widgets not having the correct (new) menu ID, because they
* have the old menu ID from the export site.
*
* @param array $widget The widget settings array.
*/
public function fix_custom_menu_widget_ids( $widget ) {
// Skip (no changes needed), if this is not a custom menu widget.
if ( ! array_key_exists( 'nav_menu', $widget ) || empty( $widget['nav_menu'] ) || ! is_int( $widget['nav_menu'] ) ) {
return $widget;
}
// Get import data, with new menu IDs.
$importer = new ProteusThemes\WPContentImporter2\Importer( array( 'fetch_attachments' => true ), new ProteusThemes\WPContentImporter2\WPImporterLogger() );
$importer->restore_import_data_transient();
$importer_mapping = $importer->get_mapping();
$term_ids = empty( $importer_mapping['term_id'] ) ? array() : $importer_mapping['term_id'];
// Set the new menu ID for the widget.
$widget['nav_menu'] = empty( $term_ids[ $widget['nav_menu'] ] ) ? $widget['nav_menu'] : $term_ids[ $widget['nav_menu'] ];
return $widget;
}
/**
* Wrapper function for the after all import action hook.
*
* @param int $selected_import_index The selected demo import index.
*/
public function after_all_import_action( $selected_import_index ) {
do_action( 'merlin_after_all_import', $selected_import_index );
return true;
}
/**
* Maybe disables generation of multiple image sizes (thumbnails) in the content import step.
*/
public function maybe_disable_creating_different_size_images_during_import() {
if ( ! apply_filters( 'merlin_regenerate_thumbnails_in_content_import', true ) ) {
add_filter( 'intermediate_image_sizes_advanced', '__return_null' );
}
}
}

View File

@@ -0,0 +1,195 @@
<?php
/**
* The logger class, which will abstract the use of the monolog library.
* More about monolog: https://github.com/Seldaek/monolog
*/
use Monolog\Logger as MonologLogger;
use Monolog\Handler\StreamHandler;
class Merlin_Logger {
/**
* @var object instance of the monolog logger class.
*/
private $log;
/**
* @var string The absolute path to the log file.
*/
private $log_path;
/**
* @var string The name of the logger instance.
*/
private $logger_name;
/**
* The instance *Singleton* of this class
*
* @var object
*/
private static $instance;
/**
* Returns the *Singleton* instance of this class.
*
* @return object EasyDigitalDownloadsFastspring *Singleton* instance.
*
* @codeCoverageIgnore Nothing to test, default PHP singleton functionality.
*/
public static function get_instance() {
if ( null === static::$instance ) {
static::$instance = new static();
}
return static::$instance;
}
/**
* Logger constructor.
*
* Protected constructor to prevent creating a new instance of the
* *Singleton* via the `new` operator from outside of this class.
*/
protected function __construct( $log_path = null, $name = 'merlin-logger' ) {
$this->log_path = $log_path;
$this->logger_name = $name;
if ( empty( $this->log_path ) ) {
$upload_dir = wp_upload_dir();
$logger_dir = $upload_dir['basedir'] . '/merlin-wp';
if ( ! file_exists( $logger_dir ) ) {
wp_mkdir_p( $logger_dir );
}
$this->log_path = $logger_dir . '/main.log';
}
$this->initialize_logger();
}
/**
* Initialize the monolog logger class.
*/
private function initialize_logger() {
if ( empty( $this->log_path ) || empty( $this->logger_name ) ) {
return false;
}
$this->log = new MonologLogger( $this->logger_name );
$this->log->pushHandler( new StreamHandler( $this->log_path, MonologLogger::DEBUG ) );
}
/**
* Log message for log level: debug.
*
* @param string $message The log message.
* @param array $context The log context.
*
* @return boolean Whether the record has been processed.
*/
public function debug( $message, $context = array() ) {
return $this->log->debug( $message, $context );
}
/**
* Log message for log level: info.
*
* @param string $message The log message.
* @param array $context The log context.
*
* @return boolean Whether the record has been processed.
*/
public function info( $message, $context = array() ) {
return $this->log->info( $message, $context );
}
/**
* Log message for log level: notice.
*
* @param string $message The log message.
* @param array $context The log context.
*
* @return boolean Whether the record has been processed.
*/
public function notice( $message, $context = array() ) {
return $this->log->notice( $message, $context );
}
/**
* Log message for log level: warning.
*
* @param string $message The log message.
* @param array $context The log context.
*
* @return boolean Whether the record has been processed.
*/
public function warning( $message, $context = array() ) {
return $this->log->warning( $message, $context );
}
/**
* Log message for log level: error.
*
* @param string $message The log message.
* @param array $context The log context.
*
* @return boolean Whether the record has been processed.
*/
public function error( $message, $context = array() ) {
return $this->log->error( $message, $context );
}
/**
* Log message for log level: alert.
*
* @param string $message The log message.
* @param array $context The log context.
*
* @return boolean Whether the record has been processed.
*/
public function alert( $message, $context = array() ) {
return $this->log->alert( $message, $context );
}
/**
* Log message for log level: emergency.
*
* @param string $message The log message.
* @param array $context The log context.
*
* @return boolean Whether the record has been processed.
*/
public function emergency( $message, $context = array() ) {
return $this->log->emergency( $message, $context );
}
/**
* Private clone method to prevent cloning of the instance of the *Singleton* instance.
*
* @return void
*/
private function __clone() {}
/**
* Private unserialize method to prevent unserializing of the *Singleton* instance.
*
* @return void
*/
public function __wakeup() {}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Class for the Redux importer.
*
* @see https://wordpress.org/plugins/redux-framework/
*
* @package Merlin WP
*/
class Merlin_Redux_Importer {
/**
* Import Redux data from a JSON file, generated by the Redux plugin.
*
* @param array $import_data Array of arrays. Child array contains 'option_name' and 'file_path'.
*
* @return boolean
*/
public static function import( $import_data ) {
// Redux plugin is not active!
if ( ! class_exists( 'ReduxFramework' ) || ! class_exists( 'ReduxFrameworkInstances' ) || empty( $import_data ) ) {
return false;
}
foreach ( $import_data as $redux_item ) {
$redux_options_raw_data = thinkai_filesystem()->get_contents( $redux_item['file_path'] );
$redux_options_data = json_decode( $redux_options_raw_data, true );
$redux_framework = ReduxFrameworkInstances::get_instance( $redux_item['option_name'] );
if ( isset( $redux_framework->args['opt_name'] ) ) {
$redux_framework->set_options( $redux_options_data );
Merlin_Logger::get_instance()->debug( __( 'The Redux Framework data was imported' , 'thinkai'), $redux_item );
}
}
return true;
}
}

View File

@@ -0,0 +1,341 @@
<?php
/**
* Class for the widget importer.
*
* Code is mostly from the Widget Importer & Exporter plugin.
*
* @see https://wordpress.org/plugins/widget-importer-exporter/
*
* @package Merlin WP
*/
class Merlin_Widget_Importer {
/**
* Import widgets from WIE or JSON file.
*
* @param string $widget_import_file_path path to the widget import file.
*/
public static function import( $widget_import_file_path ) {
if ( empty( $widget_import_file_path ) ) {
return false;
}
self::unset_default_widgets();
$results = self::import_widgets( $widget_import_file_path );
if ( is_wp_error( $results ) ) {
Merlin_Logger::get_instance()->error( $results->get_error_message() );
return false;
}
ob_start();
self::format_results_for_log( $results );
$message = ob_get_clean();
Merlin_Logger::get_instance()->debug( $message );
return true;
}
/**
* Imports widgets from a json file.
*
* @param string $data_file path to json file with WordPress widget export data.
*/
private static function import_widgets( $data_file ) {
// Get widgets data from file.
$data = self::process_import_file( $data_file );
// Return from this function if there was an error.
if ( is_wp_error( $data ) ) {
return $data;
}
// Import the widget data and save the results.
return self::import_data( $data );
}
/**
* Process import file - this parses the widget data and returns it.
*
* @param string $file path to json file.
* @return WP_Error|object
*/
private static function process_import_file( $file ) {
// File exists?
if ( ! file_exists( $file ) ) {
return new \WP_Error(
'widget_import_file_not_found',
__( 'Error: Widget import file could not be found.', 'thinkai' )
);
}
// Get file contents and decode.
$data = thinkai_filesystem()->get_contents( $file );
// Return from this function if there was an error.
if ( empty( $data ) ) {
return new \WP_Error(
'widget_import_file_missing_content',
__( 'Error: Widget import file does not have any content in it.', 'thinkai' )
);
}
return json_decode( $data );
}
/**
* Import widget JSON data
*
* @global array $wp_registered_sidebars
* @param object $data JSON widget data.
* @return array|WP_Error
*/
private static function import_data( $data ) {
global $wp_registered_sidebars;
// Have valid data? If no data or could not decode.
if ( empty( $data ) || ! is_object( $data ) ) {
return new \WP_Error(
'corrupted_widget_import_data',
__( 'Error: Widget import data could not be read. Please try a different file.', 'thinkai' )
);
}
// Hook before import.
do_action( 'merlin_widget_importer_before_widgets_import', $data );
$data = apply_filters( 'merlin_before_widgets_import_data', $data );
// Get all available widgets site supports.
$available_widgets = self::available_widgets();
// Get all existing widget instances.
$widget_instances = array();
foreach ( $available_widgets as $widget_data ) {
$widget_instances[ $widget_data['id_base'] ] = get_option( 'widget_' . $widget_data['id_base'] );
}
// Begin results.
$results = array();
// Loop import data's sidebars.
foreach ( $data as $sidebar_id => $widgets ) {
// Skip inactive widgets (should not be in export file).
if ( 'wp_inactive_widgets' == $sidebar_id ) {
continue;
}
// Check if sidebar is available on this site. Otherwise add widgets to inactive, and say so.
if ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ) {
$sidebar_available = true;
$use_sidebar_id = $sidebar_id;
$sidebar_message_type = 'success';
$sidebar_message = '';
}
else {
$sidebar_available = false;
$use_sidebar_id = 'wp_inactive_widgets'; // Add to inactive if sidebar does not exist in theme.
$sidebar_message_type = 'error';
$sidebar_message = __( 'Sidebar does not exist in theme (moving widget to Inactive)', 'thinkai' );
}
// Result for sidebar.
$results[ $sidebar_id ]['name'] = ! empty( $wp_registered_sidebars[ $sidebar_id ]['name'] ) ? $wp_registered_sidebars[ $sidebar_id ]['name'] : $sidebar_id; // Sidebar name if theme supports it; otherwise ID.
$results[ $sidebar_id ]['message_type'] = $sidebar_message_type;
$results[ $sidebar_id ]['message'] = $sidebar_message;
$results[ $sidebar_id ]['widgets'] = array();
// Loop widgets.
foreach ( $widgets as $widget_instance_id => $widget ) {
$fail = false;
// Get id_base (remove -# from end) and instance ID number.
$id_base = preg_replace( '/-[0-9]+$/', '', $widget_instance_id );
$instance_id_number = str_replace( $id_base . '-', '', $widget_instance_id );
// Does site support this widget?
if ( ! $fail && ! isset( $available_widgets[ $id_base ] ) ) {
$fail = true;
$widget_message_type = 'error';
$widget_message = __( 'Site does not support widget', 'thinkai' ); // Explain why widget not imported.
}
// Filter to modify settings object before conversion to array and import.
// Leave this filter here for backwards compatibility with manipulating objects (before conversion to array below).
// Ideally the newer wie_widget_settings_array below will be used instead of this.
$widget = apply_filters( 'merlin_widget_settings', $widget ); // Object.
// Convert multidimensional objects to multidimensional arrays.
// Some plugins like Jetpack Widget Visibility store settings as multidimensional arrays.
// Without this, they are imported as objects and cause fatal error on Widgets page.
// If this creates problems for plugins that do actually intend settings in objects then may need to consider other approach: https://wordpress.org/support/topic/problem-with-array-of-arrays.
// It is probably much more likely that arrays are used than objects, however.
$widget = json_decode( json_encode( $widget ), true );
// Filter to modify settings array.
// This is preferred over the older wie_widget_settings filter above.
// Do before identical check because changes may make it identical to end result (such as URL replacements).
$widget = apply_filters( 'merlin_widget_settings_array', $widget );
// Does widget with identical settings already exist in same sidebar?
if ( ! $fail && isset( $widget_instances[ $id_base ] ) ) {
// Get existing widgets in this sidebar.
$sidebars_widgets = get_option( 'sidebars_widgets' );
$sidebar_widgets = isset( $sidebars_widgets[ $use_sidebar_id ] ) ? $sidebars_widgets[ $use_sidebar_id ] : array(); // Check Inactive if that's where will go.
// Loop widgets with ID base.
$single_widget_instances = ! empty( $widget_instances[ $id_base ] ) ? $widget_instances[ $id_base ] : array();
foreach ( $single_widget_instances as $check_id => $check_widget ) {
// Is widget in same sidebar and has identical settings?
if ( in_array( "$id_base-$check_id", $sidebar_widgets ) && (array) $widget == $check_widget ) {
$fail = true;
$widget_message_type = 'warning';
$widget_message = __( 'Widget already exists', 'thinkai' ); // Explain why widget not imported.
break;
}
}
}
// No failure.
if ( ! $fail ) {
// Add widget instance.
$single_widget_instances = get_option( 'widget_' . $id_base ); // All instances for that widget ID base, get fresh every time.
$single_widget_instances = ! empty( $single_widget_instances ) ? $single_widget_instances : array( '_multiwidget' => 1 ); // Start fresh if have to.
$single_widget_instances[] = $widget; // Add it.
// Get the key it was given.
end( $single_widget_instances );
$new_instance_id_number = key( $single_widget_instances );
// If key is 0, make it 1.
// When 0, an issue can occur where adding a widget causes data from other widget to load, and the widget doesn't stick (reload wipes it).
if ( '0' === strval( $new_instance_id_number ) ) {
$new_instance_id_number = 1;
$single_widget_instances[ $new_instance_id_number ] = $single_widget_instances[0];
unset( $single_widget_instances[0] );
}
// Move _multiwidget to end of array for uniformity.
if ( isset( $single_widget_instances['_multiwidget'] ) ) {
$multiwidget = $single_widget_instances['_multiwidget'];
unset( $single_widget_instances['_multiwidget'] );
$single_widget_instances['_multiwidget'] = $multiwidget;
}
// Update option with new widget.
update_option( 'widget_' . $id_base, $single_widget_instances );
// Assign widget instance to sidebar.
$sidebars_widgets = get_option( 'sidebars_widgets' ); // Which sidebars have which widgets, get fresh every time.
$new_instance_id = $id_base . '-' . $new_instance_id_number; // Use ID number from new widget instance.
$sidebars_widgets[ $use_sidebar_id ][] = $new_instance_id; // Add new instance to sidebar.
update_option( 'sidebars_widgets', $sidebars_widgets ); // Save the amended data.
// After widget import action.
$after_widget_import = array(
'sidebar' => $use_sidebar_id,
'sidebar_old' => $sidebar_id,
'widget' => $widget,
'widget_type' => $id_base,
'widget_id' => $new_instance_id,
'widget_id_old' => $widget_instance_id,
'widget_id_num' => $new_instance_id_number,
'widget_id_num_old' => $instance_id_number,
);
do_action( 'merlin_widget_importer_after_single_widget_import', $after_widget_import );
// Success message.
if ( $sidebar_available ) {
$widget_message_type = 'success';
$widget_message = __( 'Imported', 'thinkai' );
}
else {
$widget_message_type = 'warning';
$widget_message = __( 'Imported to Inactive', 'thinkai' );
}
}
// Result for widget instance.
$results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['name'] = isset( $available_widgets[ $id_base ]['name'] ) ? $available_widgets[ $id_base ]['name'] : $id_base; // Widget name or ID if name not available (not supported by site).
$results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['title'] = ! empty( $widget['title'] ) ? $widget['title'] : __( 'No Title', 'thinkai' ); // Show "No Title" if widget instance is untitled.
$results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['message_type'] = $widget_message_type;
$results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['message'] = $widget_message;
}
}
// Hook after import.
do_action( 'merlin_widget_importer_after_widgets_import', $data );
// Return results.
return apply_filters( 'merlin_widget_import_results', $results );
}
/**
* Available widgets.
*
* Gather site's widgets into array with ID base, name, etc.
*
* @global array $wp_registered_widget_controls
* @return array $available_widgets, Widget information
*/
private static function available_widgets() {
global $wp_registered_widget_controls;
$widget_controls = $wp_registered_widget_controls;
$available_widgets = array();
foreach ( $widget_controls as $widget ) {
if ( ! empty( $widget['id_base'] ) && ! isset( $available_widgets[ $widget['id_base'] ] ) ) {
$available_widgets[ $widget['id_base'] ]['id_base'] = $widget['id_base'];
$available_widgets[ $widget['id_base'] ]['name'] = $widget['name'];
}
}
return apply_filters( 'merlin_available_widgets', $available_widgets );
}
/**
* Remove widgets from sidebars.
* By default none are removed, but with the filter you can remove them.
*/
private static function unset_default_widgets() {
$widget_areas = apply_filters( 'merlin_unset_default_widgets_args', false );
if ( empty( $widget_areas ) ) {
return false;
}
update_option( 'sidebars_widgets', $widget_areas );
}
/**
* Format results for log file
*
* @param array $results widget import results.
*/
private static function format_results_for_log( $results ) {
if ( empty( $results ) ) {
esc_html_e( 'No results for widget import!', 'thinkai' );
}
// Loop sidebars.
foreach ( $results as $sidebar ) {
echo esc_html( $sidebar['name'] ) . ' : ' . esc_html( $sidebar['message'] ) . PHP_EOL . PHP_EOL;
// Loop widgets.
foreach ( $sidebar['widgets'] as $widget ) {
echo esc_html( $widget['name'] ) . ' - ' . esc_html( $widget['title'] ) . ' - ' . esc_html( $widget['message'] ) . PHP_EOL;
}
echo PHP_EOL;
}
}
}

View File

@@ -0,0 +1,349 @@
# Copyright (C) 2018 Merlin-WP
# This file is distributed under the same license as the Merlin-WP package.
msgid ""
msgstr ""
"Project-Id-Version: Merlin-WP\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language-Team: Merlin WP\n"
"Last-Translator: Rich Tabor <hello@merlinwp.com>\n"
"Report-Msgid-Bugs-To: https://merlinwp.com\n"
"X-Poedit-Basepath: ..\n"
"X-Poedit-KeywordsList: __;_e;_ex:1,2c;_n:1,2;_n_noop:1,2;_nx:1,2,4c;_nx_noop:1,2,3c;_x:1,2c;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
"X-Poedit-SearchPath-0: .\n"
"X-Poedit-SearchPathExcluded-0: *.js\n"
"X-Poedit-SourceCharset: UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: class-merlin.php:453
msgid "Something went wrong. Please refresh the page and try again!"
msgstr ""
#: class-merlin.php:599
msgid "Please define default parameters in the form of an array."
msgstr ""
#: class-merlin.php:604
msgid "Please define an SVG icon filename."
msgstr ""
#: class-merlin.php:713
msgid "Welcome"
msgstr ""
#: class-merlin.php:720
msgid "Child"
msgstr ""
#: class-merlin.php:726
msgid "License"
msgstr ""
#: class-merlin.php:734
msgid "Plugins"
msgstr ""
#: class-merlin.php:742, class-merlin.php:1982
msgid "Content"
msgstr ""
#: class-merlin.php:748
msgid "Ready"
msgstr ""
#: class-merlin.php:853
msgid "The welcome step has been displayed"
msgstr ""
#: class-merlin.php:947
msgid "The license activation step has been displayed"
msgstr ""
#: class-merlin.php:1017
msgid "The child theme installation step has been displayed"
msgstr ""
#: class-merlin.php:1104
msgid "Required"
msgstr ""
#: class-merlin.php:1105
msgid "req"
msgstr ""
#: class-merlin.php:1145
msgid "The plugin installation step has been displayed"
msgstr ""
#: class-merlin.php:1191
msgid "Select Demo"
msgstr ""
#: class-merlin.php:1229
msgid "The content import step has been displayed"
msgstr ""
#: class-merlin.php:1309
msgid "The final step has been displayed"
msgstr ""
#: class-merlin.php:1386
msgid "The existing child theme was activated"
msgstr ""
#: class-merlin.php:1403
msgid "The newly generated child theme was activated"
msgstr ""
#: class-merlin.php:1424
msgid "Yikes! The theme activation failed. Please try again or contact support."
msgstr ""
#: class-merlin.php:1433
msgid "Please add your license key before attempting to activate one."
msgstr ""
#: class-merlin.php:1446
msgid "The license activation was performed with the following results"
msgstr ""
#: class-merlin.php:1492, class-merlin.php:1533
msgid "An error occurred, please try again."
msgstr ""
#: class-merlin.php:1505
msgid "Your license key expired on %s."
msgstr ""
#: class-merlin.php:1511
msgid "Your license key has been disabled."
msgstr ""
#: class-merlin.php:1515
msgid "This appears to be an invalid license key. Please try again or contact support."
msgstr ""
#: class-merlin.php:1520
msgid "Your license is not active for this URL."
msgstr ""
#: class-merlin.php:1525
msgid "This appears to be an invalid license key for %s."
msgstr ""
#: class-merlin.php:1529
msgid "Your license key has reached its activation limit."
msgstr ""
#: class-merlin.php:1623
msgid "The child theme functions.php content was generated"
msgstr ""
#: class-merlin.php:1654
msgid "The child theme style.css content was generated"
msgstr ""
#: class-merlin.php:1688
msgid "The child theme screenshot was copied to the child theme, with the following result"
msgstr ""
#: class-merlin.php:1690
msgid "The child theme screenshot was not generated, because of these results"
msgstr ""
#: class-merlin.php:1719
msgid "Activating"
msgstr ""
#: class-merlin.php:1735
msgid "Updating"
msgstr ""
#: class-merlin.php:1751, class-merlin.php:1767, class-merlin.php:1985, class-merlin.php:1998, class-merlin.php:2011, class-merlin.php:2024, class-merlin.php:2037, class-merlin.php:2050, class-merlin.php:2093
msgid "Installing"
msgstr ""
#: class-merlin.php:1759
msgid "A plugin with the following data will be processed"
msgstr ""
#: class-merlin.php:1771
msgid "A plugin with the following data was processed"
msgstr ""
#: class-merlin.php:1780, class-merlin.php:1986, class-merlin.php:1999, class-merlin.php:2012, class-merlin.php:2025, class-merlin.php:2038, class-merlin.php:2051
msgid "Success"
msgstr ""
#: class-merlin.php:1803
msgid "The content importer AJAX call failed to start, because of incorrect data"
msgstr ""
#: class-merlin.php:1808
msgid "Invalid content!"
msgstr ""
#: class-merlin.php:1819
msgid "The content import AJAX call will be executed with this import data"
msgstr ""
#: class-merlin.php:1861
msgid "The content import AJAX call failed with this passed data"
msgstr ""
#: class-merlin.php:1872
msgid "Error"
msgstr ""
#: class-merlin.php:1886
msgid "The content importer AJAX call for retrieving total content import items failed to start, because of incorrect data."
msgstr ""
#: class-merlin.php:1891
msgid "Invalid data!"
msgstr ""
#: class-merlin.php:1983
msgid "Demo content data."
msgstr ""
#: class-merlin.php:1984, class-merlin.php:1997, class-merlin.php:2010, class-merlin.php:2023, class-merlin.php:2036, class-merlin.php:2049
msgid "Pending"
msgstr ""
#: class-merlin.php:1995
msgid "Widgets"
msgstr ""
#: class-merlin.php:1996
msgid "Sample widgets data."
msgstr ""
#: class-merlin.php:2008
msgid "Revolution Slider"
msgstr ""
#: class-merlin.php:2009
msgid "Sample Revolution sliders data."
msgstr ""
#: class-merlin.php:2021
msgid "Options"
msgstr ""
#: class-merlin.php:2022
msgid "Sample theme options data."
msgstr ""
#: class-merlin.php:2034
msgid "Redux Options"
msgstr ""
#: class-merlin.php:2035
msgid "Redux framework options."
msgstr ""
#: class-merlin.php:2047
msgid "After import setup"
msgstr ""
#: class-merlin.php:2048
msgid "After import setup."
msgstr ""
#: class-merlin.php:2077
msgid "The revolution slider import was executed"
msgstr ""
#: class-merlin.php:2114
msgid "The home page was set"
msgstr ""
#: class-merlin.php:2124
msgid "The blog page was set"
msgstr ""
#: class-merlin.php:2139
msgid "The Hello world post status was set to draft"
msgstr ""
#: class-merlin.php:2163
msgid "This predefined demo import does not have the name parameter: import_file_name"
msgstr ""
#: includes/class-merlin-customizer-importer.php:30
msgid "The customizer import has finished successfully"
msgstr ""
#: includes/class-merlin-customizer-importer.php:57
msgid "Error: The customizer import file is missing! File path: %s"
msgstr ""
#: includes/class-merlin-customizer-importer.php:70
msgid "Error: The customizer import file does not have any content in it. Please make sure to use the correct customizer import file."
msgstr ""
#: includes/class-merlin-customizer-importer.php:80
msgid "Error: The customizer import file is not in a correct format. Please make sure to use the correct customizer import file."
msgstr ""
#: includes/class-merlin-customizer-importer.php:86
msgid "Error: The customizer import file is not suitable for current theme. You can only import customizer settings for the same theme or a child theme."
msgstr ""
#: includes/class-merlin-downloader.php:49
msgid "The file was not able to save to disk, while trying to download it"
msgstr ""
#: includes/class-merlin-downloader.php:66
msgid "Missing URL for downloading a file!"
msgstr ""
#: includes/class-merlin-downloader.php:84
msgid "An error occurred while fetching file from: %1$s%2$s%3$s!%4$sReason: %5$s - %6$s."
msgstr ""
#: includes/class-merlin-redux-importer.php:32
msgid "The Redux Framework data was imported"
msgstr ""
#: includes/class-merlin-widget-importer.php:72
msgid "Error: Widget import file could not be found."
msgstr ""
#: includes/class-merlin-widget-importer.php:83
msgid "Error: Widget import file does not have any content in it."
msgstr ""
#: includes/class-merlin-widget-importer.php:105
msgid "Error: Widget import data could not be read. Please try a different file."
msgstr ""
#: includes/class-merlin-widget-importer.php:144
msgid "Sidebar does not exist in theme (moving widget to Inactive)"
msgstr ""
#: includes/class-merlin-widget-importer.php:165
msgid "Site does not support widget"
msgstr ""
#: includes/class-merlin-widget-importer.php:198
msgid "Widget already exists"
msgstr ""
#: includes/class-merlin-widget-importer.php:256
msgid "Imported"
msgstr ""
#: includes/class-merlin-widget-importer.php:260
msgid "Imported to Inactive"
msgstr ""
#: includes/class-merlin-widget-importer.php:266
msgid "No Title"
msgstr ""
#: includes/class-merlin-widget-importer.php:328
msgid "No results for widget import!"
msgstr ""

View File

@@ -0,0 +1,125 @@
<?php
/**
* Merlin WP configuration file.
*
* @package Merlin WP
* @version @@pkg.version
* @link https://merlinwp.com/
* @author Rich Tabor, from ThemeBeans.com & the team at ProteusThemes.com
* @copyright Copyright (c) 2018, Merlin WP of Inventionn LLC
* @license Licensed GPLv3 for Open Source Use
*/
if ( ! class_exists( 'Merlin' ) ) {
return;
}
defined('THEMEKALIA_IMPORT_VENDOR_PATH') || define('THEMEKALIA_IMPORT_VENDOR_PATH', get_template_directory() . '/demo-import/vendor/');
require_once THEMEKALIA_IMPORT_VENDOR_PATH . 'autoload.php';
if ( ! class_exists( '\WP_Importer' ) ) {
require ABSPATH . '/wp-admin/includes/class-wp-importer.php';
}
require_once get_template_directory() . '/demo-import/importer/WPImporterLogger.php';
require_once get_template_directory() . '/demo-import/importer/WPImporterLoggerCLI.php';
require_once get_template_directory() . '/demo-import/importer/WXRImportInfo.php';
require_once get_template_directory() . '/demo-import/importer/WXRImporter.php';
require_once get_template_directory() . '/demo-import/importer/Importer.php';
/**
* Set directory locations, text strings, and settings.
*/
$wizard = new Merlin(
$config = array(
'directory' => 'demo-import',
// Location / directory where Merlin WP is placed in your theme.
'merlin_url' => 'demo-import',
// The wp-admin page slug where Merlin WP loads.
'parent_slug' => 'themes.php',
// The wp-admin parent page slug for the admin menu item.
'capability' => 'manage_options',
// The capability required for this menu to be displayed to the user.
'child_action_btn_url' => 'https://codex.wordpress.org/child_themes',
// URL for the 'child-action-link'.
'dev_mode' => true,
// Enable development mode for testing.
'license_step' => false,
// EDD license activation step.
'license_required' => false,
// Require the license activation step.
'license_help_url' => '',
// URL for the 'license-tooltip'.
'edd_remote_api_url' => '',
// EDD_Theme_Updater_Admin remote_api_url.
'edd_item_name' => 'Thinkai',
// EDD_Theme_Updater_Admin item_name.
'edd_theme_slug' => 'thinkai',
// EDD_Theme_Updater_Admin item_slug.
'ready_big_button_url' => home_url( '/' ),
// Link for the big button on the ready step.
),
$strings = array(
'admin-menu' => esc_html__( 'Theme Kalia Import', 'thinkai' ),
/* translators: 1: Title Tag 2: Theme Name 3: Closing Title Tag */
'title%s%s%s%s' => esc_html__( '%1$s%2$s Themes &lsaquo; Theme Setup: %3$s%4$s', 'thinkai' ),
'return-to-dashboard' => esc_html__( 'Return to the dashboard', 'thinkai' ),
'ignore' => esc_html__( 'Disable this wizard', 'thinkai' ),
'btn-skip' => esc_html__( 'Skip', 'thinkai' ),
'btn-next' => esc_html__( 'Next', 'thinkai' ),
'btn-start' => esc_html__( 'Start', 'thinkai' ),
'btn-no' => esc_html__( 'Cancel', 'thinkai' ),
'btn-plugins-install' => esc_html__( 'Install', 'thinkai' ),
'btn-child-install' => esc_html__( 'Install', 'thinkai' ),
'btn-content-install' => esc_html__( 'Install', 'thinkai' ),
'btn-import' => esc_html__( 'Import', 'thinkai' ),
'btn-license-activate' => esc_html__( 'Activate', 'thinkai' ),
'btn-license-skip' => esc_html__( 'Later', 'thinkai' ),
/* translators: Theme Name */
'license-header%s' => esc_html__( 'Activate %s', 'thinkai' ),
/* translators: Theme Name */
'license-header-success%s' => esc_html__( '%s is Activated', 'thinkai' ),
/* translators: Theme Name */
'license%s' => esc_html__( 'Enter your license key to enable remote updates and theme support.', 'thinkai' ),
'license-label' => esc_html__( 'License key', 'thinkai' ),
'license-success%s' => esc_html__( 'The theme is already registered, so you can go to the next step!', 'thinkai' ),
'license-json-success%s' => esc_html__( 'Your theme is activated! Remote updates and theme support are enabled.', 'thinkai' ),
'license-tooltip' => esc_html__( 'Need help?', 'thinkai' ),
/* translators: Theme Name */
'welcome-header%s' => esc_html__( 'Welcome to %s', 'thinkai' ),
'welcome-header-success%s' => esc_html__( 'Hi. Welcome back', 'thinkai' ),
'welcome%s' => esc_html__( 'This wizard will set up your theme, install plugins, and import content. It is optional & should take only a few minutes.', 'thinkai' ),
'welcome-success%s' => esc_html__( 'You may have already run this theme setup wizard. If you would like to proceed anyway, click on the "Start" button below.', 'thinkai' ),
'child-header' => esc_html__( 'Install Child Theme', 'thinkai' ),
'child-header-success' => esc_html__( 'You\'re good to go!', 'thinkai' ),
'child' => esc_html__( 'Let\'s build & activate a child theme so you may easily make theme changes.', 'thinkai' ),
'child-success%s' => esc_html__( 'Your child theme has already been installed and is now activated, if it wasn\'t already.', 'thinkai' ),
'child-action-link' => esc_html__( 'Learn about child themes', 'thinkai' ),
'child-json-success%s' => esc_html__( 'Awesome. Your child theme has already been installed and is now activated.', 'thinkai' ),
'child-json-already%s' => esc_html__( 'Awesome. Your child theme has been created and is now activated.', 'thinkai' ),
'plugins-header' => esc_html__( 'Install Plugins', 'thinkai' ),
'plugins-header-success' => esc_html__( 'You\'re up to speed!', 'thinkai' ),
'plugins' => esc_html__( 'Let\'s install some essential WordPress plugins to get your site up to speed.', 'thinkai' ),
'plugins-success%s' => esc_html__( 'The required WordPress plugins are all installed and up to date. Press "Next" to continue the setup wizard.', 'thinkai' ),
'plugins-action-link' => esc_html__( 'Advanced', 'thinkai' ),
'import-header' => esc_html__( 'Import Content', 'thinkai' ),
'import' => esc_html__( 'Let\'s import content to your website, to help you get familiar with the theme.', 'thinkai' ),
'import-action-link' => esc_html__( 'Advanced', 'thinkai' ),
'ready-header' => esc_html__( 'All done. Have fun!', 'thinkai' ),
/* translators: Theme Author */
'ready%s' => esc_html__( 'Your theme has been all set up. Enjoy your new theme by %s.', 'thinkai' ),
'ready-action-link' => esc_html__( 'Extras', 'thinkai' ),
'ready-big-button' => esc_html__( 'View your website', 'thinkai' ),
'ready-link-1' => sprintf( '<a href="%1$s" target="_blank">%2$s</a>', 'https://themeforest.net/user/themekalia/', esc_html__( '5 Star For Project Thinkai', 'thinkai' ) ),
'ready-link-2' => sprintf( '<a href="%1$s" target="_blank">%2$s</a>', 'https://themekalia.com/support', esc_html__( 'Get Thinkai Theme Support', 'thinkai' ) ),
)
);

View File

@@ -0,0 +1,172 @@
<?php
/**
* Available filters for extending Merlin WP.
*
* @package Merlin WP
* @version @@pkg.version
* @link https://merlinwp.com/
* @author Rich Tabor, from ThemeBeans.com & the team at ProteusThemes.com
* @copyright Copyright (c) 2018, Merlin WP of Inventionn LLC
* @license Licensed GPLv3 for Open Source Use
*/
/**
* Add your widget area to unset the default widgets from.
* If your theme's first widget area is "sidebar-1", you don't need this.
*
* @see https://stackoverflow.com/questions/11757461/how-to-populate-widgets-on-sidebar-on-theme-activation
*
* @param array $widget_areas Arguments for the sidebars_widgets widget areas.
*
* @return array of arguments to update the sidebars_widgets option.
*/
function thinkai_unset_default_widgets_args( $widget_areas ) {
$widget_areas = array(
'default-sidebar' => array(),
);
return $widget_areas;
}
add_filter( 'merlin_unset_default_widgets_args', 'thinkai_unset_default_widgets_args' );
/**
* Custom content for the generated child theme's functions.php file.
*
* @param string $output Generated content.
* @param string $slug Parent theme slug.
*/
function thinkai_child_functions_php( $output, $slug ) {
$slug_no_hyphens = strtolower( preg_replace( '#[^a-zA-Z]#', '', $slug ) );
$output = "
<?php
/**
* Theme functions and definitions.
*/
function {$slug_no_hyphens}_child_enqueue_styles() {
if ( SCRIPT_DEBUG ) {
wp_enqueue_style( '{$slug}-style' , get_template_directory_uri() . '/style.css' );
} else {
wp_enqueue_style( '{$slug}-minified-style' , get_template_directory_uri() . '/style.css' );
}
wp_enqueue_style( '{$slug}-child-style',
get_stylesheet_directory_uri() . '/style.css',
array( '{$slug}-style' ),
wp_get_theme()->get('Version')
);
}
add_action( 'wp_enqueue_scripts', '{$slug_no_hyphens}_child_enqueue_styles' );\n
";
// Let's remove the tabs so that it displays nicely.
$output = trim( preg_replace( '/\t+/', '', $output ) );
// Filterable return.
return $output;
}
add_filter( 'merlin_generate_child_functions_php', 'thinkai_child_functions_php', 10, 2 );
/**
* Define the demo import files (local files).
* You have to use the same filter as in above example,
* but with a slightly different array keys: local_*.
* The values have to be absolute paths (not URLs) to your import files.
* To use local import files, that reside in your theme folder,
* please use the below code.
* Note: make sure your import files are readable!
*/
function thinkai_local_import_files() {
return array(
array(
'import_file_name' => esc_html__('Main Demo', 'thinkai'),
'local_import_widget_file' => trailingslashit( get_template_directory() ) . 'demo-import/content/widgets.json',
//'import_rev_slider_file_url' => trailingslashit( get_template_directory_uri() ) . 'demo-import/content/home.zip',
'local_import_redux' => array(
array(
'file_path' => trailingslashit( get_template_directory() ) . 'demo-import/content/redux_options.json',
'option_name' => 'thinkai_options',
),
),
'local_import_file' => trailingslashit( get_template_directory() ) . 'demo-import/content/content.xml',
'import_preview_image_url' => get_template_directory_uri() . '/screenshot.png',
'import_notice' => esc_html__( 'After you import this demo, you will have to setup the slider separately.', 'thinkai' ),
'preview_url' => 'http://commonsupport.net/newwp/thinkai/',
),
);
}
add_filter( 'merlin_import_files', 'thinkai_local_import_files' );
/**
* Execute custom code after the whole import has finished.
*/
function thinkai_after_import_setup() {
// Assign menus to their locations.
$main_menu = get_term_by( 'name', 'Main Menu', 'nav_menu' );
$footer_menu = get_term_by( 'name', 'Footer Menu', 'nav_menu' );
set_theme_mod(
'nav_menu_locations', array(
'main_menu' => $main_menu->term_id,
'footer_menu' => $footer_menu->term_id,
)
);
// Assign front page and posts page (blog page).
$front_page_id = get_page_by_title( 'Image Generator' );
$blog_page_id = get_page_by_title( 'List Large Image' );
$shop_page_id = get_page_by_title( 'My Shop' );
$cart_page_id = get_page_by_title( 'My Cart' );
$checkout_page_id = get_page_by_title( 'My Checkout' );
$myaccount_page_id = get_page_by_title( 'My account' );
update_option( 'show_on_front', 'page' );
update_option( 'page_on_front', $front_page_id->ID );
update_option( 'page_for_posts', $blog_page_id->ID );
update_option( 'woocommerce_shop_page_id', $shop_page_id->ID );
update_option( 'woocommerce_cart_page_id', $cart_page_id->ID );
update_option( 'woocommerce_checkout_page_id', $checkout_page_id->ID );
update_option( 'woocommerce_myaccount_page_id', $myaccount_page_id->ID );
$logo = get_page_by_title( 'logo', OBJECT, 'attachment' );
if( $logo ) {
set_theme_mod( 'custom_logo', $logo->ID );
}
/*
if( class_exists('RevSliderSliderImport') ) {
foreach(array('home', 'home-1') as $slider) {
$file = get_template_directory() . '/demo-import/content/'.$slider.'.zip';
if( file_exists($file) ) {
$importer = new RevSliderSliderImport();
$response = $importer->import_slider( true, $file );
}
}
}
*/
/*$header = get_page_by_title( 'header', OBJECT, 'elementor_library' );
if( $header ) {
$meta = get_post_meta($header->ID, '_elementor_data', true);
if( $meta && $main_menu) {
$meta = json_decode($meta, true);
if(isset($meta[0]['elements'][0]['elements'][1]['elements'][1]['elements'])) {
$meta[0]['elements'][0]['elements'][1]['elements'][1]['elements'][0]['settings']['wp']['nav_menu'] = $main_menu->term_id;
update_post_meta( $header->ID, '_elementor_data', $meta );
}
}
}*/
}
add_action( 'merlin_after_all_import', 'thinkai_after_import_setup' );

View File

@@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit04828d80146b9fd8fbee50609a30f85a::getLoader();

View File

@@ -0,0 +1,445 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath.'\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,12 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'ProteusThemes\\WPContentImporter2\\' => array($vendorDir . '/proteusthemes/wp-content-importer-v2/src'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
);

View File

@@ -0,0 +1,52 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit04828d80146b9fd8fbee50609a30f85a
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit04828d80146b9fd8fbee50609a30f85a', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit04828d80146b9fd8fbee50609a30f85a', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit04828d80146b9fd8fbee50609a30f85a::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}

View File

@@ -0,0 +1,44 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit04828d80146b9fd8fbee50609a30f85a
{
public static $prefixLengthsPsr4 = array (
'P' =>
array (
'Psr\\Log\\' => 8,
'ProteusThemes\\WPContentImporter2\\' => 33,
),
'M' =>
array (
'Monolog\\' => 8,
),
);
public static $prefixDirsPsr4 = array (
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
'ProteusThemes\\WPContentImporter2\\' =>
array (
0 => __DIR__ . '/..' . '/proteusthemes/wp-content-importer-v2/src',
),
'Monolog\\' =>
array (
0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit04828d80146b9fd8fbee50609a30f85a::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit04828d80146b9fd8fbee50609a30f85a::$prefixDirsPsr4;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1,182 @@
[
{
"name": "monolog/monolog",
"version": "1.23.0",
"version_normalized": "1.23.0.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0"
},
"provide": {
"psr/log-implementation": "1.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"graylog2/gelf-php": "~1.0",
"jakub-onderka/php-parallel-lint": "0.9",
"php-amqplib/php-amqplib": "~2.4",
"php-console/php-console": "^3.1.3",
"phpunit/phpunit": "~4.5",
"phpunit/phpunit-mock-objects": "2.3.0",
"ruflin/elastica": ">=0.90 <3.0",
"sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "^5.3|^6.0"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-console/php-console": "Allow sending log messages to Google Chrome",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"sentry/sentry": "Allow sending log messages to a Sentry server"
},
"time": "2017-06-19T01:22:40+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "http://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
]
},
{
"name": "proteusthemes/wp-content-importer-v2",
"version": "v2.1.0",
"version_normalized": "2.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/proteusthemes/WordPress-Importer.git",
"reference": "7414ce1bfb56a8be780c520ed63e99cac7ac403b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/proteusthemes/WordPress-Importer/zipball/7414ce1bfb56a8be780c520ed63e99cac7ac403b",
"reference": "7414ce1bfb56a8be780c520ed63e99cac7ac403b",
"shasum": ""
},
"time": "2018-04-13T07:31:18+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"ProteusThemes\\WPContentImporter2\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0+"
],
"authors": [
{
"name": "Primoz Cigler",
"email": "primoz@proteusnet.com"
},
{
"name": "Gregor Capuder",
"email": "capuderg@gmail.com"
},
{
"name": "Humanmade contributors",
"homepage": "https://github.com/humanmade/WordPress-Importer/graphs/contributors"
}
],
"description": "Improved WP content importer used in OCDI plugin and MerlinWP.",
"keywords": [
"content",
"import",
"proteusthemes",
"theme",
"wordpress",
"wp"
]
},
{
"name": "psr/log",
"version": "1.0.2",
"version_normalized": "1.0.2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2016-10-10T12:19:37+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
]
}
]

View File

@@ -0,0 +1,342 @@
### 1.23.0 (2017-06-19)
* Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument
* Fixed GelfHandler truncation to be per field and not per message
* Fixed compatibility issue with PHP <5.3.6
* Fixed support for headless Chrome in ChromePHPHandler
* Fixed support for latest Aws SDK in DynamoDbHandler
* Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler
### 1.22.1 (2017-03-13)
* Fixed lots of minor issues in the new Slack integrations
* Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces
### 1.22.0 (2016-11-26)
* Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily
* Added MercurialProcessor to add mercurial revision and branch names to log records
* Added support for AWS SDK v3 in DynamoDbHandler
* Fixed fatal errors occuring when normalizing generators that have been fully consumed
* Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix)
* Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore
* Fixed SyslogUdpHandler to avoid sending empty frames
* Fixed a few PHP 7.0 and 7.1 compatibility issues
### 1.21.0 (2016-07-29)
* Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues
* Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order
* Added ability to format the main line of text the SlackHandler sends by explictly setting a formatter on the handler
* Added information about SoapFault instances in NormalizerFormatter
* Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level
### 1.20.0 (2016-07-02)
* Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy
* Added StreamHandler::getUrl to retrieve the stream's URL
* Added ability to override addRow/addTitle in HtmlFormatter
* Added the $context to context information when the ErrorHandler handles a regular php error
* Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d
* Fixed WhatFailureGroupHandler to work with PHP7 throwables
* Fixed a few minor bugs
### 1.19.0 (2016-04-12)
* Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed
* Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors
* Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler
* Fixed HipChatHandler handling of long messages
### 1.18.2 (2016-04-02)
* Fixed ElasticaFormatter to use more precise dates
* Fixed GelfMessageFormatter sending too long messages
### 1.18.1 (2016-03-13)
* Fixed SlackHandler bug where slack dropped messages randomly
* Fixed RedisHandler issue when using with the PHPRedis extension
* Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension
* Fixed BrowserConsoleHandler regression
### 1.18.0 (2016-03-01)
* Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond
* Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames
* Added `Logger->withName` to clone a logger (keeping all handlers) with a new name
* Added FluentdFormatter for the Fluentd unix socket protocol
* Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed
* Added support for replacing context sub-keys using `%context.*%` in LineFormatter
* Added support for `payload` context value in RollbarHandler
* Added setRelease to RavenHandler to describe the application version, sent with every log
* Added support for `fingerprint` context value in RavenHandler
* Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed
* Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()`
* Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places
### 1.17.2 (2015-10-14)
* Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers
* Fixed SlackHandler handling to use slack functionalities better
* Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id
* Fixed 5.3 compatibility regression
### 1.17.1 (2015-08-31)
* Fixed RollbarHandler triggering PHP notices
### 1.17.0 (2015-08-30)
* Added support for `checksum` and `release` context/extra values in RavenHandler
* Added better support for exceptions in RollbarHandler
* Added UidProcessor::getUid
* Added support for showing the resource type in NormalizedFormatter
* Fixed IntrospectionProcessor triggering PHP notices
### 1.16.0 (2015-08-09)
* Added IFTTTHandler to notify ifttt.com triggers
* Added Logger::setHandlers() to allow setting/replacing all handlers
* Added $capSize in RedisHandler to cap the log size
* Fixed StreamHandler creation of directory to only trigger when the first log write happens
* Fixed bug in the handling of curl failures
* Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler
* Fixed missing fatal errors records with handlers that need to be closed to flush log records
* Fixed TagProcessor::addTags support for associative arrays
### 1.15.0 (2015-07-12)
* Added addTags and setTags methods to change a TagProcessor
* Added automatic creation of directories if they are missing for a StreamHandler to open a log file
* Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure
* Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used
* Fixed HTML/JS escaping in BrowserConsoleHandler
* Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only)
### 1.14.0 (2015-06-19)
* Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library
* Added support for objects implementing __toString in the NormalizerFormatter
* Added support for HipChat's v2 API in HipChatHandler
* Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app
* Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true)
* Fixed curl errors being silently suppressed
### 1.13.1 (2015-03-09)
* Fixed regression in HipChat requiring a new token to be created
### 1.13.0 (2015-03-05)
* Added Registry::hasLogger to check for the presence of a logger instance
* Added context.user support to RavenHandler
* Added HipChat API v2 support in the HipChatHandler
* Added NativeMailerHandler::addParameter to pass params to the mail() process
* Added context data to SlackHandler when $includeContextAndExtra is true
* Added ability to customize the Swift_Message per-email in SwiftMailerHandler
* Fixed SwiftMailerHandler to lazily create message instances if a callback is provided
* Fixed serialization of INF and NaN values in Normalizer and LineFormatter
### 1.12.0 (2014-12-29)
* Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers.
* Added PsrHandler to forward records to another PSR-3 logger
* Added SamplingHandler to wrap around a handler and include only every Nth record
* Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now)
* Added exception codes in the output of most formatters
* Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line)
* Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data
* Added $host to HipChatHandler for users of private instances
* Added $transactionName to NewRelicHandler and support for a transaction_name context value
* Fixed MandrillHandler to avoid outputing API call responses
* Fixed some non-standard behaviors in SyslogUdpHandler
### 1.11.0 (2014-09-30)
* Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names
* Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails
* Added MandrillHandler to send emails via the Mandrillapp.com API
* Added SlackHandler to log records to a Slack.com account
* Added FleepHookHandler to log records to a Fleep.io account
* Added LogglyHandler::addTag to allow adding tags to an existing handler
* Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end
* Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing
* Added support for PhpAmqpLib in the AmqpHandler
* Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs
* Added support for adding extra fields from $_SERVER in the WebProcessor
* Fixed support for non-string values in PrsLogMessageProcessor
* Fixed SwiftMailer messages being sent with the wrong date in long running scripts
* Fixed minor PHP 5.6 compatibility issues
* Fixed BufferHandler::close being called twice
### 1.10.0 (2014-06-04)
* Added Logger::getHandlers() and Logger::getProcessors() methods
* Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached
* Added support for extra data in NewRelicHandler
* Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines
### 1.9.1 (2014-04-24)
* Fixed regression in RotatingFileHandler file permissions
* Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records
* Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative
### 1.9.0 (2014-04-20)
* Added LogEntriesHandler to send logs to a LogEntries account
* Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler
* Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes
* Added support for table formatting in FirePHPHandler via the table context key
* Added a TagProcessor to add tags to records, and support for tags in RavenHandler
* Added $appendNewline flag to the JsonFormatter to enable using it when logging to files
* Added sound support to the PushoverHandler
* Fixed multi-threading support in StreamHandler
* Fixed empty headers issue when ChromePHPHandler received no records
* Fixed default format of the ErrorLogHandler
### 1.8.0 (2014-03-23)
* Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them
* Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output
* Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler
* Added FlowdockHandler to send logs to a Flowdock account
* Added RollbarHandler to send logs to a Rollbar account
* Added HtmlFormatter to send prettier log emails with colors for each log level
* Added GitProcessor to add the current branch/commit to extra record data
* Added a Monolog\Registry class to allow easier global access to pre-configured loggers
* Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement
* Added support for HHVM
* Added support for Loggly batch uploads
* Added support for tweaking the content type and encoding in NativeMailerHandler
* Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor
* Fixed batch request support in GelfHandler
### 1.7.0 (2013-11-14)
* Added ElasticSearchHandler to send logs to an Elastic Search server
* Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB
* Added SyslogUdpHandler to send logs to a remote syslogd server
* Added LogglyHandler to send logs to a Loggly account
* Added $level to IntrospectionProcessor so it only adds backtraces when needed
* Added $version to LogstashFormatter to allow using the new v1 Logstash format
* Added $appName to NewRelicHandler
* Added configuration of Pushover notification retries/expiry
* Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default
* Added chainability to most setters for all handlers
* Fixed RavenHandler batch processing so it takes the message from the record with highest priority
* Fixed HipChatHandler batch processing so it sends all messages at once
* Fixed issues with eAccelerator
* Fixed and improved many small things
### 1.6.0 (2013-07-29)
* Added HipChatHandler to send logs to a HipChat chat room
* Added ErrorLogHandler to send logs to PHP's error_log function
* Added NewRelicHandler to send logs to NewRelic's service
* Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler
* Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel
* Added stack traces output when normalizing exceptions (json output & co)
* Added Monolog\Logger::API constant (currently 1)
* Added support for ChromePHP's v4.0 extension
* Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel
* Added support for sending messages to multiple users at once with the PushoverHandler
* Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler)
* Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now
* Fixed issue in RotatingFileHandler when an open_basedir restriction is active
* Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0
* Fixed SyslogHandler issue when many were used concurrently with different facilities
### 1.5.0 (2013-04-23)
* Added ProcessIdProcessor to inject the PID in log records
* Added UidProcessor to inject a unique identifier to all log records of one request/run
* Added support for previous exceptions in the LineFormatter exception serialization
* Added Monolog\Logger::getLevels() to get all available levels
* Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle
### 1.4.1 (2013-04-01)
* Fixed exception formatting in the LineFormatter to be more minimalistic
* Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0
* Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days
* Fixed WebProcessor array access so it checks for data presence
* Fixed Buffer, Group and FingersCrossed handlers to make use of their processors
### 1.4.0 (2013-02-13)
* Added RedisHandler to log to Redis via the Predis library or the phpredis extension
* Added ZendMonitorHandler to log to the Zend Server monitor
* Added the possibility to pass arrays of handlers and processors directly in the Logger constructor
* Added `$useSSL` option to the PushoverHandler which is enabled by default
* Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously
* Fixed header injection capability in the NativeMailHandler
### 1.3.1 (2013-01-11)
* Fixed LogstashFormatter to be usable with stream handlers
* Fixed GelfMessageFormatter levels on Windows
### 1.3.0 (2013-01-08)
* Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface`
* Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance
* Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash)
* Added PushoverHandler to send mobile notifications
* Added CouchDBHandler and DoctrineCouchDBHandler
* Added RavenHandler to send data to Sentry servers
* Added support for the new MongoClient class in MongoDBHandler
* Added microsecond precision to log records' timestamps
* Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing
the oldest entries
* Fixed normalization of objects with cyclic references
### 1.2.1 (2012-08-29)
* Added new $logopts arg to SyslogHandler to provide custom openlog options
* Fixed fatal error in SyslogHandler
### 1.2.0 (2012-08-18)
* Added AmqpHandler (for use with AMQP servers)
* Added CubeHandler
* Added NativeMailerHandler::addHeader() to send custom headers in mails
* Added the possibility to specify more than one recipient in NativeMailerHandler
* Added the possibility to specify float timeouts in SocketHandler
* Added NOTICE and EMERGENCY levels to conform with RFC 5424
* Fixed the log records to use the php default timezone instead of UTC
* Fixed BufferHandler not being flushed properly on PHP fatal errors
* Fixed normalization of exotic resource types
* Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog
### 1.1.0 (2012-04-23)
* Added Monolog\Logger::isHandling() to check if a handler will
handle the given log level
* Added ChromePHPHandler
* Added MongoDBHandler
* Added GelfHandler (for use with Graylog2 servers)
* Added SocketHandler (for use with syslog-ng for example)
* Added NormalizerFormatter
* Added the possibility to change the activation strategy of the FingersCrossedHandler
* Added possibility to show microseconds in logs
* Added `server` and `referer` to WebProcessor output
### 1.0.2 (2011-10-24)
* Fixed bug in IE with large response headers and FirePHPHandler
### 1.0.1 (2011-08-25)
* Added MemoryPeakUsageProcessor and MemoryUsageProcessor
* Added Monolog\Logger::getName() to get a logger's channel name
### 1.0.0 (2011-07-06)
* Added IntrospectionProcessor to get info from where the logger was called
* Fixed WebProcessor in CLI
### 1.0.0-RC1 (2011-07-01)
* Initial release

View File

@@ -0,0 +1,19 @@
Copyright (c) 2011-2016 Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,95 @@
# Monolog - Logging for PHP [![Build Status](https://img.shields.io/travis/Seldaek/monolog.svg)](https://travis-ci.org/Seldaek/monolog)
[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)
[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)
[![Reference Status](https://www.versioneye.com/php/monolog:monolog/reference_badge.svg)](https://www.versioneye.com/php/monolog:monolog/references)
Monolog sends your logs to files, sockets, inboxes, databases and various
web services. See the complete list of handlers below. Special handlers
allow you to build advanced logging strategies.
This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
interface that you can type-hint against in your own libraries to keep
a maximum of interoperability. You can also use it in your applications to
make sure you can always use another compatible logger at a later time.
As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels.
Internally Monolog still uses its own level scheme since it predates PSR-3.
## Installation
Install the latest version with
```bash
$ composer require monolog/monolog
```
## Basic Usage
```php
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
// add records to the log
$log->addWarning('Foo');
$log->addError('Bar');
```
## Documentation
- [Usage Instructions](doc/01-usage.md)
- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md)
- [Utility classes](doc/03-utilities.md)
- [Extending Monolog](doc/04-extending.md)
## Third Party Packages
Third party handlers, formatters and processors are
[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You
can also add your own there if you publish one.
## About
### Requirements
- Monolog works with PHP 5.3 or above, and is also tested to work with HHVM.
### Submitting bugs and feature requests
Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues)
### Framework Integrations
- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
can be used very easily with Monolog since it implements the interface.
- [Symfony2](http://symfony.com) comes out of the box with Monolog.
- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog.
- [Laravel 4 & 5](http://laravel.com/) come out of the box with Monolog.
- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog.
- [PPI](http://www.ppi.io/) comes out of the box with Monolog.
- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin.
- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer.
- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog.
- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog.
- [Nette Framework](http://nette.org/en/) can be used with Monolog via [Kdyby/Monolog](https://github.com/Kdyby/Monolog) extension.
- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog.
### Author
Jordi Boggiano - <j.boggiano@seld.be> - <http://twitter.com/seldaek><br />
See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project.
### License
Monolog is licensed under the MIT License - see the `LICENSE` file for details
### Acknowledgements
This library is heavily inspired by Python's [Logbook](http://packages.python.org/Logbook/)
library, although most concepts have been adjusted to fit to the PHP world.

View File

@@ -0,0 +1,66 @@
{
"name": "monolog/monolog",
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"keywords": ["log", "logging", "psr-3"],
"homepage": "http://github.com/Seldaek/monolog",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.5",
"graylog2/gelf-php": "~1.0",
"sentry/sentry": "^0.13",
"ruflin/elastica": ">=0.90 <3.0",
"doctrine/couchdb": "~1.0@dev",
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"php-amqplib/php-amqplib": "~2.4",
"swiftmailer/swiftmailer": "^5.3|^6.0",
"php-console/php-console": "^3.1.3",
"phpunit/phpunit-mock-objects": "2.3.0",
"jakub-onderka/php-parallel-lint": "0.9"
},
"_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223 - needs hhvm 3.8+ on travis",
"suggest": {
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"sentry/sentry": "Allow sending log messages to a Sentry server",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"php-console/php-console": "Allow sending log messages to Google Chrome"
},
"autoload": {
"psr-4": {"Monolog\\": "src/Monolog"}
},
"autoload-dev": {
"psr-4": {"Monolog\\": "tests/Monolog"}
},
"provide": {
"psr/log-implementation": "1.0.0"
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"scripts": {
"test": [
"parallel-lint . --exclude vendor",
"phpunit"
]
}
}

View File

@@ -0,0 +1,230 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Monolog\Handler\AbstractHandler;
/**
* Monolog error handler
*
* A facility to enable logging of runtime errors, exceptions and fatal errors.
*
* Quick setup: <code>ErrorHandler::register($logger);</code>
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ErrorHandler
{
private $logger;
private $previousExceptionHandler;
private $uncaughtExceptionLevel;
private $previousErrorHandler;
private $errorLevelMap;
private $handleOnlyReportedErrors;
private $hasFatalErrorHandler;
private $fatalLevel;
private $reservedMemory;
private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Registers a new ErrorHandler for a given Logger
*
* By default it will handle errors, exceptions and fatal errors
*
* @param LoggerInterface $logger
* @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
* @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling
* @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling
* @return ErrorHandler
*/
public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null)
{
//Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929
class_exists('\\Psr\\Log\\LogLevel', true);
$handler = new static($logger);
if ($errorLevelMap !== false) {
$handler->registerErrorHandler($errorLevelMap);
}
if ($exceptionLevel !== false) {
$handler->registerExceptionHandler($exceptionLevel);
}
if ($fatalLevel !== false) {
$handler->registerFatalHandler($fatalLevel);
}
return $handler;
}
public function registerExceptionHandler($level = null, $callPrevious = true)
{
$prev = set_exception_handler(array($this, 'handleException'));
$this->uncaughtExceptionLevel = $level;
if ($callPrevious && $prev) {
$this->previousExceptionHandler = $prev;
}
}
public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true)
{
$prev = set_error_handler(array($this, 'handleError'), $errorTypes);
$this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
if ($callPrevious) {
$this->previousErrorHandler = $prev ?: true;
}
$this->handleOnlyReportedErrors = $handleOnlyReportedErrors;
}
public function registerFatalHandler($level = null, $reservedMemorySize = 20)
{
register_shutdown_function(array($this, 'handleFatalError'));
$this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
$this->fatalLevel = $level;
$this->hasFatalErrorHandler = true;
}
protected function defaultErrorLevelMap()
{
return array(
E_ERROR => LogLevel::CRITICAL,
E_WARNING => LogLevel::WARNING,
E_PARSE => LogLevel::ALERT,
E_NOTICE => LogLevel::NOTICE,
E_CORE_ERROR => LogLevel::CRITICAL,
E_CORE_WARNING => LogLevel::WARNING,
E_COMPILE_ERROR => LogLevel::ALERT,
E_COMPILE_WARNING => LogLevel::WARNING,
E_USER_ERROR => LogLevel::ERROR,
E_USER_WARNING => LogLevel::WARNING,
E_USER_NOTICE => LogLevel::NOTICE,
E_STRICT => LogLevel::NOTICE,
E_RECOVERABLE_ERROR => LogLevel::ERROR,
E_DEPRECATED => LogLevel::NOTICE,
E_USER_DEPRECATED => LogLevel::NOTICE,
);
}
/**
* @private
*/
public function handleException($e)
{
$this->logger->log(
$this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel,
sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()),
array('exception' => $e)
);
if ($this->previousExceptionHandler) {
call_user_func($this->previousExceptionHandler, $e);
}
exit(255);
}
/**
* @private
*/
public function handleError($code, $message, $file = '', $line = 0, $context = array())
{
if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) {
return;
}
// fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
$level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL;
$this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line));
}
if ($this->previousErrorHandler === true) {
return false;
} elseif ($this->previousErrorHandler) {
return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context);
}
}
/**
* @private
*/
public function handleFatalError()
{
$this->reservedMemory = null;
$lastError = error_get_last();
if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) {
$this->logger->log(
$this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel,
'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'])
);
if ($this->logger instanceof Logger) {
foreach ($this->logger->getHandlers() as $handler) {
if ($handler instanceof AbstractHandler) {
$handler->close();
}
}
}
}
}
private static function codeToString($code)
{
switch ($code) {
case E_ERROR:
return 'E_ERROR';
case E_WARNING:
return 'E_WARNING';
case E_PARSE:
return 'E_PARSE';
case E_NOTICE:
return 'E_NOTICE';
case E_CORE_ERROR:
return 'E_CORE_ERROR';
case E_CORE_WARNING:
return 'E_CORE_WARNING';
case E_COMPILE_ERROR:
return 'E_COMPILE_ERROR';
case E_COMPILE_WARNING:
return 'E_COMPILE_WARNING';
case E_USER_ERROR:
return 'E_USER_ERROR';
case E_USER_WARNING:
return 'E_USER_WARNING';
case E_USER_NOTICE:
return 'E_USER_NOTICE';
case E_STRICT:
return 'E_STRICT';
case E_RECOVERABLE_ERROR:
return 'E_RECOVERABLE_ERROR';
case E_DEPRECATED:
return 'E_DEPRECATED';
case E_USER_DEPRECATED:
return 'E_USER_DEPRECATED';
}
return 'Unknown PHP error';
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
/**
* Formats a log message according to the ChromePHP array format
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ChromePHPFormatter implements FormatterInterface
{
/**
* Translates Monolog log levels to Wildfire levels.
*/
private $logLevels = array(
Logger::DEBUG => 'log',
Logger::INFO => 'info',
Logger::NOTICE => 'info',
Logger::WARNING => 'warn',
Logger::ERROR => 'error',
Logger::CRITICAL => 'error',
Logger::ALERT => 'error',
Logger::EMERGENCY => 'error',
);
/**
* {@inheritdoc}
*/
public function format(array $record)
{
// Retrieve the line and file if set and remove them from the formatted extra
$backtrace = 'unknown';
if (isset($record['extra']['file'], $record['extra']['line'])) {
$backtrace = $record['extra']['file'].' : '.$record['extra']['line'];
unset($record['extra']['file'], $record['extra']['line']);
}
$message = array('message' => $record['message']);
if ($record['context']) {
$message['context'] = $record['context'];
}
if ($record['extra']) {
$message['extra'] = $record['extra'];
}
if (count($message) === 1) {
$message = reset($message);
}
return array(
$record['channel'],
$message,
$backtrace,
$this->logLevels[$record['level']],
);
}
public function formatBatch(array $records)
{
$formatted = array();
foreach ($records as $record) {
$formatted[] = $this->format($record);
}
return $formatted;
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Elastica\Document;
/**
* Format a log message into an Elastica Document
*
* @author Jelle Vink <jelle.vink@gmail.com>
*/
class ElasticaFormatter extends NormalizerFormatter
{
/**
* @var string Elastic search index name
*/
protected $index;
/**
* @var string Elastic search document type
*/
protected $type;
/**
* @param string $index Elastic Search index name
* @param string $type Elastic Search document type
*/
public function __construct($index, $type)
{
// elasticsearch requires a ISO 8601 format date with optional millisecond precision.
parent::__construct('Y-m-d\TH:i:s.uP');
$this->index = $index;
$this->type = $type;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$record = parent::format($record);
return $this->getDocument($record);
}
/**
* Getter index
* @return string
*/
public function getIndex()
{
return $this->index;
}
/**
* Getter type
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Convert a log message into an Elastica Document
*
* @param array $record Log message
* @return Document
*/
protected function getDocument($record)
{
$document = new Document();
$document->setData($record);
$document->setType($this->type);
$document->setIndex($this->index);
return $document;
}
}

View File

@@ -0,0 +1,116 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* formats the record to be used in the FlowdockHandler
*
* @author Dominik Liebler <liebler.dominik@gmail.com>
*/
class FlowdockFormatter implements FormatterInterface
{
/**
* @var string
*/
private $source;
/**
* @var string
*/
private $sourceEmail;
/**
* @param string $source
* @param string $sourceEmail
*/
public function __construct($source, $sourceEmail)
{
$this->source = $source;
$this->sourceEmail = $sourceEmail;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$tags = array(
'#logs',
'#' . strtolower($record['level_name']),
'#' . $record['channel'],
);
foreach ($record['extra'] as $value) {
$tags[] = '#' . $value;
}
$subject = sprintf(
'in %s: %s - %s',
$this->source,
$record['level_name'],
$this->getShortMessage($record['message'])
);
$record['flowdock'] = array(
'source' => $this->source,
'from_address' => $this->sourceEmail,
'subject' => $subject,
'content' => $record['message'],
'tags' => $tags,
'project' => $this->source,
);
return $record;
}
/**
* {@inheritdoc}
*/
public function formatBatch(array $records)
{
$formatted = array();
foreach ($records as $record) {
$formatted[] = $this->format($record);
}
return $formatted;
}
/**
* @param string $message
*
* @return string
*/
public function getShortMessage($message)
{
static $hasMbString;
if (null === $hasMbString) {
$hasMbString = function_exists('mb_strlen');
}
$maxLength = 45;
if ($hasMbString) {
if (mb_strlen($message, 'UTF-8') > $maxLength) {
$message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...';
}
} else {
if (strlen($message) > $maxLength) {
$message = substr($message, 0, $maxLength - 4) . ' ...';
}
}
return $message;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Class FluentdFormatter
*
* Serializes a log message to Fluentd unix socket protocol
*
* Fluentd config:
*
* <source>
* type unix
* path /var/run/td-agent/td-agent.sock
* </source>
*
* Monolog setup:
*
* $logger = new Monolog\Logger('fluent.tag');
* $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock');
* $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter());
* $logger->pushHandler($fluentHandler);
*
* @author Andrius Putna <fordnox@gmail.com>
*/
class FluentdFormatter implements FormatterInterface
{
/**
* @var bool $levelTag should message level be a part of the fluentd tag
*/
protected $levelTag = false;
public function __construct($levelTag = false)
{
if (!function_exists('json_encode')) {
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter');
}
$this->levelTag = (bool) $levelTag;
}
public function isUsingLevelsInTag()
{
return $this->levelTag;
}
public function format(array $record)
{
$tag = $record['channel'];
if ($this->levelTag) {
$tag .= '.' . strtolower($record['level_name']);
}
$message = array(
'message' => $record['message'],
'extra' => $record['extra'],
);
if (!$this->levelTag) {
$message['level'] = $record['level'];
$message['level_name'] = $record['level_name'];
}
return json_encode(array($tag, $record['datetime']->getTimestamp(), $message));
}
public function formatBatch(array $records)
{
$message = '';
foreach ($records as $record) {
$message .= $this->format($record);
}
return $message;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Interface for formatters
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface FormatterInterface
{
/**
* Formats a log record.
*
* @param array $record A record to format
* @return mixed The formatted record
*/
public function format(array $record);
/**
* Formats a set of log records.
*
* @param array $records A set of records to format
* @return mixed The formatted set of records
*/
public function formatBatch(array $records);
}

View File

@@ -0,0 +1,138 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
use Gelf\Message;
/**
* Serializes a log message to GELF
* @see http://www.graylog2.org/about/gelf
*
* @author Matt Lehner <mlehner@gmail.com>
*/
class GelfMessageFormatter extends NormalizerFormatter
{
const DEFAULT_MAX_LENGTH = 32766;
/**
* @var string the name of the system for the Gelf log message
*/
protected $systemName;
/**
* @var string a prefix for 'extra' fields from the Monolog record (optional)
*/
protected $extraPrefix;
/**
* @var string a prefix for 'context' fields from the Monolog record (optional)
*/
protected $contextPrefix;
/**
* @var int max length per field
*/
protected $maxLength;
/**
* Translates Monolog log levels to Graylog2 log priorities.
*/
private $logLevels = array(
Logger::DEBUG => 7,
Logger::INFO => 6,
Logger::NOTICE => 5,
Logger::WARNING => 4,
Logger::ERROR => 3,
Logger::CRITICAL => 2,
Logger::ALERT => 1,
Logger::EMERGENCY => 0,
);
public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $maxLength = null)
{
parent::__construct('U.u');
$this->systemName = $systemName ?: gethostname();
$this->extraPrefix = $extraPrefix;
$this->contextPrefix = $contextPrefix;
$this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$record = parent::format($record);
if (!isset($record['datetime'], $record['message'], $record['level'])) {
throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given');
}
$message = new Message();
$message
->setTimestamp($record['datetime'])
->setShortMessage((string) $record['message'])
->setHost($this->systemName)
->setLevel($this->logLevels[$record['level']]);
// message length + system name length + 200 for padding / metadata
$len = 200 + strlen((string) $record['message']) + strlen($this->systemName);
if ($len > $this->maxLength) {
$message->setShortMessage(substr($record['message'], 0, $this->maxLength));
}
if (isset($record['channel'])) {
$message->setFacility($record['channel']);
}
if (isset($record['extra']['line'])) {
$message->setLine($record['extra']['line']);
unset($record['extra']['line']);
}
if (isset($record['extra']['file'])) {
$message->setFile($record['extra']['file']);
unset($record['extra']['file']);
}
foreach ($record['extra'] as $key => $val) {
$val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
$len = strlen($this->extraPrefix . $key . $val);
if ($len > $this->maxLength) {
$message->setAdditional($this->extraPrefix . $key, substr($val, 0, $this->maxLength));
break;
}
$message->setAdditional($this->extraPrefix . $key, $val);
}
foreach ($record['context'] as $key => $val) {
$val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
$len = strlen($this->contextPrefix . $key . $val);
if ($len > $this->maxLength) {
$message->setAdditional($this->contextPrefix . $key, substr($val, 0, $this->maxLength));
break;
}
$message->setAdditional($this->contextPrefix . $key, $val);
}
if (null === $message->getFile() && isset($record['context']['exception']['file'])) {
if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) {
$message->setFile($matches[1]);
$message->setLine($matches[2]);
}
}
return $message;
}
}

View File

@@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
/**
* Formats incoming records into an HTML table
*
* This is especially useful for html email logging
*
* @author Tiago Brito <tlfbrito@gmail.com>
*/
class HtmlFormatter extends NormalizerFormatter
{
/**
* Translates Monolog log levels to html color priorities.
*/
protected $logLevels = array(
Logger::DEBUG => '#cccccc',
Logger::INFO => '#468847',
Logger::NOTICE => '#3a87ad',
Logger::WARNING => '#c09853',
Logger::ERROR => '#f0ad4e',
Logger::CRITICAL => '#FF7708',
Logger::ALERT => '#C12A19',
Logger::EMERGENCY => '#000000',
);
/**
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
*/
public function __construct($dateFormat = null)
{
parent::__construct($dateFormat);
}
/**
* Creates an HTML table row
*
* @param string $th Row header content
* @param string $td Row standard cell content
* @param bool $escapeTd false if td content must not be html escaped
* @return string
*/
protected function addRow($th, $td = ' ', $escapeTd = true)
{
$th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8');
if ($escapeTd) {
$td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>';
}
return "<tr style=\"padding: 4px;spacing: 0;text-align: left;\">\n<th style=\"background: #cccccc\" width=\"100px\">$th:</th>\n<td style=\"padding: 4px;spacing: 0;text-align: left;background: #eeeeee\">".$td."</td>\n</tr>";
}
/**
* Create a HTML h1 tag
*
* @param string $title Text to be in the h1
* @param int $level Error level
* @return string
*/
protected function addTitle($title, $level)
{
$title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');
return '<h1 style="background: '.$this->logLevels[$level].';color: #ffffff;padding: 5px;" class="monolog-output">'.$title.'</h1>';
}
/**
* Formats a log record.
*
* @param array $record A record to format
* @return mixed The formatted record
*/
public function format(array $record)
{
$output = $this->addTitle($record['level_name'], $record['level']);
$output .= '<table cellspacing="1" width="100%" class="monolog-output">';
$output .= $this->addRow('Message', (string) $record['message']);
$output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat));
$output .= $this->addRow('Channel', $record['channel']);
if ($record['context']) {
$embeddedTable = '<table cellspacing="1" width="100%">';
foreach ($record['context'] as $key => $value) {
$embeddedTable .= $this->addRow($key, $this->convertToString($value));
}
$embeddedTable .= '</table>';
$output .= $this->addRow('Context', $embeddedTable, false);
}
if ($record['extra']) {
$embeddedTable = '<table cellspacing="1" width="100%">';
foreach ($record['extra'] as $key => $value) {
$embeddedTable .= $this->addRow($key, $this->convertToString($value));
}
$embeddedTable .= '</table>';
$output .= $this->addRow('Extra', $embeddedTable, false);
}
return $output.'</table>';
}
/**
* Formats a set of log records.
*
* @param array $records A set of records to format
* @return mixed The formatted set of records
*/
public function formatBatch(array $records)
{
$message = '';
foreach ($records as $record) {
$message .= $this->format($record);
}
return $message;
}
protected function convertToString($data)
{
if (null === $data || is_scalar($data)) {
return (string) $data;
}
$data = $this->normalize($data);
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return str_replace('\\/', '/', json_encode($data));
}
}

View File

@@ -0,0 +1,208 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Exception;
use Throwable;
/**
* Encodes whatever record data is passed to it as json
*
* This can be useful to log to databases or remote APIs
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class JsonFormatter extends NormalizerFormatter
{
const BATCH_MODE_JSON = 1;
const BATCH_MODE_NEWLINES = 2;
protected $batchMode;
protected $appendNewline;
/**
* @var bool
*/
protected $includeStacktraces = false;
/**
* @param int $batchMode
* @param bool $appendNewline
*/
public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true)
{
$this->batchMode = $batchMode;
$this->appendNewline = $appendNewline;
}
/**
* The batch mode option configures the formatting style for
* multiple records. By default, multiple records will be
* formatted as a JSON-encoded array. However, for
* compatibility with some API endpoints, alternative styles
* are available.
*
* @return int
*/
public function getBatchMode()
{
return $this->batchMode;
}
/**
* True if newlines are appended to every formatted record
*
* @return bool
*/
public function isAppendingNewlines()
{
return $this->appendNewline;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
}
/**
* {@inheritdoc}
*/
public function formatBatch(array $records)
{
switch ($this->batchMode) {
case static::BATCH_MODE_NEWLINES:
return $this->formatBatchNewlines($records);
case static::BATCH_MODE_JSON:
default:
return $this->formatBatchJson($records);
}
}
/**
* @param bool $include
*/
public function includeStacktraces($include = true)
{
$this->includeStacktraces = $include;
}
/**
* Return a JSON-encoded array of records.
*
* @param array $records
* @return string
*/
protected function formatBatchJson(array $records)
{
return $this->toJson($this->normalize($records), true);
}
/**
* Use new lines to separate records instead of a
* JSON-encoded array.
*
* @param array $records
* @return string
*/
protected function formatBatchNewlines(array $records)
{
$instance = $this;
$oldNewline = $this->appendNewline;
$this->appendNewline = false;
array_walk($records, function (&$value, $key) use ($instance) {
$value = $instance->format($value);
});
$this->appendNewline = $oldNewline;
return implode("\n", $records);
}
/**
* Normalizes given $data.
*
* @param mixed $data
*
* @return mixed
*/
protected function normalize($data)
{
if (is_array($data) || $data instanceof \Traversable) {
$normalized = array();
$count = 1;
foreach ($data as $key => $value) {
if ($count++ >= 1000) {
$normalized['...'] = 'Over 1000 items, aborting normalization';
break;
}
$normalized[$key] = $this->normalize($value);
}
return $normalized;
}
if ($data instanceof Exception || $data instanceof Throwable) {
return $this->normalizeException($data);
}
return $data;
}
/**
* Normalizes given exception with or without its own stack trace based on
* `includeStacktraces` property.
*
* @param Exception|Throwable $e
*
* @return array
*/
protected function normalizeException($e)
{
// TODO 2.0 only check for Throwable
if (!$e instanceof Exception && !$e instanceof Throwable) {
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
}
$data = array(
'class' => get_class($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile().':'.$e->getLine(),
);
if ($this->includeStacktraces) {
$trace = $e->getTrace();
foreach ($trace as $frame) {
if (isset($frame['file'])) {
$data['trace'][] = $frame['file'].':'.$frame['line'];
} elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
// We should again normalize the frames, because it might contain invalid items
$data['trace'][] = $frame['function'];
} else {
// We should again normalize the frames, because it might contain invalid items
$data['trace'][] = $this->normalize($frame);
}
}
}
if ($previous = $e->getPrevious()) {
$data['previous'] = $this->normalizeException($previous);
}
return $data;
}
}

View File

@@ -0,0 +1,179 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Formats incoming records into a one-line string
*
* This is especially useful for logging to files
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*/
class LineFormatter extends NormalizerFormatter
{
const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
protected $format;
protected $allowInlineLineBreaks;
protected $ignoreEmptyContextAndExtra;
protected $includeStacktraces;
/**
* @param string $format The format of the message
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
* @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
* @param bool $ignoreEmptyContextAndExtra
*/
public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false)
{
$this->format = $format ?: static::SIMPLE_FORMAT;
$this->allowInlineLineBreaks = $allowInlineLineBreaks;
$this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
parent::__construct($dateFormat);
}
public function includeStacktraces($include = true)
{
$this->includeStacktraces = $include;
if ($this->includeStacktraces) {
$this->allowInlineLineBreaks = true;
}
}
public function allowInlineLineBreaks($allow = true)
{
$this->allowInlineLineBreaks = $allow;
}
public function ignoreEmptyContextAndExtra($ignore = true)
{
$this->ignoreEmptyContextAndExtra = $ignore;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$vars = parent::format($record);
$output = $this->format;
foreach ($vars['extra'] as $var => $val) {
if (false !== strpos($output, '%extra.'.$var.'%')) {
$output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
unset($vars['extra'][$var]);
}
}
foreach ($vars['context'] as $var => $val) {
if (false !== strpos($output, '%context.'.$var.'%')) {
$output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
unset($vars['context'][$var]);
}
}
if ($this->ignoreEmptyContextAndExtra) {
if (empty($vars['context'])) {
unset($vars['context']);
$output = str_replace('%context%', '', $output);
}
if (empty($vars['extra'])) {
unset($vars['extra']);
$output = str_replace('%extra%', '', $output);
}
}
foreach ($vars as $var => $val) {
if (false !== strpos($output, '%'.$var.'%')) {
$output = str_replace('%'.$var.'%', $this->stringify($val), $output);
}
}
// remove leftover %extra.xxx% and %context.xxx% if any
if (false !== strpos($output, '%')) {
$output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
}
return $output;
}
public function formatBatch(array $records)
{
$message = '';
foreach ($records as $record) {
$message .= $this->format($record);
}
return $message;
}
public function stringify($value)
{
return $this->replaceNewlines($this->convertToString($value));
}
protected function normalizeException($e)
{
// TODO 2.0 only check for Throwable
if (!$e instanceof \Exception && !$e instanceof \Throwable) {
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
}
$previousText = '';
if ($previous = $e->getPrevious()) {
do {
$previousText .= ', '.get_class($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine();
} while ($previous = $previous->getPrevious());
}
$str = '[object] ('.get_class($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')';
if ($this->includeStacktraces) {
$str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n";
}
return $str;
}
protected function convertToString($data)
{
if (null === $data || is_bool($data)) {
return var_export($data, true);
}
if (is_scalar($data)) {
return (string) $data;
}
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return $this->toJson($data, true);
}
return str_replace('\\/', '/', @json_encode($data));
}
protected function replaceNewlines($str)
{
if ($this->allowInlineLineBreaks) {
if (0 === strpos($str, '{')) {
return str_replace(array('\r', '\n'), array("\r", "\n"), $str);
}
return $str;
}
return str_replace(array("\r\n", "\r", "\n"), ' ', $str);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Encodes message information into JSON in a format compatible with Loggly.
*
* @author Adam Pancutt <adam@pancutt.com>
*/
class LogglyFormatter extends JsonFormatter
{
/**
* Overrides the default batch mode to new lines for compatibility with the
* Loggly bulk API.
*
* @param int $batchMode
*/
public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false)
{
parent::__construct($batchMode, $appendNewline);
}
/**
* Appends the 'timestamp' parameter for indexing by Loggly.
*
* @see https://www.loggly.com/docs/automated-parsing/#json
* @see \Monolog\Formatter\JsonFormatter::format()
*/
public function format(array $record)
{
if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) {
$record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO");
// TODO 2.0 unset the 'datetime' parameter, retained for BC
}
return parent::format($record);
}
}

View File

@@ -0,0 +1,166 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Serializes a log message to Logstash Event Format
*
* @see http://logstash.net/
* @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb
*
* @author Tim Mower <timothy.mower@gmail.com>
*/
class LogstashFormatter extends NormalizerFormatter
{
const V0 = 0;
const V1 = 1;
/**
* @var string the name of the system for the Logstash log message, used to fill the @source field
*/
protected $systemName;
/**
* @var string an application name for the Logstash log message, used to fill the @type field
*/
protected $applicationName;
/**
* @var string a prefix for 'extra' fields from the Monolog record (optional)
*/
protected $extraPrefix;
/**
* @var string a prefix for 'context' fields from the Monolog record (optional)
*/
protected $contextPrefix;
/**
* @var int logstash format version to use
*/
protected $version;
/**
* @param string $applicationName the application that sends the data, used as the "type" field of logstash
* @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine
* @param string $extraPrefix prefix for extra keys inside logstash "fields"
* @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_
* @param int $version the logstash format version to use, defaults to 0
*/
public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0)
{
// logstash requires a ISO 8601 format date with optional millisecond precision.
parent::__construct('Y-m-d\TH:i:s.uP');
$this->systemName = $systemName ?: gethostname();
$this->applicationName = $applicationName;
$this->extraPrefix = $extraPrefix;
$this->contextPrefix = $contextPrefix;
$this->version = $version;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$record = parent::format($record);
if ($this->version === self::V1) {
$message = $this->formatV1($record);
} else {
$message = $this->formatV0($record);
}
return $this->toJson($message) . "\n";
}
protected function formatV0(array $record)
{
if (empty($record['datetime'])) {
$record['datetime'] = gmdate('c');
}
$message = array(
'@timestamp' => $record['datetime'],
'@source' => $this->systemName,
'@fields' => array(),
);
if (isset($record['message'])) {
$message['@message'] = $record['message'];
}
if (isset($record['channel'])) {
$message['@tags'] = array($record['channel']);
$message['@fields']['channel'] = $record['channel'];
}
if (isset($record['level'])) {
$message['@fields']['level'] = $record['level'];
}
if ($this->applicationName) {
$message['@type'] = $this->applicationName;
}
if (isset($record['extra']['server'])) {
$message['@source_host'] = $record['extra']['server'];
}
if (isset($record['extra']['url'])) {
$message['@source_path'] = $record['extra']['url'];
}
if (!empty($record['extra'])) {
foreach ($record['extra'] as $key => $val) {
$message['@fields'][$this->extraPrefix . $key] = $val;
}
}
if (!empty($record['context'])) {
foreach ($record['context'] as $key => $val) {
$message['@fields'][$this->contextPrefix . $key] = $val;
}
}
return $message;
}
protected function formatV1(array $record)
{
if (empty($record['datetime'])) {
$record['datetime'] = gmdate('c');
}
$message = array(
'@timestamp' => $record['datetime'],
'@version' => 1,
'host' => $this->systemName,
);
if (isset($record['message'])) {
$message['message'] = $record['message'];
}
if (isset($record['channel'])) {
$message['type'] = $record['channel'];
$message['channel'] = $record['channel'];
}
if (isset($record['level_name'])) {
$message['level'] = $record['level_name'];
}
if ($this->applicationName) {
$message['type'] = $this->applicationName;
}
if (!empty($record['extra'])) {
foreach ($record['extra'] as $key => $val) {
$message[$this->extraPrefix . $key] = $val;
}
}
if (!empty($record['context'])) {
foreach ($record['context'] as $key => $val) {
$message[$this->contextPrefix . $key] = $val;
}
}
return $message;
}
}

View File

@@ -0,0 +1,105 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Formats a record for use with the MongoDBHandler.
*
* @author Florian Plattner <me@florianplattner.de>
*/
class MongoDBFormatter implements FormatterInterface
{
private $exceptionTraceAsString;
private $maxNestingLevel;
/**
* @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2
* @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings
*/
public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true)
{
$this->maxNestingLevel = max($maxNestingLevel, 0);
$this->exceptionTraceAsString = (bool) $exceptionTraceAsString;
}
/**
* {@inheritDoc}
*/
public function format(array $record)
{
return $this->formatArray($record);
}
/**
* {@inheritDoc}
*/
public function formatBatch(array $records)
{
foreach ($records as $key => $record) {
$records[$key] = $this->format($record);
}
return $records;
}
protected function formatArray(array $record, $nestingLevel = 0)
{
if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) {
foreach ($record as $name => $value) {
if ($value instanceof \DateTime) {
$record[$name] = $this->formatDate($value, $nestingLevel + 1);
} elseif ($value instanceof \Exception) {
$record[$name] = $this->formatException($value, $nestingLevel + 1);
} elseif (is_array($value)) {
$record[$name] = $this->formatArray($value, $nestingLevel + 1);
} elseif (is_object($value)) {
$record[$name] = $this->formatObject($value, $nestingLevel + 1);
}
}
} else {
$record = '[...]';
}
return $record;
}
protected function formatObject($value, $nestingLevel)
{
$objectVars = get_object_vars($value);
$objectVars['class'] = get_class($value);
return $this->formatArray($objectVars, $nestingLevel);
}
protected function formatException(\Exception $exception, $nestingLevel)
{
$formattedException = array(
'class' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile() . ':' . $exception->getLine(),
);
if ($this->exceptionTraceAsString === true) {
$formattedException['trace'] = $exception->getTraceAsString();
} else {
$formattedException['trace'] = $exception->getTrace();
}
return $this->formatArray($formattedException, $nestingLevel);
}
protected function formatDate(\DateTime $value, $nestingLevel)
{
return new \MongoDate($value->getTimestamp());
}
}

View File

@@ -0,0 +1,297 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Exception;
/**
* Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class NormalizerFormatter implements FormatterInterface
{
const SIMPLE_DATE = "Y-m-d H:i:s";
protected $dateFormat;
/**
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
*/
public function __construct($dateFormat = null)
{
$this->dateFormat = $dateFormat ?: static::SIMPLE_DATE;
if (!function_exists('json_encode')) {
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter');
}
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
return $this->normalize($record);
}
/**
* {@inheritdoc}
*/
public function formatBatch(array $records)
{
foreach ($records as $key => $record) {
$records[$key] = $this->format($record);
}
return $records;
}
protected function normalize($data)
{
if (null === $data || is_scalar($data)) {
if (is_float($data)) {
if (is_infinite($data)) {
return ($data > 0 ? '' : '-') . 'INF';
}
if (is_nan($data)) {
return 'NaN';
}
}
return $data;
}
if (is_array($data)) {
$normalized = array();
$count = 1;
foreach ($data as $key => $value) {
if ($count++ >= 1000) {
$normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
break;
}
$normalized[$key] = $this->normalize($value);
}
return $normalized;
}
if ($data instanceof \DateTime) {
return $data->format($this->dateFormat);
}
if (is_object($data)) {
// TODO 2.0 only check for Throwable
if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) {
return $this->normalizeException($data);
}
// non-serializable objects that implement __toString stringified
if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) {
$value = $data->__toString();
} else {
// the rest is json-serialized in some way
$value = $this->toJson($data, true);
}
return sprintf("[object] (%s: %s)", get_class($data), $value);
}
if (is_resource($data)) {
return sprintf('[resource] (%s)', get_resource_type($data));
}
return '[unknown('.gettype($data).')]';
}
protected function normalizeException($e)
{
// TODO 2.0 only check for Throwable
if (!$e instanceof Exception && !$e instanceof \Throwable) {
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
}
$data = array(
'class' => get_class($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile().':'.$e->getLine(),
);
if ($e instanceof \SoapFault) {
if (isset($e->faultcode)) {
$data['faultcode'] = $e->faultcode;
}
if (isset($e->faultactor)) {
$data['faultactor'] = $e->faultactor;
}
if (isset($e->detail)) {
$data['detail'] = $e->detail;
}
}
$trace = $e->getTrace();
foreach ($trace as $frame) {
if (isset($frame['file'])) {
$data['trace'][] = $frame['file'].':'.$frame['line'];
} elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
// We should again normalize the frames, because it might contain invalid items
$data['trace'][] = $frame['function'];
} else {
// We should again normalize the frames, because it might contain invalid items
$data['trace'][] = $this->toJson($this->normalize($frame), true);
}
}
if ($previous = $e->getPrevious()) {
$data['previous'] = $this->normalizeException($previous);
}
return $data;
}
/**
* Return the JSON representation of a value
*
* @param mixed $data
* @param bool $ignoreErrors
* @throws \RuntimeException if encoding fails and errors are not ignored
* @return string
*/
protected function toJson($data, $ignoreErrors = false)
{
// suppress json_encode errors since it's twitchy with some inputs
if ($ignoreErrors) {
return $this->jsonEncode($data);
}
$json = $this->jsonEncode($data);
if ($json === false) {
$json = $this->handleJsonError(json_last_error(), $data);
}
return $json;
}
/**
* @param mixed $data
* @return string JSON encoded data or null on failure
*/
private function jsonEncode($data)
{
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return json_encode($data);
}
/**
* Handle a json_encode failure.
*
* If the failure is due to invalid string encoding, try to clean the
* input and encode again. If the second encoding attempt fails, the
* inital error is not encoding related or the input can't be cleaned then
* raise a descriptive exception.
*
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @throws \RuntimeException if failure can't be corrected
* @return string JSON encoded data after error correction
*/
private function handleJsonError($code, $data)
{
if ($code !== JSON_ERROR_UTF8) {
$this->throwEncodeError($code, $data);
}
if (is_string($data)) {
$this->detectAndCleanUtf8($data);
} elseif (is_array($data)) {
array_walk_recursive($data, array($this, 'detectAndCleanUtf8'));
} else {
$this->throwEncodeError($code, $data);
}
$json = $this->jsonEncode($data);
if ($json === false) {
$this->throwEncodeError(json_last_error(), $data);
}
return $json;
}
/**
* Throws an exception according to a given code with a customized message
*
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @throws \RuntimeException
*/
private function throwEncodeError($code, $data)
{
switch ($code) {
case JSON_ERROR_DEPTH:
$msg = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Unexpected control character found';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$msg = 'Unknown error';
}
throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));
}
/**
* Detect invalid UTF-8 string characters and convert to valid UTF-8.
*
* Valid UTF-8 input will be left unmodified, but strings containing
* invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed
* original encoding of ISO-8859-15. This conversion may result in
* incorrect output if the actual encoding was not ISO-8859-15, but it
* will be clean UTF-8 output and will not rely on expensive and fragile
* detection algorithms.
*
* Function converts the input in place in the passed variable so that it
* can be used as a callback for array_walk_recursive.
*
* @param mixed &$data Input to check and convert if needed
* @private
*/
public function detectAndCleanUtf8(&$data)
{
if (is_string($data) && !preg_match('//u', $data)) {
$data = preg_replace_callback(
'/[\x80-\xFF]+/',
function ($m) { return utf8_encode($m[0]); },
$data
);
$data = str_replace(
array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'),
array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'),
$data
);
}
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Formats data into an associative array of scalar values.
* Objects and arrays will be JSON encoded.
*
* @author Andrew Lawson <adlawson@gmail.com>
*/
class ScalarFormatter extends NormalizerFormatter
{
/**
* {@inheritdoc}
*/
public function format(array $record)
{
foreach ($record as $key => $value) {
$record[$key] = $this->normalizeValue($value);
}
return $record;
}
/**
* @param mixed $value
* @return mixed
*/
protected function normalizeValue($value)
{
$normalized = $this->normalize($value);
if (is_array($normalized) || is_object($normalized)) {
return $this->toJson($normalized, true);
}
return $normalized;
}
}

View File

@@ -0,0 +1,113 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
/**
* Serializes a log message according to Wildfire's header requirements
*
* @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
* @author Christophe Coevoet <stof@notk.org>
* @author Kirill chEbba Chebunin <iam@chebba.org>
*/
class WildfireFormatter extends NormalizerFormatter
{
const TABLE = 'table';
/**
* Translates Monolog log levels to Wildfire levels.
*/
private $logLevels = array(
Logger::DEBUG => 'LOG',
Logger::INFO => 'INFO',
Logger::NOTICE => 'INFO',
Logger::WARNING => 'WARN',
Logger::ERROR => 'ERROR',
Logger::CRITICAL => 'ERROR',
Logger::ALERT => 'ERROR',
Logger::EMERGENCY => 'ERROR',
);
/**
* {@inheritdoc}
*/
public function format(array $record)
{
// Retrieve the line and file if set and remove them from the formatted extra
$file = $line = '';
if (isset($record['extra']['file'])) {
$file = $record['extra']['file'];
unset($record['extra']['file']);
}
if (isset($record['extra']['line'])) {
$line = $record['extra']['line'];
unset($record['extra']['line']);
}
$record = $this->normalize($record);
$message = array('message' => $record['message']);
$handleError = false;
if ($record['context']) {
$message['context'] = $record['context'];
$handleError = true;
}
if ($record['extra']) {
$message['extra'] = $record['extra'];
$handleError = true;
}
if (count($message) === 1) {
$message = reset($message);
}
if (isset($record['context'][self::TABLE])) {
$type = 'TABLE';
$label = $record['channel'] .': '. $record['message'];
$message = $record['context'][self::TABLE];
} else {
$type = $this->logLevels[$record['level']];
$label = $record['channel'];
}
// Create JSON object describing the appearance of the message in the console
$json = $this->toJson(array(
array(
'Type' => $type,
'File' => $file,
'Line' => $line,
'Label' => $label,
),
$message,
), $handleError);
// The message itself is a serialization of the above JSON object + it's length
return sprintf(
'%s|%s|',
strlen($json),
$json
);
}
public function formatBatch(array $records)
{
throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter');
}
protected function normalize($data)
{
if (is_object($data) && !$data instanceof \DateTime) {
return $data;
}
return parent::normalize($data);
}
}

View File

@@ -0,0 +1,186 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
/**
* Base Handler class providing the Handler structure
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
abstract class AbstractHandler implements HandlerInterface
{
protected $level = Logger::DEBUG;
protected $bubble = true;
/**
* @var FormatterInterface
*/
protected $formatter;
protected $processors = array();
/**
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
{
$this->setLevel($level);
$this->bubble = $bubble;
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return $record['level'] >= $this->level;
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
foreach ($records as $record) {
$this->handle($record);
}
}
/**
* Closes the handler.
*
* This will be called automatically when the object is destroyed
*/
public function close()
{
}
/**
* {@inheritdoc}
*/
public function pushProcessor($callback)
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');
}
array_unshift($this->processors, $callback);
return $this;
}
/**
* {@inheritdoc}
*/
public function popProcessor()
{
if (!$this->processors) {
throw new \LogicException('You tried to pop from an empty processor stack.');
}
return array_shift($this->processors);
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
$this->formatter = $formatter;
return $this;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
if (!$this->formatter) {
$this->formatter = $this->getDefaultFormatter();
}
return $this->formatter;
}
/**
* Sets minimum logging level at which this handler will be triggered.
*
* @param int|string $level Level or level name
* @return self
*/
public function setLevel($level)
{
$this->level = Logger::toMonologLevel($level);
return $this;
}
/**
* Gets minimum logging level at which this handler will be triggered.
*
* @return int
*/
public function getLevel()
{
return $this->level;
}
/**
* Sets the bubbling behavior.
*
* @param Boolean $bubble true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @return self
*/
public function setBubble($bubble)
{
$this->bubble = $bubble;
return $this;
}
/**
* Gets the bubbling behavior.
*
* @return Boolean true means that this handler allows bubbling.
* false means that bubbling is not permitted.
*/
public function getBubble()
{
return $this->bubble;
}
public function __destruct()
{
try {
$this->close();
} catch (\Exception $e) {
// do nothing
} catch (\Throwable $e) {
// do nothing
}
}
/**
* Gets the default formatter.
*
* @return FormatterInterface
*/
protected function getDefaultFormatter()
{
return new LineFormatter();
}
}

View File

@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
/**
* Base Handler class providing the Handler structure
*
* Classes extending it should (in most cases) only implement write($record)
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*/
abstract class AbstractProcessingHandler extends AbstractHandler
{
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if (!$this->isHandling($record)) {
return false;
}
$record = $this->processRecord($record);
$record['formatted'] = $this->getFormatter()->format($record);
$this->write($record);
return false === $this->bubble;
}
/**
* Writes the record down to the log of the implementing handler
*
* @param array $record
* @return void
*/
abstract protected function write(array $record);
/**
* Processes a record.
*
* @param array $record
* @return array
*/
protected function processRecord(array $record)
{
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
return $record;
}
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
/**
* Common syslog functionality
*/
abstract class AbstractSyslogHandler extends AbstractProcessingHandler
{
protected $facility;
/**
* Translates Monolog log levels to syslog log priorities.
*/
protected $logLevels = array(
Logger::DEBUG => LOG_DEBUG,
Logger::INFO => LOG_INFO,
Logger::NOTICE => LOG_NOTICE,
Logger::WARNING => LOG_WARNING,
Logger::ERROR => LOG_ERR,
Logger::CRITICAL => LOG_CRIT,
Logger::ALERT => LOG_ALERT,
Logger::EMERGENCY => LOG_EMERG,
);
/**
* List of valid log facility names.
*/
protected $facilities = array(
'auth' => LOG_AUTH,
'authpriv' => LOG_AUTHPRIV,
'cron' => LOG_CRON,
'daemon' => LOG_DAEMON,
'kern' => LOG_KERN,
'lpr' => LOG_LPR,
'mail' => LOG_MAIL,
'news' => LOG_NEWS,
'syslog' => LOG_SYSLOG,
'user' => LOG_USER,
'uucp' => LOG_UUCP,
);
/**
* @param mixed $facility
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->facilities['local0'] = LOG_LOCAL0;
$this->facilities['local1'] = LOG_LOCAL1;
$this->facilities['local2'] = LOG_LOCAL2;
$this->facilities['local3'] = LOG_LOCAL3;
$this->facilities['local4'] = LOG_LOCAL4;
$this->facilities['local5'] = LOG_LOCAL5;
$this->facilities['local6'] = LOG_LOCAL6;
$this->facilities['local7'] = LOG_LOCAL7;
} else {
$this->facilities['local0'] = 128; // LOG_LOCAL0
$this->facilities['local1'] = 136; // LOG_LOCAL1
$this->facilities['local2'] = 144; // LOG_LOCAL2
$this->facilities['local3'] = 152; // LOG_LOCAL3
$this->facilities['local4'] = 160; // LOG_LOCAL4
$this->facilities['local5'] = 168; // LOG_LOCAL5
$this->facilities['local6'] = 176; // LOG_LOCAL6
$this->facilities['local7'] = 184; // LOG_LOCAL7
}
// convert textual description of facility to syslog constant
if (array_key_exists(strtolower($facility), $this->facilities)) {
$facility = $this->facilities[strtolower($facility)];
} elseif (!in_array($facility, array_values($this->facilities), true)) {
throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given');
}
$this->facility = $facility;
}
/**
* {@inheritdoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%');
}
}

View File

@@ -0,0 +1,148 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\JsonFormatter;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Channel\AMQPChannel;
use AMQPExchange;
class AmqpHandler extends AbstractProcessingHandler
{
/**
* @var AMQPExchange|AMQPChannel $exchange
*/
protected $exchange;
/**
* @var string
*/
protected $exchangeName;
/**
* @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use
* @param string $exchangeName
* @param int $level
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true)
{
if ($exchange instanceof AMQPExchange) {
$exchange->setName($exchangeName);
} elseif ($exchange instanceof AMQPChannel) {
$this->exchangeName = $exchangeName;
} else {
throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required');
}
$this->exchange = $exchange;
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$data = $record["formatted"];
$routingKey = $this->getRoutingKey($record);
if ($this->exchange instanceof AMQPExchange) {
$this->exchange->publish(
$data,
$routingKey,
0,
array(
'delivery_mode' => 2,
'content_type' => 'application/json',
)
);
} else {
$this->exchange->basic_publish(
$this->createAmqpMessage($data),
$this->exchangeName,
$routingKey
);
}
}
/**
* {@inheritDoc}
*/
public function handleBatch(array $records)
{
if ($this->exchange instanceof AMQPExchange) {
parent::handleBatch($records);
return;
}
foreach ($records as $record) {
if (!$this->isHandling($record)) {
continue;
}
$record = $this->processRecord($record);
$data = $this->getFormatter()->format($record);
$this->exchange->batch_basic_publish(
$this->createAmqpMessage($data),
$this->exchangeName,
$this->getRoutingKey($record)
);
}
$this->exchange->publish_batch();
}
/**
* Gets the routing key for the AMQP exchange
*
* @param array $record
* @return string
*/
protected function getRoutingKey(array $record)
{
$routingKey = sprintf(
'%s.%s',
// TODO 2.0 remove substr call
substr($record['level_name'], 0, 4),
$record['channel']
);
return strtolower($routingKey);
}
/**
* @param string $data
* @return AMQPMessage
*/
private function createAmqpMessage($data)
{
return new AMQPMessage(
(string) $data,
array(
'delivery_mode' => 2,
'content_type' => 'application/json',
)
);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
}
}

View File

@@ -0,0 +1,230 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\LineFormatter;
/**
* Handler sending logs to browser's javascript console with no browser extension required
*
* @author Olivier Poitrey <rs@dailymotion.com>
*/
class BrowserConsoleHandler extends AbstractProcessingHandler
{
protected static $initialized = false;
protected static $records = array();
/**
* {@inheritDoc}
*
* Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format.
*
* Example of formatted string:
*
* You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%');
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
// Accumulate records
self::$records[] = $record;
// Register shutdown handler if not already done
if (!self::$initialized) {
self::$initialized = true;
$this->registerShutdownFunction();
}
}
/**
* Convert records to javascript console commands and send it to the browser.
* This method is automatically called on PHP shutdown if output is HTML or Javascript.
*/
public static function send()
{
$format = self::getResponseFormat();
if ($format === 'unknown') {
return;
}
if (count(self::$records)) {
if ($format === 'html') {
self::writeOutput('<script>' . self::generateScript() . '</script>');
} elseif ($format === 'js') {
self::writeOutput(self::generateScript());
}
self::reset();
}
}
/**
* Forget all logged records
*/
public static function reset()
{
self::$records = array();
}
/**
* Wrapper for register_shutdown_function to allow overriding
*/
protected function registerShutdownFunction()
{
if (PHP_SAPI !== 'cli') {
register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send'));
}
}
/**
* Wrapper for echo to allow overriding
*
* @param string $str
*/
protected static function writeOutput($str)
{
echo wp_kses($str, true);
}
/**
* Checks the format of the response
*
* If Content-Type is set to application/javascript or text/javascript -> js
* If Content-Type is set to text/html, or is unset -> html
* If Content-Type is anything else -> unknown
*
* @return string One of 'js', 'html' or 'unknown'
*/
protected static function getResponseFormat()
{
// Check content type
foreach (headers_list() as $header) {
if (stripos($header, 'content-type:') === 0) {
// This handler only works with HTML and javascript outputs
// text/javascript is obsolete in favour of application/javascript, but still used
if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) {
return 'js';
}
if (stripos($header, 'text/html') === false) {
return 'unknown';
}
break;
}
}
return 'html';
}
private static function generateScript()
{
$script = array();
foreach (self::$records as $record) {
$context = self::dump('Context', $record['context']);
$extra = self::dump('Extra', $record['extra']);
if (empty($context) && empty($extra)) {
$script[] = self::call_array('log', self::handleStyles($record['formatted']));
} else {
$script = array_merge($script,
array(self::call_array('groupCollapsed', self::handleStyles($record['formatted']))),
$context,
$extra,
array(self::call('groupEnd'))
);
}
}
return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);";
}
private static function handleStyles($formatted)
{
$args = array(self::quote('font-weight: normal'));
$format = '%c' . $formatted;
preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
foreach (array_reverse($matches) as $match) {
$args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0]));
$args[] = '"font-weight: normal"';
$pos = $match[0][1];
$format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0]));
}
array_unshift($args, self::quote($format));
return $args;
}
private static function handleCustomStyles($style, $string)
{
static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey');
static $labels = array();
return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) {
if (trim($m[1]) === 'autolabel') {
// Format the string as a label with consistent auto assigned background color
if (!isset($labels[$string])) {
$labels[$string] = $colors[count($labels) % count($colors)];
}
$color = $labels[$string];
return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px";
}
return $m[1];
}, $style);
}
private static function dump($title, array $dict)
{
$script = array();
$dict = array_filter($dict);
if (empty($dict)) {
return $script;
}
$script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title));
foreach ($dict as $key => $value) {
$value = json_encode($value);
if (empty($value)) {
$value = self::quote('');
}
$script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value);
}
return $script;
}
private static function quote($arg)
{
return '"' . addcslashes($arg, "\"\n\\") . '"';
}
private static function call()
{
$args = func_get_args();
$method = array_shift($args);
return self::call_array($method, $args);
}
private static function call_array($method, array $args)
{
return 'c.' . $method . '(' . implode(', ', $args) . ');';
}
}

View File

@@ -0,0 +1,117 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Buffers all records until closing the handler and then pass them as batch.
*
* This is useful for a MailHandler to send only one mail per request instead of
* sending one per log message.
*
* @author Christophe Coevoet <stof@notk.org>
*/
class BufferHandler extends AbstractHandler
{
protected $handler;
protected $bufferSize = 0;
protected $bufferLimit;
protected $flushOnOverflow;
protected $buffer = array();
protected $initialized = false;
/**
* @param HandlerInterface $handler Handler.
* @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param Boolean $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
*/
public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false)
{
parent::__construct($level, $bubble);
$this->handler = $handler;
$this->bufferLimit = (int) $bufferLimit;
$this->flushOnOverflow = $flushOnOverflow;
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($record['level'] < $this->level) {
return false;
}
if (!$this->initialized) {
// __destructor() doesn't get called on Fatal errors
register_shutdown_function(array($this, 'close'));
$this->initialized = true;
}
if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) {
if ($this->flushOnOverflow) {
$this->flush();
} else {
array_shift($this->buffer);
$this->bufferSize--;
}
}
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
$this->buffer[] = $record;
$this->bufferSize++;
return false === $this->bubble;
}
public function flush()
{
if ($this->bufferSize === 0) {
return;
}
$this->handler->handleBatch($this->buffer);
$this->clear();
}
public function __destruct()
{
// suppress the parent behavior since we already have register_shutdown_function()
// to call close(), and the reference contained there will prevent this from being
// GC'd until the end of the request
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->flush();
}
/**
* Clears the buffer without flushing any messages down to the wrapped handler.
*/
public function clear()
{
$this->bufferSize = 0;
$this->buffer = array();
}
}

View File

@@ -0,0 +1,211 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\ChromePHPFormatter;
use Monolog\Logger;
/**
* Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
*
* This also works out of the box with Firefox 43+
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ChromePHPHandler extends AbstractProcessingHandler
{
/**
* Version of the extension
*/
const VERSION = '4.0';
/**
* Header name
*/
const HEADER_NAME = 'X-ChromeLogger-Data';
/**
* Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+)
*/
const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}';
protected static $initialized = false;
/**
* Tracks whether we sent too much data
*
* Chrome limits the headers to 256KB, so when we sent 240KB we stop sending
*
* @var Boolean
*/
protected static $overflowed = false;
protected static $json = array(
'version' => self::VERSION,
'columns' => array('label', 'log', 'backtrace', 'type'),
'rows' => array(),
);
protected static $sendHeaders = true;
/**
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
if (!function_exists('json_encode')) {
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler');
}
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$messages = array();
foreach ($records as $record) {
if ($record['level'] < $this->level) {
continue;
}
$messages[] = $this->processRecord($record);
}
if (!empty($messages)) {
$messages = $this->getFormatter()->formatBatch($messages);
self::$json['rows'] = array_merge(self::$json['rows'], $messages);
$this->send();
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new ChromePHPFormatter();
}
/**
* Creates & sends header for a record
*
* @see sendHeader()
* @see send()
* @param array $record
*/
protected function write(array $record)
{
self::$json['rows'][] = $record['formatted'];
$this->send();
}
/**
* Sends the log header
*
* @see sendHeader()
*/
protected function send()
{
if (self::$overflowed || !self::$sendHeaders) {
return;
}
if (!self::$initialized) {
self::$initialized = true;
self::$sendHeaders = $this->headersAccepted();
if (!self::$sendHeaders) {
return;
}
self::$json['request_uri'] = function_exists('thinkai_server') ? thinkai_server('REQUEST_URI') : '';
}
$json = @json_encode(self::$json);
$data = (function_exists('thinkai_basencode')) ? thinkai_basencode(utf8_encode($json)) : '';
if (strlen($data) > 240 * 1024) {
self::$overflowed = true;
$record = array(
'message' => 'Incomplete logs, chrome header size limit reached',
'context' => array(),
'level' => Logger::WARNING,
'level_name' => Logger::getLevelName(Logger::WARNING),
'channel' => 'monolog',
'datetime' => new \DateTime(),
'extra' => array(),
);
self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record);
$json = @json_encode(self::$json);
$data = (function_exists('thinkai_basencode')) ? thinkai_basencode(utf8_encode($json)) : '';
}
if (trim($data) !== '') {
$this->sendHeader(self::HEADER_NAME, $data);
}
}
/**
* Send header string to the client
*
* @param string $header
* @param string $content
*/
protected function sendHeader($header, $content)
{
if (!headers_sent() && self::$sendHeaders) {
header(sprintf('%s: %s', $header, $content));
}
}
/**
* Verifies if the headers are accepted by the current user agent
*
* @return Boolean
*/
protected function headersAccepted()
{
if (empty(thinkai_get_server('HTTP_USER_AGENT'))) {
return false;
}
return preg_match(self::USER_AGENT_REGEX, thinkai_get_server('HTTP_USER_AGENT') );
}
/**
* BC getter for the sendHeaders property that has been made static
*/
public function __get($property)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
return static::$sendHeaders;
}
/**
* BC setter for the sendHeaders property that has been made static
*/
public function __set($property, $value)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
static::$sendHeaders = $value;
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\JsonFormatter;
use Monolog\Logger;
/**
* CouchDB handler
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class CouchDBHandler extends AbstractProcessingHandler
{
private $options;
public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true)
{
$this->options = array_merge(array(
'host' => 'localhost',
'port' => 5984,
'dbname' => 'logger',
'username' => null,
'password' => null,
), $options);
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$basicAuth = null;
if ($this->options['username']) {
$basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']);
}
$url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname'];
$context = stream_context_create(array(
'http' => array(
'method' => 'POST',
'content' => $record['formatted'],
'ignore_errors' => true,
'max_redirects' => 0,
'header' => 'Content-type: application/json',
),
));
if (false === wp_remote_get($url, $context)) {
throw new \RuntimeException(sprintf('Could not connect to %s', $url));
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
}
}

View File

@@ -0,0 +1,142 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Logs to Cube.
*
* @link http://square.github.com/cube/
* @author Wan Chen <kami@kamisama.me>
*/
class CubeHandler extends AbstractProcessingHandler
{
private $udpConnection;
private $httpConnection;
private $scheme;
private $host;
private $port;
private $acceptedSchemes = array('http', 'udp');
/**
* Create a Cube handler
*
* @throws \UnexpectedValueException when given url is not a valid url.
* A valid url must consist of three parts : protocol://host:port
* Only valid protocols used by Cube are http and udp
*/
public function __construct($url, $level = Logger::DEBUG, $bubble = true)
{
$urlInfo = parse_url($url);
if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) {
throw new \UnexpectedValueException('URL "'.$url.'" is not valid');
}
if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) {
throw new \UnexpectedValueException(
'Invalid protocol (' . $urlInfo['scheme'] . ').'
. ' Valid options are ' . implode(', ', $this->acceptedSchemes));
}
$this->scheme = $urlInfo['scheme'];
$this->host = $urlInfo['host'];
$this->port = $urlInfo['port'];
parent::__construct($level, $bubble);
}
/**
* Establish a connection to an UDP socket
*
* @throws \LogicException when unable to connect to the socket
* @throws MissingExtensionException when there is no socket extension
*/
protected function connectUdp()
{
if (!extension_loaded('sockets')) {
throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler');
}
$this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0);
if (!$this->udpConnection) {
throw new \LogicException('Unable to create a socket');
}
if (!socket_connect($this->udpConnection, $this->host, $this->port)) {
throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);
}
}
/**
* Establish a connection to a http server
* @throws \LogicException when no curl extension
*/
protected function connectHttp()
{
if (!extension_loaded('curl')) {
throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler');
}
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$date = $record['datetime'];
$data = array('time' => $date->format('Y-m-d\TH:i:s.uO'));
unset($record['datetime']);
if (isset($record['context']['type'])) {
$data['type'] = $record['context']['type'];
unset($record['context']['type']);
} else {
$data['type'] = $record['channel'];
}
$data['data'] = $record['context'];
$data['data']['level'] = $record['level'];
if ($this->scheme === 'http') {
$this->writeHttp(json_encode($data));
} else {
$this->writeUdp(json_encode($data));
}
}
private function writeUdp($data)
{
if (!$this->udpConnection) {
$this->connectUdp();
}
socket_send($this->udpConnection, $data, strlen($data), 0);
}
private function writeHttp($data)
{
if (!$this->httpConnection) {
$this->connectHttp();
}
curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen('['.$data.']'),
));
Curl\Util::execute($this->httpConnection, 5, false);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\Curl;
class Util
{
private static $retriableErrorCodes = array(
CURLE_COULDNT_RESOLVE_HOST,
CURLE_COULDNT_CONNECT,
CURLE_HTTP_NOT_FOUND,
CURLE_READ_ERROR,
CURLE_OPERATION_TIMEOUTED,
CURLE_HTTP_POST_ERROR,
CURLE_SSL_CONNECT_ERROR,
);
/**
* Executes a CURL request with optional retries and exception on failure
*
* @param resource $ch curl handler
* @throws \RuntimeException
*/
public static function execute($ch, $retries = 5, $closeAfterDone = true)
{
}
}

View File

@@ -0,0 +1,150 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Simple handler wrapper that deduplicates log records across multiple requests
*
* It also includes the BufferHandler functionality and will buffer
* all messages until the end of the request or flush() is called.
*
* This works by storing all log records' messages above $deduplicationLevel
* to the file specified by $deduplicationStore. When further logs come in at the end of the
* request (or when flush() is called), all those above $deduplicationLevel are checked
* against the existing stored logs. If they match and the timestamps in the stored log is
* not older than $time seconds, the new log record is discarded. If no log record is new, the
* whole data set is discarded.
*
* This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers
* that send messages to people, to avoid spamming with the same message over and over in case of
* a major component failure like a database server being down which makes all requests fail in the
* same way.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class DeduplicationHandler extends BufferHandler
{
/**
* @var string
*/
protected $deduplicationStore;
/**
* @var int
*/
protected $deduplicationLevel;
/**
* @var int
*/
protected $time;
/**
* @var bool
*/
private $gc = false;
/**
* @param HandlerInterface $handler Handler.
* @param string $deduplicationStore The file/path where the deduplication log should be kept
* @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes
* @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true)
{
parent::__construct($handler, 0, Logger::DEBUG, $bubble, false);
$this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore;
$this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel);
$this->time = $time;
}
public function flush()
{
if ($this->bufferSize === 0) {
return;
}
$passthru = null;
foreach ($this->buffer as $record) {
if ($record['level'] >= $this->deduplicationLevel) {
$passthru = $passthru || !$this->isDuplicate($record);
if ($passthru) {
$this->appendRecord($record);
}
}
}
// default of null is valid as well as if no record matches duplicationLevel we just pass through
if ($passthru === true || $passthru === null) {
$this->handler->handleBatch($this->buffer);
}
$this->clear();
if ($this->gc) {
$this->collectLogs();
}
}
private function isDuplicate(array $record)
{
if (!file_exists($this->deduplicationStore)) {
return false;
}
$store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!is_array($store)) {
return false;
}
$yesterday = time() - 86400;
$timestampValidity = $record['datetime']->getTimestamp() - $this->time;
$expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']);
for ($i = count($store) - 1; $i >= 0; $i--) {
list($timestamp, $level, $message) = explode(':', $store[$i], 3);
if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) {
return true;
}
if ($timestamp < $yesterday) {
$this->gc = true;
}
}
return false;
}
private function collectLogs()
{
if (!file_exists($this->deduplicationStore)) {
return false;
}
$validLogs = array();
$this->gc = false;
}
private function appendRecord(array $record)
{
thinkai_filesystem()->put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;
use Doctrine\CouchDB\CouchDBClient;
/**
* CouchDB handler for Doctrine CouchDB ODM
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class DoctrineCouchDBHandler extends AbstractProcessingHandler
{
private $client;
public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true)
{
$this->client = $client;
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$this->client->postDocument($record['formatted']);
}
protected function getDefaultFormatter()
{
return new NormalizerFormatter;
}
}

View File

@@ -0,0 +1,107 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Aws\Sdk;
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Marshaler;
use Monolog\Formatter\ScalarFormatter;
use Monolog\Logger;
/**
* Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/)
*
* @link https://github.com/aws/aws-sdk-php/
* @author Andrew Lawson <adlawson@gmail.com>
*/
class DynamoDbHandler extends AbstractProcessingHandler
{
const DATE_FORMAT = 'Y-m-d\TH:i:s.uO';
/**
* @var DynamoDbClient
*/
protected $client;
/**
* @var string
*/
protected $table;
/**
* @var int
*/
protected $version;
/**
* @var Marshaler
*/
protected $marshaler;
/**
* @param DynamoDbClient $client
* @param string $table
* @param int $level
* @param bool $bubble
*/
public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true)
{
if (defined('Aws\Sdk::VERSION') && version_compare(Sdk::VERSION, '3.0', '>=')) {
$this->version = 3;
$this->marshaler = new Marshaler;
} else {
$this->version = 2;
}
$this->client = $client;
$this->table = $table;
parent::__construct($level, $bubble);
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$filtered = $this->filterEmptyFields($record['formatted']);
if ($this->version === 3) {
$formatted = $this->marshaler->marshalItem($filtered);
} else {
$formatted = $this->client->formatAttributes($filtered);
}
$this->client->putItem(array(
'TableName' => $this->table,
'Item' => $formatted,
));
}
/**
* @param array $record
* @return array
*/
protected function filterEmptyFields(array $record)
{
return array_filter($record, function ($value) {
return !empty($value) || false === $value || 0 === $value;
});
}
/**
* {@inheritdoc}
*/
protected function getDefaultFormatter()
{
return new ScalarFormatter(self::DATE_FORMAT);
}
}

View File

@@ -0,0 +1,128 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\ElasticaFormatter;
use Monolog\Logger;
use Elastica\Client;
use Elastica\Exception\ExceptionInterface;
/**
* Elastic Search handler
*
* Usage example:
*
* $client = new \Elastica\Client();
* $options = array(
* 'index' => 'elastic_index_name',
* 'type' => 'elastic_doc_type',
* );
* $handler = new ElasticSearchHandler($client, $options);
* $log = new Logger('application');
* $log->pushHandler($handler);
*
* @author Jelle Vink <jelle.vink@gmail.com>
*/
class ElasticSearchHandler extends AbstractProcessingHandler
{
/**
* @var Client
*/
protected $client;
/**
* @var array Handler config options
*/
protected $options = array();
/**
* @param Client $client Elastica Client object
* @param array $options Handler configuration
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
$this->client = $client;
$this->options = array_merge(
array(
'index' => 'monolog', // Elastic index name
'type' => 'record', // Elastic document type
'ignore_error' => false, // Suppress Elastica exceptions
),
$options
);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$this->bulkSend(array($record['formatted']));
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
if ($formatter instanceof ElasticaFormatter) {
return parent::setFormatter($formatter);
}
throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter');
}
/**
* Getter options
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new ElasticaFormatter($this->options['index'], $this->options['type']);
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$documents = $this->getFormatter()->formatBatch($records);
$this->bulkSend($documents);
}
/**
* Use Elasticsearch bulk API to send list of documents
* @param array $documents
* @throws \RuntimeException
*/
protected function bulkSend(array $documents)
{
try {
$this->client->addDocuments($documents);
} catch (ExceptionInterface $e) {
if (!$this->options['ignore_error']) {
throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e);
}
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
/**
* Stores to PHP error_log() handler.
*
* @author Elan Ruusamäe <glen@delfi.ee>
*/
class ErrorLogHandler extends AbstractProcessingHandler
{
const OPERATING_SYSTEM = 0;
const SAPI = 4;
protected $messageType;
protected $expandNewlines;
/**
* @param int $messageType Says where the error should go.
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param Boolean $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
*/
public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false)
{
parent::__construct($level, $bubble);
if (false === in_array($messageType, self::getAvailableTypes())) {
$message = sprintf('The given message type "%s" is not supported', print_r($messageType, true));
throw new \InvalidArgumentException($message);
}
$this->messageType = $messageType;
$this->expandNewlines = $expandNewlines;
}
/**
* @return array With all available types
*/
public static function getAvailableTypes()
{
return array(
self::OPERATING_SYSTEM,
self::SAPI,
);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%');
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
if ($this->expandNewlines) {
$lines = preg_split('{[\r\n]+}', (string) $record['formatted']);
foreach ($lines as $line) {
error_log($line, $this->messageType);
}
} else {
error_log((string) $record['formatted'], $this->messageType);
}
}
}

View File

@@ -0,0 +1,140 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Simple handler wrapper that filters records based on a list of levels
*
* It can be configured with an exact list of levels to allow, or a min/max level.
*
* @author Hennadiy Verkh
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class FilterHandler extends AbstractHandler
{
/**
* Handler or factory callable($record, $this)
*
* @var callable|\Monolog\Handler\HandlerInterface
*/
protected $handler;
/**
* Minimum level for logs that are passed to handler
*
* @var int[]
*/
protected $acceptedLevels;
/**
* Whether the messages that are handled can bubble up the stack or not
*
* @var Boolean
*/
protected $bubble;
/**
* @param callable|HandlerInterface $handler Handler or factory callable($record, $this).
* @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided
* @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true)
{
$this->handler = $handler;
$this->bubble = $bubble;
$this->setAcceptedLevels($minLevelOrList, $maxLevel);
if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) {
throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
}
}
/**
* @return array
*/
public function getAcceptedLevels()
{
return array_flip($this->acceptedLevels);
}
/**
* @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided
* @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array
*/
public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY)
{
if (is_array($minLevelOrList)) {
$acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList);
} else {
$minLevelOrList = Logger::toMonologLevel($minLevelOrList);
$maxLevel = Logger::toMonologLevel($maxLevel);
$acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) {
return $level >= $minLevelOrList && $level <= $maxLevel;
}));
}
$this->acceptedLevels = array_flip($acceptedLevels);
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return isset($this->acceptedLevels[$record['level']]);
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if (!$this->isHandling($record)) {
return false;
}
// The same logic as in FingersCrossedHandler
if (!$this->handler instanceof HandlerInterface) {
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
$this->handler->handle($record);
return false === $this->bubble;
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$filtered = array();
foreach ($records as $record) {
if ($this->isHandling($record)) {
$filtered[] = $record;
}
}
$this->handler->handleBatch($filtered);
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\FingersCrossed;
/**
* Interface for activation strategies for the FingersCrossedHandler.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ActivationStrategyInterface
{
/**
* Returns whether the given record activates the handler.
*
* @param array $record
* @return Boolean
*/
public function isHandlerActivated(array $record);
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\FingersCrossed;
use Monolog\Logger;
/**
* Channel and Error level based monolog activation strategy. Allows to trigger activation
* based on level per channel. e.g. trigger activation on level 'ERROR' by default, except
* for records of the 'sql' channel; those should trigger activation on level 'WARN'.
*
* Example:
*
* <code>
* $activationStrategy = new ChannelLevelActivationStrategy(
* Logger::CRITICAL,
* array(
* 'request' => Logger::ALERT,
* 'sensitive' => Logger::ERROR,
* )
* );
* $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy);
* </code>
*
* @author Mike Meessen <netmikey@gmail.com>
*/
class ChannelLevelActivationStrategy implements ActivationStrategyInterface
{
private $defaultActionLevel;
private $channelToActionLevel;
/**
* @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any
* @param array $channelToActionLevel An array that maps channel names to action levels.
*/
public function __construct($defaultActionLevel, $channelToActionLevel = array())
{
$this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel);
$this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel);
}
public function isHandlerActivated(array $record)
{
if (isset($this->channelToActionLevel[$record['channel']])) {
return $record['level'] >= $this->channelToActionLevel[$record['channel']];
}
return $record['level'] >= $this->defaultActionLevel;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\FingersCrossed;
use Monolog\Logger;
/**
* Error level based activation strategy.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ErrorLevelActivationStrategy implements ActivationStrategyInterface
{
private $actionLevel;
public function __construct($actionLevel)
{
$this->actionLevel = Logger::toMonologLevel($actionLevel);
}
public function isHandlerActivated(array $record)
{
return $record['level'] >= $this->actionLevel;
}
}

View File

@@ -0,0 +1,163 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
use Monolog\Logger;
/**
* Buffers all records until a certain level is reached
*
* The advantage of this approach is that you don't get any clutter in your log files.
* Only requests which actually trigger an error (or whatever your actionLevel is) will be
* in the logs, but they will contain all records, not only those above the level threshold.
*
* You can find the various activation strategies in the
* Monolog\Handler\FingersCrossed\ namespace.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class FingersCrossedHandler extends AbstractHandler
{
protected $handler;
protected $activationStrategy;
protected $buffering = true;
protected $bufferSize;
protected $buffer = array();
protected $stopBuffering;
protected $passthruLevel;
/**
* @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler).
* @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action
* @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true)
* @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered
*/
public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null)
{
if (null === $activationStrategy) {
$activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING);
}
// convert simple int activationStrategy to an object
if (!$activationStrategy instanceof ActivationStrategyInterface) {
$activationStrategy = new ErrorLevelActivationStrategy($activationStrategy);
}
$this->handler = $handler;
$this->activationStrategy = $activationStrategy;
$this->bufferSize = $bufferSize;
$this->bubble = $bubble;
$this->stopBuffering = $stopBuffering;
if ($passthruLevel !== null) {
$this->passthruLevel = Logger::toMonologLevel($passthruLevel);
}
if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) {
throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
}
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return true;
}
/**
* Manually activate this logger regardless of the activation strategy
*/
public function activate()
{
if ($this->stopBuffering) {
$this->buffering = false;
}
if (!$this->handler instanceof HandlerInterface) {
$record = end($this->buffer) ?: null;
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
$this->handler->handleBatch($this->buffer);
$this->buffer = array();
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
if ($this->buffering) {
$this->buffer[] = $record;
if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) {
array_shift($this->buffer);
}
if ($this->activationStrategy->isHandlerActivated($record)) {
$this->activate();
}
} else {
$this->handler->handle($record);
}
return false === $this->bubble;
}
/**
* {@inheritdoc}
*/
public function close()
{
if (null !== $this->passthruLevel) {
$level = $this->passthruLevel;
$this->buffer = array_filter($this->buffer, function ($record) use ($level) {
return $record['level'] >= $level;
});
if (count($this->buffer) > 0) {
$this->handler->handleBatch($this->buffer);
$this->buffer = array();
}
}
}
/**
* Resets the state of the handler. Stops forwarding records to the wrapped handler.
*/
public function reset()
{
$this->buffering = true;
}
/**
* Clears the buffer without flushing any messages down to the wrapped handler.
*
* It also resets the handler to its initial buffering state.
*/
public function clear()
{
$this->buffer = array();
$this->reset();
}
}

View File

@@ -0,0 +1,195 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\WildfireFormatter;
/**
* Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol.
*
* @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
*/
class FirePHPHandler extends AbstractProcessingHandler
{
/**
* WildFire JSON header message format
*/
const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2';
/**
* FirePHP structure for parsing messages & their presentation
*/
const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1';
/**
* Must reference a "known" plugin, otherwise headers won't display in FirePHP
*/
const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3';
/**
* Header prefix for Wildfire to recognize & parse headers
*/
const HEADER_PREFIX = 'X-Wf';
/**
* Whether or not Wildfire vendor-specific headers have been generated & sent yet
*/
protected static $initialized = false;
/**
* Shared static message index between potentially multiple handlers
* @var int
*/
protected static $messageIndex = 1;
protected static $sendHeaders = true;
/**
* Base header creation function used by init headers & record headers
*
* @param array $meta Wildfire Plugin, Protocol & Structure Indexes
* @param string $message Log message
* @return array Complete header string ready for the client as key and message as value
*/
protected function createHeader(array $meta, $message)
{
$header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta));
return array($header => $message);
}
/**
* Creates message header from record
*
* @see createHeader()
* @param array $record
* @return string
*/
protected function createRecordHeader(array $record)
{
// Wildfire is extensible to support multiple protocols & plugins in a single request,
// but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake.
return $this->createHeader(
array(1, 1, 1, self::$messageIndex++),
$record['formatted']
);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new WildfireFormatter();
}
/**
* Wildfire initialization headers to enable message parsing
*
* @see createHeader()
* @see sendHeader()
* @return array
*/
protected function getInitHeaders()
{
// Initial payload consists of required headers for Wildfire
return array_merge(
$this->createHeader(array('Protocol', 1), self::PROTOCOL_URI),
$this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI),
$this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI)
);
}
/**
* Send header string to the client
*
* @param string $header
* @param string $content
*/
protected function sendHeader($header, $content)
{
if (!headers_sent() && self::$sendHeaders) {
header(sprintf('%s: %s', $header, $content));
}
}
/**
* Creates & sends header for a record, ensuring init headers have been sent prior
*
* @see sendHeader()
* @see sendInitHeaders()
* @param array $record
*/
protected function write(array $record)
{
if (!self::$sendHeaders) {
return;
}
// WildFire-specific headers must be sent prior to any messages
if (!self::$initialized) {
self::$initialized = true;
self::$sendHeaders = $this->headersAccepted();
if (!self::$sendHeaders) {
return;
}
foreach ($this->getInitHeaders() as $header => $content) {
$this->sendHeader($header, $content);
}
}
$header = $this->createRecordHeader($record);
if (trim(current($header)) !== '') {
$this->sendHeader(key($header), current($header));
}
}
/**
* Verifies if the headers are accepted by the current user agent
*
* @return Boolean
*/
protected function headersAccepted()
{
if (!empty(thinkai_get_server('HTTP_USER_AGENT')) && preg_match('{\bFirePHP/\d+\.\d+\b}', thinkai_get_server('HTTP_USER_AGENT'))) {
return true;
}
return thinkai_get_server('HTTP_X_FIREPHP_VERSION');
}
/**
* BC getter for the sendHeaders property that has been made static
*/
public function __get($property)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
return static::$sendHeaders;
}
/**
* BC setter for the sendHeaders property that has been made static
*/
public function __set($property, $value)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
static::$sendHeaders = $value;
}
}

View File

@@ -0,0 +1,126 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
/**
* Sends logs to Fleep.io using Webhook integrations
*
* You'll need a Fleep.io account to use this handler.
*
* @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation
* @author Ando Roots <ando@sqroot.eu>
*/
class FleepHookHandler extends SocketHandler
{
const FLEEP_HOST = 'fleep.io';
const FLEEP_HOOK_URI = '/hook/';
/**
* @var string Webhook token (specifies the conversation where logs are sent)
*/
protected $token;
/**
* Construct a new Fleep.io Handler.
*
* For instructions on how to create a new web hook in your conversations
* see https://fleep.io/integrations/webhooks/
*
* @param string $token Webhook token
* @param bool|int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @throws MissingExtensionException
*/
public function __construct($token, $level = Logger::DEBUG, $bubble = true)
{
if (!extension_loaded('openssl')) {
throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler');
}
$this->token = $token;
$connectionString = 'ssl://' . self::FLEEP_HOST . ':443';
parent::__construct($connectionString, $level, $bubble);
}
/**
* Returns the default formatter to use with this handler
*
* Overloaded to remove empty context and extra arrays from the end of the log message.
*
* @return LineFormatter
*/
protected function getDefaultFormatter()
{
return new LineFormatter(null, null, true, true);
}
/**
* Handles a log record
*
* @param array $record
*/
public function write(array $record)
{
parent::write($record);
$this->closeSocket();
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
$content = $this->buildContent($record);
return $this->buildHeader($content) . $content;
}
/**
* Builds the header of the API Call
*
* @param string $content
* @return string
*/
private function buildHeader($content)
{
$header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n";
$header .= "Host: " . self::FLEEP_HOST . "\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($content) . "\r\n";
$header .= "\r\n";
return $header;
}
/**
* Builds the body of API call
*
* @param array $record
* @return string
*/
private function buildContent($record)
{
$dataArray = array(
'message' => $record['formatted'],
);
return http_build_query($dataArray);
}
}

View File

@@ -0,0 +1,127 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FlowdockFormatter;
use Monolog\Formatter\FormatterInterface;
/**
* Sends notifications through the Flowdock push API
*
* This must be configured with a FlowdockFormatter instance via setFormatter()
*
* Notes:
* API token - Flowdock API token
*
* @author Dominik Liebler <liebler.dominik@gmail.com>
* @see https://www.flowdock.com/api/push
*/
class FlowdockHandler extends SocketHandler
{
/**
* @var string
*/
protected $apiToken;
/**
* @param string $apiToken
* @param bool|int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*
* @throws MissingExtensionException if OpenSSL is missing
*/
public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true)
{
if (!extension_loaded('openssl')) {
throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler');
}
parent::__construct('ssl://api.flowdock.com:443', $level, $bubble);
$this->apiToken = $apiToken;
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
if (!$formatter instanceof FlowdockFormatter) {
throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly');
}
return parent::setFormatter($formatter);
}
/**
* Gets the default formatter.
*
* @return FormatterInterface
*/
protected function getDefaultFormatter()
{
throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly');
}
/**
* {@inheritdoc}
*
* @param array $record
*/
protected function write(array $record)
{
parent::write($record);
$this->closeSocket();
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
$content = $this->buildContent($record);
return $this->buildHeader($content) . $content;
}
/**
* Builds the body of API call
*
* @param array $record
* @return string
*/
private function buildContent($record)
{
return json_encode($record['formatted']['flowdock']);
}
/**
* Builds the header of the API Call
*
* @param string $content
* @return string
*/
private function buildHeader($content)
{
$header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n";
$header .= "Host: api.flowdock.com\r\n";
$header .= "Content-Type: application/json\r\n";
$header .= "Content-Length: " . strlen($content) . "\r\n";
$header .= "\r\n";
return $header;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Gelf\IMessagePublisher;
use Gelf\PublisherInterface;
use Gelf\Publisher;
use InvalidArgumentException;
use Monolog\Logger;
use Monolog\Formatter\GelfMessageFormatter;
/**
* Handler to send messages to a Graylog2 (http://www.graylog2.org) server
*
* @author Matt Lehner <mlehner@gmail.com>
* @author Benjamin Zikarsky <benjamin@zikarsky.de>
*/
class GelfHandler extends AbstractProcessingHandler
{
/**
* @var Publisher the publisher object that sends the message to the server
*/
protected $publisher;
/**
* @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($publisher, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) {
throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance');
}
$this->publisher = $publisher;
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->publisher = null;
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$this->publisher->publish($record['formatted']);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new GelfMessageFormatter();
}
}

View File

@@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
/**
* Forwards records to multiple handlers
*
* @author Lenar Lõhmus <lenar@city.ee>
*/
class GroupHandler extends AbstractHandler
{
protected $handlers;
/**
* @param array $handlers Array of Handlers.
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(array $handlers, $bubble = true)
{
foreach ($handlers as $handler) {
if (!$handler instanceof HandlerInterface) {
throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.');
}
}
$this->handlers = $handlers;
$this->bubble = $bubble;
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
foreach ($this->handlers as $handler) {
if ($handler->isHandling($record)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
foreach ($this->handlers as $handler) {
$handler->handle($record);
}
return false === $this->bubble;
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
if ($this->processors) {
$processed = array();
foreach ($records as $record) {
foreach ($this->processors as $processor) {
$processed[] = call_user_func($processor, $record);
}
}
$records = $processed;
}
foreach ($this->handlers as $handler) {
$handler->handleBatch($records);
}
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
foreach ($this->handlers as $handler) {
$handler->setFormatter($formatter);
}
return $this;
}
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
/**
* Interface that all Monolog Handlers must implement
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface HandlerInterface
{
/**
* Checks whether the given record will be handled by this handler.
*
* This is mostly done for performance reasons, to avoid calling processors for nothing.
*
* Handlers should still check the record levels within handle(), returning false in isHandling()
* is no guarantee that handle() will not be called, and isHandling() might not be called
* for a given record.
*
* @param array $record Partial log record containing only a level key
*
* @return Boolean
*/
public function isHandling(array $record);
/**
* Handles a record.
*
* All records may be passed to this method, and the handler should discard
* those that it does not want to handle.
*
* The return value of this function controls the bubbling process of the handler stack.
* Unless the bubbling is interrupted (by returning true), the Logger class will keep on
* calling further handlers in the stack with a given log record.
*
* @param array $record The record to handle
* @return Boolean true means that this handler handled the record, and that bubbling is not permitted.
* false means the record was either not processed or that this handler allows bubbling.
*/
public function handle(array $record);
/**
* Handles a set of records at once.
*
* @param array $records The records to handle (an array of record arrays)
*/
public function handleBatch(array $records);
/**
* Adds a processor in the stack.
*
* @param callable $callback
* @return self
*/
public function pushProcessor($callback);
/**
* Removes the processor on top of the stack and returns it.
*
* @return callable
*/
public function popProcessor();
/**
* Sets the formatter.
*
* @param FormatterInterface $formatter
* @return self
*/
public function setFormatter(FormatterInterface $formatter);
/**
* Gets the formatter.
*
* @return FormatterInterface
*/
public function getFormatter();
}

View File

@@ -0,0 +1,108 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
/**
* This simple wrapper class can be used to extend handlers functionality.
*
* Example: A custom filtering that can be applied to any handler.
*
* Inherit from this class and override handle() like this:
*
* public function handle(array $record)
* {
* if ($record meets certain conditions) {
* return false;
* }
* return $this->handler->handle($record);
* }
*
* @author Alexey Karapetov <alexey@karapetov.com>
*/
class HandlerWrapper implements HandlerInterface
{
/**
* @var HandlerInterface
*/
protected $handler;
/**
* HandlerWrapper constructor.
* @param HandlerInterface $handler
*/
public function __construct(HandlerInterface $handler)
{
$this->handler = $handler;
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return $this->handler->isHandling($record);
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
return $this->handler->handle($record);
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
return $this->handler->handleBatch($records);
}
/**
* {@inheritdoc}
*/
public function pushProcessor($callback)
{
$this->handler->pushProcessor($callback);
return $this;
}
/**
* {@inheritdoc}
*/
public function popProcessor()
{
return $this->handler->popProcessor();
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
$this->handler->setFormatter($formatter);
return $this;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->handler->getFormatter();
}
}

View File

@@ -0,0 +1,350 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Sends notifications through the hipchat api to a hipchat room
*
* Notes:
* API token - HipChat API token
* Room - HipChat Room Id or name, where messages are sent
* Name - Name used to send the message (from)
* notify - Should the message trigger a notification in the clients
* version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2)
*
* @author Rafael Dohms <rafael@doh.ms>
* @see https://www.hipchat.com/docs/api
*/
class HipChatHandler extends SocketHandler
{
/**
* Use API version 1
*/
const API_V1 = 'v1';
/**
* Use API version v2
*/
const API_V2 = 'v2';
/**
* The maximum allowed length for the name used in the "from" field.
*/
const MAXIMUM_NAME_LENGTH = 15;
/**
* The maximum allowed length for the message.
*/
const MAXIMUM_MESSAGE_LENGTH = 9500;
/**
* @var string
*/
private $token;
/**
* @var string
*/
private $room;
/**
* @var string
*/
private $name;
/**
* @var bool
*/
private $notify;
/**
* @var string
*/
private $format;
/**
* @var string
*/
private $host;
/**
* @var string
*/
private $version;
/**
* @param string $token HipChat API Token
* @param string $room The room that should be alerted of the message (Id or Name)
* @param string $name Name used in the "from" field.
* @param bool $notify Trigger a notification in clients or not
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $useSSL Whether to connect via SSL.
* @param string $format The format of the messages (default to text, can be set to html if you have html in the messages)
* @param string $host The HipChat server hostname.
* @param string $version The HipChat API version (default HipChatHandler::API_V1)
*/
public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1)
{
if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) {
throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.');
}
$connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80';
parent::__construct($connectionString, $level, $bubble);
$this->token = $token;
$this->name = $name;
$this->notify = $notify;
$this->room = $room;
$this->format = $format;
$this->host = $host;
$this->version = $version;
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
$content = $this->buildContent($record);
return $this->buildHeader($content) . $content;
}
/**
* Builds the body of API call
*
* @param array $record
* @return string
*/
private function buildContent($record)
{
$dataArray = array(
'notify' => $this->version == self::API_V1 ?
($this->notify ? 1 : 0) :
($this->notify ? 'true' : 'false'),
'message' => $record['formatted'],
'message_format' => $this->format,
'color' => $this->getAlertColor($record['level']),
);
if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) {
if (function_exists('mb_substr')) {
$dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
} else {
$dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
}
}
// if we are using the legacy API then we need to send some additional information
if ($this->version == self::API_V1) {
$dataArray['room_id'] = $this->room;
}
// append the sender name if it is set
// always append it if we use the v1 api (it is required in v1)
if ($this->version == self::API_V1 || $this->name !== null) {
$dataArray['from'] = (string) $this->name;
}
return http_build_query($dataArray);
}
/**
* Builds the header of the API Call
*
* @param string $content
* @return string
*/
private function buildHeader($content)
{
if ($this->version == self::API_V1) {
$header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n";
} else {
// needed for rooms with special (spaces, etc) characters in the name
$room = rawurlencode($this->room);
$header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n";
}
$header .= "Host: {$this->host}\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($content) . "\r\n";
$header .= "\r\n";
return $header;
}
/**
* Assigns a color to each level of log records.
*
* @param int $level
* @return string
*/
protected function getAlertColor($level)
{
switch (true) {
case $level >= Logger::ERROR:
return 'red';
case $level >= Logger::WARNING:
return 'yellow';
case $level >= Logger::INFO:
return 'green';
case $level == Logger::DEBUG:
return 'gray';
default:
return 'yellow';
}
}
/**
* {@inheritdoc}
*
* @param array $record
*/
protected function write(array $record)
{
parent::write($record);
$this->closeSocket();
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
if (count($records) == 0) {
return true;
}
$batchRecords = $this->combineRecords($records);
$handled = false;
foreach ($batchRecords as $batchRecord) {
if ($this->isHandling($batchRecord)) {
$this->write($batchRecord);
$handled = true;
}
}
if (!$handled) {
return false;
}
return false === $this->bubble;
}
/**
* Combines multiple records into one. Error level of the combined record
* will be the highest level from the given records. Datetime will be taken
* from the first record.
*
* @param $records
* @return array
*/
private function combineRecords($records)
{
$batchRecord = null;
$batchRecords = array();
$messages = array();
$formattedMessages = array();
$level = 0;
$levelName = null;
$datetime = null;
foreach ($records as $record) {
$record = $this->processRecord($record);
if ($record['level'] > $level) {
$level = $record['level'];
$levelName = $record['level_name'];
}
if (null === $datetime) {
$datetime = $record['datetime'];
}
$messages[] = $record['message'];
$messageStr = implode(PHP_EOL, $messages);
$formattedMessages[] = $this->getFormatter()->format($record);
$formattedMessageStr = implode('', $formattedMessages);
$batchRecord = array(
'message' => $messageStr,
'formatted' => $formattedMessageStr,
'context' => array(),
'extra' => array(),
);
if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) {
// Pop the last message and implode the remaining messages
$lastMessage = array_pop($messages);
$lastFormattedMessage = array_pop($formattedMessages);
$batchRecord['message'] = implode(PHP_EOL, $messages);
$batchRecord['formatted'] = implode('', $formattedMessages);
$batchRecords[] = $batchRecord;
$messages = array($lastMessage);
$formattedMessages = array($lastFormattedMessage);
$batchRecord = null;
}
}
if (null !== $batchRecord) {
$batchRecords[] = $batchRecord;
}
// Set the max level and datetime for all records
foreach ($batchRecords as &$batchRecord) {
$batchRecord = array_merge(
$batchRecord,
array(
'level' => $level,
'level_name' => $levelName,
'datetime' => $datetime,
)
);
}
return $batchRecords;
}
/**
* Validates the length of a string.
*
* If the `mb_strlen()` function is available, it will use that, as HipChat
* allows UTF-8 characters. Otherwise, it will fall back to `strlen()`.
*
* Note that this might cause false failures in the specific case of using
* a valid name with less than 16 characters, but 16 or more bytes, on a
* system where `mb_strlen()` is unavailable.
*
* @param string $str
* @param int $length
*
* @return bool
*/
private function validateStringLength($str, $length)
{
if (function_exists('mb_strlen')) {
return (mb_strlen($str) <= $length);
}
return (strlen($str) <= $length);
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* IFTTTHandler uses cURL to trigger IFTTT Maker actions
*
* Register a secret key and trigger/event name at https://ifttt.com/maker
*
* value1 will be the channel from monolog's Logger constructor,
* value2 will be the level name (ERROR, WARNING, ..)
* value3 will be the log record's message
*
* @author Nehal Patel <nehal@nehalpatel.me>
*/
class IFTTTHandler extends AbstractProcessingHandler
{
private $eventName;
private $secretKey;
/**
* @param string $eventName The name of the IFTTT Maker event that should be triggered
* @param string $secretKey A valid IFTTT secret key
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true)
{
$this->eventName = $eventName;
$this->secretKey = $secretKey;
parent::__construct($level, $bubble);
}
/**
* {@inheritdoc}
*/
public function write(array $record)
{
$postData = array(
"value1" => $record["channel"],
"value2" => $record["level_name"],
"value3" => $record["message"],
);
$postString = json_encode($postData);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* @author Robert Kaufmann III <rok3@rok3.me>
*/
class LogEntriesHandler extends SocketHandler
{
/**
* @var string
*/
protected $logToken;
/**
* @param string $token Log token supplied by LogEntries
* @param bool $useSSL Whether or not SSL encryption should be used.
* @param int $level The minimum logging level to trigger this handler
* @param bool $bubble Whether or not messages that are handled should bubble up the stack.
*
* @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
*/
public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true)
{
if ($useSSL && !extension_loaded('openssl')) {
throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');
}
$endpoint = $useSSL ? 'ssl://data.logentries.com:443' : 'data.logentries.com:80';
parent::__construct($endpoint, $level, $bubble);
$this->logToken = $token;
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
return $this->logToken . ' ' . $record['formatted'];
}
}

View File

@@ -0,0 +1,93 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\LogglyFormatter;
/**
* Sends errors to Loggly.
*
* @author Przemek Sobstel <przemek@sobstel.org>
* @author Adam Pancutt <adam@pancutt.com>
* @author Gregory Barchard <gregory@barchard.net>
*/
class LogglyHandler extends AbstractProcessingHandler
{
const HOST = 'logs-01.loggly.com';
const ENDPOINT_SINGLE = 'inputs';
const ENDPOINT_BATCH = 'bulk';
protected $token;
protected $tag = array();
public function __construct($token, $level = Logger::DEBUG, $bubble = true)
{
if (!extension_loaded('curl')) {
throw new \LogicException('The curl extension is needed to use the LogglyHandler');
}
$this->token = $token;
parent::__construct($level, $bubble);
}
public function setTag($tag)
{
$tag = !empty($tag) ? $tag : array();
$this->tag = is_array($tag) ? $tag : array($tag);
}
public function addTag($tag)
{
if (!empty($tag)) {
$tag = is_array($tag) ? $tag : array($tag);
$this->tag = array_unique(array_merge($this->tag, $tag));
}
}
protected function write(array $record)
{
$this->send($record["formatted"], self::ENDPOINT_SINGLE);
}
public function handleBatch(array $records)
{
$level = $this->level;
$records = array_filter($records, function ($record) use ($level) {
return ($record['level'] >= $level);
});
if ($records) {
$this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH);
}
}
protected function send($data, $endpoint)
{
$url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token);
$headers = array('Content-Type: application/json');
if (!empty($this->tag)) {
$headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag);
}
}
protected function getDefaultFormatter()
{
return new LogglyFormatter();
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
/**
* Base class for all mail handlers
*
* @author Gyula Sallai
*/
abstract class MailHandler extends AbstractProcessingHandler
{
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$messages = array();
foreach ($records as $record) {
if ($record['level'] < $this->level) {
continue;
}
$messages[] = $this->processRecord($record);
}
if (!empty($messages)) {
$this->send((string) $this->getFormatter()->formatBatch($messages), $messages);
}
}
/**
* Send a mail with the given content
*
* @param string $content formatted email body to be sent
* @param array $records the array of log records that formed this content
*/
abstract protected function send($content, array $records);
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$this->send((string) $record['formatted'], array($record));
}
protected function getHighestRecord(array $records)
{
$highestRecord = null;
foreach ($records as $record) {
if ($highestRecord === null || $highestRecord['level'] < $record['level']) {
$highestRecord = $record;
}
}
return $highestRecord;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* MandrillHandler uses cURL to send the emails to the Mandrill API
*
* @author Adam Nicholson <adamnicholson10@gmail.com>
*/
class MandrillHandler extends MailHandler
{
protected $message;
protected $apiKey;
/**
* @param string $apiKey A valid Mandrill API key
* @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true)
{
parent::__construct($level, $bubble);
if (!$message instanceof \Swift_Message && is_callable($message)) {
$message = call_user_func($message);
}
if (!$message instanceof \Swift_Message) {
throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it');
}
$this->message = $message;
$this->apiKey = $apiKey;
}
/**
* {@inheritdoc}
*/
protected function send($content, array $records)
{
$message = clone $this->message;
$message->setBody($content);
$message->setDate(time());
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
/**
* Exception can be thrown if an extension for an handler is missing
*
* @author Christian Bergau <cbergau86@gmail.com>
*/
class MissingExtensionException extends \Exception
{
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;
/**
* Logs to a MongoDB database.
*
* usage example:
*
* $log = new Logger('application');
* $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod");
* $log->pushHandler($mongodb);
*
* @author Thomas Tourlourat <thomas@tourlourat.com>
*/
class MongoDBHandler extends AbstractProcessingHandler
{
protected $mongoCollection;
public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true)
{
if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) {
throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required');
}
$this->mongoCollection = $mongo->selectCollection($database, $collection);
parent::__construct($level, $bubble);
}
protected function write(array $record)
{
if ($this->mongoCollection instanceof \MongoDB\Collection) {
$this->mongoCollection->insertOne($record["formatted"]);
} else {
$this->mongoCollection->save($record["formatted"]);
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new NormalizerFormatter();
}
}

View File

@@ -0,0 +1,184 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
/**
* NativeMailerHandler uses the mail() function to send the emails
*
* @author Christophe Coevoet <stof@notk.org>
* @author Mark Garrett <mark@moderndeveloperllc.com>
*/
class NativeMailerHandler extends MailHandler
{
/**
* The email addresses to which the message will be sent
* @var array
*/
protected $to;
/**
* The subject of the email
* @var string
*/
protected $subject;
/**
* Optional headers for the message
* @var array
*/
protected $headers = array();
/**
* Optional parameters for the message
* @var array
*/
protected $parameters = array();
/**
* The wordwrap length for the message
* @var int
*/
protected $maxColumnWidth;
/**
* The Content-type for the message
* @var string
*/
protected $contentType = 'text/plain';
/**
* The encoding for the message
* @var string
*/
protected $encoding = 'utf-8';
/**
* @param string|array $to The receiver of the mail
* @param string $subject The subject of the mail
* @param string $from The sender of the mail
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param int $maxColumnWidth The maximum column width that the message lines will have
*/
public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70)
{
parent::__construct($level, $bubble);
$this->to = is_array($to) ? $to : array($to);
$this->subject = $subject;
$this->addHeader(sprintf('From: %s', $from));
$this->maxColumnWidth = $maxColumnWidth;
}
/**
* Add headers to the message
*
* @param string|array $headers Custom added headers
* @return self
*/
public function addHeader($headers)
{
foreach ((array) $headers as $header) {
if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) {
throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons');
}
$this->headers[] = $header;
}
return $this;
}
/**
* Add parameters to the message
*
* @param string|array $parameters Custom added parameters
* @return self
*/
public function addParameter($parameters)
{
$this->parameters = array_merge($this->parameters, (array) $parameters);
return $this;
}
/**
* {@inheritdoc}
*/
protected function send($content, array $records)
{
$content = wordwrap($content, $this->maxColumnWidth);
$headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n");
$headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n";
if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) {
$headers .= 'MIME-Version: 1.0' . "\r\n";
}
$subject = $this->subject;
if ($records) {
$subjectFormatter = new LineFormatter($this->subject);
$subject = $subjectFormatter->format($this->getHighestRecord($records));
}
$parameters = implode(' ', $this->parameters);
foreach ($this->to as $to) {
}
}
/**
* @return string $contentType
*/
public function getContentType()
{
return $this->contentType;
}
/**
* @return string $encoding
*/
public function getEncoding()
{
return $this->encoding;
}
/**
* @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML
* messages.
* @return self
*/
public function setContentType($contentType)
{
if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) {
throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection');
}
$this->contentType = $contentType;
return $this;
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) {
throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection');
}
$this->encoding = $encoding;
return $this;
}
}

View File

@@ -0,0 +1,202 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;
/**
* Class to record a log on a NewRelic application.
* Enabling New Relic High Security mode may prevent capture of useful information.
*
* @see https://docs.newrelic.com/docs/agents/php-agent
* @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security
*/
class NewRelicHandler extends AbstractProcessingHandler
{
/**
* Name of the New Relic application that will receive logs from this handler.
*
* @var string
*/
protected $appName;
/**
* Name of the current transaction
*
* @var string
*/
protected $transactionName;
/**
* Some context and extra data is passed into the handler as arrays of values. Do we send them as is
* (useful if we are using the API), or explode them for display on the NewRelic RPM website?
*
* @var bool
*/
protected $explodeArrays;
/**
* {@inheritDoc}
*
* @param string $appName
* @param bool $explodeArrays
* @param string $transactionName
*/
public function __construct(
$level = Logger::ERROR,
$bubble = true,
$appName = null,
$explodeArrays = false,
$transactionName = null
) {
parent::__construct($level, $bubble);
$this->appName = $appName;
$this->explodeArrays = $explodeArrays;
$this->transactionName = $transactionName;
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
if (!$this->isNewRelicEnabled()) {
throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler');
}
if ($appName = $this->getAppName($record['context'])) {
$this->setNewRelicAppName($appName);
}
if ($transactionName = $this->getTransactionName($record['context'])) {
$this->setNewRelicTransactionName($transactionName);
unset($record['formatted']['context']['transaction_name']);
}
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) {
newrelic_notice_error($record['message'], $record['context']['exception']);
unset($record['formatted']['context']['exception']);
} else {
newrelic_notice_error($record['message']);
}
if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) {
foreach ($record['formatted']['context'] as $key => $parameter) {
if (is_array($parameter) && $this->explodeArrays) {
foreach ($parameter as $paramKey => $paramValue) {
$this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue);
}
} else {
$this->setNewRelicParameter('context_' . $key, $parameter);
}
}
}
if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) {
foreach ($record['formatted']['extra'] as $key => $parameter) {
if (is_array($parameter) && $this->explodeArrays) {
foreach ($parameter as $paramKey => $paramValue) {
$this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue);
}
} else {
$this->setNewRelicParameter('extra_' . $key, $parameter);
}
}
}
}
/**
* Checks whether the NewRelic extension is enabled in the system.
*
* @return bool
*/
protected function isNewRelicEnabled()
{
return extension_loaded('newrelic');
}
/**
* Returns the appname where this log should be sent. Each log can override the default appname, set in this
* handler's constructor, by providing the appname in it's context.
*
* @param array $context
* @return null|string
*/
protected function getAppName(array $context)
{
if (isset($context['appname'])) {
return $context['appname'];
}
return $this->appName;
}
/**
* Returns the name of the current transaction. Each log can override the default transaction name, set in this
* handler's constructor, by providing the transaction_name in it's context
*
* @param array $context
*
* @return null|string
*/
protected function getTransactionName(array $context)
{
if (isset($context['transaction_name'])) {
return $context['transaction_name'];
}
return $this->transactionName;
}
/**
* Sets the NewRelic application that should receive this log.
*
* @param string $appName
*/
protected function setNewRelicAppName($appName)
{
newrelic_set_appname($appName);
}
/**
* Overwrites the name of the current transaction
*
* @param string $transactionName
*/
protected function setNewRelicTransactionName($transactionName)
{
newrelic_name_transaction($transactionName);
}
/**
* @param string $key
* @param mixed $value
*/
protected function setNewRelicParameter($key, $value)
{
if (null === $value || is_scalar($value)) {
newrelic_add_custom_parameter($key, $value);
} else {
newrelic_add_custom_parameter($key, @json_encode($value));
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new NormalizerFormatter();
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Blackhole
*
* Any record it can handle will be thrown away. This can be used
* to put on top of an existing stack to override it temporarily.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class NullHandler extends AbstractHandler
{
/**
* @param int $level The minimum logging level at which this handler will be triggered
*/
public function __construct($level = Logger::DEBUG)
{
parent::__construct($level, false);
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($record['level'] < $this->level) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,242 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Exception;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
use PhpConsole\Connector;
use PhpConsole\Handler;
use PhpConsole\Helper;
/**
* Monolog handler for Google Chrome extension "PHP Console"
*
* Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely
*
* Usage:
* 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef
* 2. See overview https://github.com/barbushin/php-console#overview
* 3. Install PHP Console library https://github.com/barbushin/php-console#installation
* 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png)
*
* $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler()));
* \Monolog\ErrorHandler::register($logger);
* echo $undefinedVar;
* $logger->addDebug('SELECT * FROM users', array('db', 'time' => 0.012));
* PC::debug($_SERVER); // PHP Console debugger for any type of vars
*
* @author Sergey Barbushin https://www.linkedin.com/in/barbushin
*/
class PHPConsoleHandler extends AbstractProcessingHandler
{
private $options = array(
'enabled' => true, // bool Is PHP Console server enabled
'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with...
'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled
'useOwnErrorsHandler' => false, // bool Enable errors handling
'useOwnExceptionsHandler' => false, // bool Enable exceptions handling
'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths
'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s')
'serverEncoding' => null, // string|null Server internal encoding
'headersLimit' => null, // int|null Set headers size limit for your web-server
'password' => null, // string|null Protect PHP Console connection by password
'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed
'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1')
'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required)
'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings
'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level
'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number
'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item
'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON
'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug
'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ)
);
/** @var Connector */
private $connector;
/**
* @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details
* @param Connector|null $connector Instance of \PhpConsole\Connector class (optional)
* @param int $level
* @param bool $bubble
* @throws Exception
*/
public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true)
{
if (!class_exists('PhpConsole\Connector')) {
throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation');
}
parent::__construct($level, $bubble);
$this->options = $this->initOptions($options);
$this->connector = $this->initConnector($connector);
}
private function initOptions(array $options)
{
$wrongOptions = array_diff(array_keys($options), array_keys($this->options));
if ($wrongOptions) {
throw new Exception('Unknown options: ' . implode(', ', $wrongOptions));
}
return array_replace($this->options, $options);
}
private function initConnector(Connector $connector = null)
{
if (!$connector) {
if ($this->options['dataStorage']) {
Connector::setPostponeStorage($this->options['dataStorage']);
}
$connector = Connector::getInstance();
}
if ($this->options['registerHelper'] && !Helper::isRegistered()) {
Helper::register();
}
if ($this->options['enabled'] && $connector->isActiveClient()) {
if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) {
$handler = Handler::getInstance();
$handler->setHandleErrors($this->options['useOwnErrorsHandler']);
$handler->setHandleExceptions($this->options['useOwnExceptionsHandler']);
$handler->start();
}
if ($this->options['sourcesBasePath']) {
$connector->setSourcesBasePath($this->options['sourcesBasePath']);
}
if ($this->options['serverEncoding']) {
$connector->setServerEncoding($this->options['serverEncoding']);
}
if ($this->options['password']) {
$connector->setPassword($this->options['password']);
}
if ($this->options['enableSslOnlyMode']) {
$connector->enableSslOnlyMode();
}
if ($this->options['ipMasks']) {
$connector->setAllowedIpMasks($this->options['ipMasks']);
}
if ($this->options['headersLimit']) {
$connector->setHeadersLimit($this->options['headersLimit']);
}
if ($this->options['detectDumpTraceAndSource']) {
$connector->getDebugDispatcher()->detectTraceAndSource = true;
}
$dumper = $connector->getDumper();
$dumper->levelLimit = $this->options['dumperLevelLimit'];
$dumper->itemsCountLimit = $this->options['dumperItemsCountLimit'];
$dumper->itemSizeLimit = $this->options['dumperItemSizeLimit'];
$dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit'];
$dumper->detectCallbacks = $this->options['dumperDetectCallbacks'];
if ($this->options['enableEvalListener']) {
$connector->startEvalRequestsListener();
}
}
return $connector;
}
public function getConnector()
{
return $this->connector;
}
public function getOptions()
{
return $this->options;
}
public function handle(array $record)
{
if ($this->options['enabled'] && $this->connector->isActiveClient()) {
return parent::handle($record);
}
return !$this->bubble;
}
/**
* Writes the record down to the log of the implementing handler
*
* @param array $record
* @return void
*/
protected function write(array $record)
{
if ($record['level'] < Logger::NOTICE) {
$this->handleDebugRecord($record);
} elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) {
$this->handleExceptionRecord($record);
} else {
$this->handleErrorRecord($record);
}
}
private function handleDebugRecord(array $record)
{
$tags = $this->getRecordTags($record);
$message = $record['message'];
if ($record['context']) {
$message .= ' ' . json_encode($this->connector->getDumper()->dump(array_filter($record['context'])));
}
$this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']);
}
private function handleExceptionRecord(array $record)
{
$this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']);
}
private function handleErrorRecord(array $record)
{
$context = $record['context'];
$this->connector->getErrorsDispatcher()->dispatchError(
isset($context['code']) ? $context['code'] : null,
isset($context['message']) ? $context['message'] : $record['message'],
isset($context['file']) ? $context['file'] : null,
isset($context['line']) ? $context['line'] : null,
$this->options['classesPartialsTraceIgnore']
);
}
private function getRecordTags(array &$record)
{
$tags = null;
if (!empty($record['context'])) {
$context = & $record['context'];
foreach ($this->options['debugTagsKeysInContext'] as $key) {
if (!empty($context[$key])) {
$tags = $context[$key];
if ($key === 0) {
array_shift($context);
} else {
unset($context[$key]);
}
break;
}
}
}
return $tags ?: strtolower($record['level_name']);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('%message%');
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
/**
* Proxies log messages to an existing PSR-3 compliant logger.
*
* @author Michael Moussa <michael.moussa@gmail.com>
*/
class PsrHandler extends AbstractHandler
{
/**
* PSR-3 compliant logger
*
* @var LoggerInterface
*/
protected $logger;
/**
* @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
$this->logger = $logger;
}
/**
* {@inheritDoc}
*/
public function handle(array $record)
{
if (!$this->isHandling($record)) {
return false;
}
$this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']);
return false === $this->bubble;
}
}

View File

@@ -0,0 +1,185 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Sends notifications through the pushover api to mobile phones
*
* @author Sebastian Göttschkes <sebastian.goettschkes@googlemail.com>
* @see https://www.pushover.net/api
*/
class PushoverHandler extends SocketHandler
{
private $token;
private $users;
private $title;
private $user;
private $retry;
private $expire;
private $highPriorityLevel;
private $emergencyLevel;
private $useFormattedMessage = false;
/**
* All parameters that can be sent to Pushover
* @see https://pushover.net/api
* @var array
*/
private $parameterNames = array(
'token' => true,
'user' => true,
'message' => true,
'device' => true,
'title' => true,
'url' => true,
'url_title' => true,
'priority' => true,
'timestamp' => true,
'sound' => true,
'retry' => true,
'expire' => true,
'callback' => true,
);
/**
* Sounds the api supports by default
* @see https://pushover.net/api#sounds
* @var array
*/
private $sounds = array(
'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming',
'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb',
'persistent', 'echo', 'updown', 'none',
);
/**
* @param string $token Pushover api token
* @param string|array $users Pushover user id or array of ids the message will be sent to
* @param string $title Title sent to the Pushover API
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param Boolean $useSSL Whether to connect via SSL. Required when pushing messages to users that are not
* the pushover.net app owner. OpenSSL is required for this option.
* @param int $highPriorityLevel The minimum logging level at which this handler will start
* sending "high priority" requests to the Pushover API
* @param int $emergencyLevel The minimum logging level at which this handler will start
* sending "emergency" requests to the Pushover API
* @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user.
* @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds).
*/
public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200)
{
$connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80';
parent::__construct($connectionString, $level, $bubble);
$this->token = $token;
$this->users = (array) $users;
$this->title = $title ?: gethostname();
$this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel);
$this->emergencyLevel = Logger::toMonologLevel($emergencyLevel);
$this->retry = $retry;
$this->expire = $expire;
}
protected function generateDataStream($record)
{
$content = $this->buildContent($record);
return $this->buildHeader($content) . $content;
}
private function buildContent($record)
{
// Pushover has a limit of 512 characters on title and message combined.
$maxMessageLength = 512 - strlen($this->title);
$message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message'];
$message = substr($message, 0, $maxMessageLength);
$timestamp = $record['datetime']->getTimestamp();
$dataArray = array(
'token' => $this->token,
'user' => $this->user,
'message' => $message,
'title' => $this->title,
'timestamp' => $timestamp,
);
if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) {
$dataArray['priority'] = 2;
$dataArray['retry'] = $this->retry;
$dataArray['expire'] = $this->expire;
} elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) {
$dataArray['priority'] = 1;
}
// First determine the available parameters
$context = array_intersect_key($record['context'], $this->parameterNames);
$extra = array_intersect_key($record['extra'], $this->parameterNames);
// Least important info should be merged with subsequent info
$dataArray = array_merge($extra, $context, $dataArray);
// Only pass sounds that are supported by the API
if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) {
unset($dataArray['sound']);
}
return http_build_query($dataArray);
}
private function buildHeader($content)
{
$header = "POST /1/messages.json HTTP/1.1\r\n";
$header .= "Host: api.pushover.net\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($content) . "\r\n";
$header .= "\r\n";
return $header;
}
protected function write(array $record)
{
foreach ($this->users as $user) {
$this->user = $user;
parent::write($record);
$this->closeSocket();
}
$this->user = null;
}
public function setHighPriorityLevel($value)
{
$this->highPriorityLevel = $value;
}
public function setEmergencyLevel($value)
{
$this->emergencyLevel = $value;
}
/**
* Use the formatted message?
* @param bool $value
*/
public function useFormattedMessage($value)
{
$this->useFormattedMessage = (boolean) $value;
}
}

View File

@@ -0,0 +1,232 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\LineFormatter;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Raven_Client;
/**
* Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server
* using raven-php (https://github.com/getsentry/raven-php)
*
* @author Marc Abramowitz <marc@marc-abramowitz.com>
*/
class RavenHandler extends AbstractProcessingHandler
{
/**
* Translates Monolog log levels to Raven log levels.
*/
private $logLevels = array(
Logger::DEBUG => Raven_Client::DEBUG,
Logger::INFO => Raven_Client::INFO,
Logger::NOTICE => Raven_Client::INFO,
Logger::WARNING => Raven_Client::WARNING,
Logger::ERROR => Raven_Client::ERROR,
Logger::CRITICAL => Raven_Client::FATAL,
Logger::ALERT => Raven_Client::FATAL,
Logger::EMERGENCY => Raven_Client::FATAL,
);
/**
* @var string should represent the current version of the calling
* software. Can be any string (git commit, version number)
*/
private $release;
/**
* @var Raven_Client the client object that sends the message to the server
*/
protected $ravenClient;
/**
* @var LineFormatter The formatter to use for the logs generated via handleBatch()
*/
protected $batchFormatter;
/**
* @param Raven_Client $ravenClient
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
$this->ravenClient = $ravenClient;
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$level = $this->level;
// filter records based on their level
$records = array_filter($records, function ($record) use ($level) {
return $record['level'] >= $level;
});
if (!$records) {
return;
}
// the record with the highest severity is the "main" one
$record = array_reduce($records, function ($highest, $record) {
if ($record['level'] > $highest['level']) {
return $record;
}
return $highest;
});
// the other ones are added as a context item
$logs = array();
foreach ($records as $r) {
$logs[] = $this->processRecord($r);
}
if ($logs) {
$record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs);
}
$this->handle($record);
}
/**
* Sets the formatter for the logs generated by handleBatch().
*
* @param FormatterInterface $formatter
*/
public function setBatchFormatter(FormatterInterface $formatter)
{
$this->batchFormatter = $formatter;
}
/**
* Gets the formatter for the logs generated by handleBatch().
*
* @return FormatterInterface
*/
public function getBatchFormatter()
{
if (!$this->batchFormatter) {
$this->batchFormatter = $this->getDefaultBatchFormatter();
}
return $this->batchFormatter;
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$previousUserContext = false;
$options = array();
$options['level'] = $this->logLevels[$record['level']];
$options['tags'] = array();
if (!empty($record['extra']['tags'])) {
$options['tags'] = array_merge($options['tags'], $record['extra']['tags']);
unset($record['extra']['tags']);
}
if (!empty($record['context']['tags'])) {
$options['tags'] = array_merge($options['tags'], $record['context']['tags']);
unset($record['context']['tags']);
}
if (!empty($record['context']['fingerprint'])) {
$options['fingerprint'] = $record['context']['fingerprint'];
unset($record['context']['fingerprint']);
}
if (!empty($record['context']['logger'])) {
$options['logger'] = $record['context']['logger'];
unset($record['context']['logger']);
} else {
$options['logger'] = $record['channel'];
}
foreach ($this->getExtraParameters() as $key) {
foreach (array('extra', 'context') as $source) {
if (!empty($record[$source][$key])) {
$options[$key] = $record[$source][$key];
unset($record[$source][$key]);
}
}
}
if (!empty($record['context'])) {
$options['extra']['context'] = $record['context'];
if (!empty($record['context']['user'])) {
$previousUserContext = $this->ravenClient->context->user;
$this->ravenClient->user_context($record['context']['user']);
unset($options['extra']['context']['user']);
}
}
if (!empty($record['extra'])) {
$options['extra']['extra'] = $record['extra'];
}
if (!empty($this->release) && !isset($options['release'])) {
$options['release'] = $this->release;
}
if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) {
$options['extra']['message'] = $record['formatted'];
$this->ravenClient->captureException($record['context']['exception'], $options);
} else {
$this->ravenClient->captureMessage($record['formatted'], array(), $options);
}
if ($previousUserContext !== false) {
$this->ravenClient->user_context($previousUserContext);
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('[%channel%] %message%');
}
/**
* Gets the default formatter for the logs generated by handleBatch().
*
* @return FormatterInterface
*/
protected function getDefaultBatchFormatter()
{
return new LineFormatter();
}
/**
* Gets extra parameters supported by Raven that can be found in "extra" and "context"
*
* @return array
*/
protected function getExtraParameters()
{
return array('checksum', 'release', 'event_id');
}
/**
* @param string $value
* @return self
*/
public function setRelease($value)
{
$this->release = $value;
return $this;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
/**
* Logs to a Redis key using rpush
*
* usage example:
*
* $log = new Logger('application');
* $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod");
* $log->pushHandler($redis);
*
* @author Thomas Tourlourat <thomas@tourlourat.com>
*/
class RedisHandler extends AbstractProcessingHandler
{
private $redisClient;
private $redisKey;
protected $capSize;
/**
* @param \Predis\Client|\Redis $redis The redis instance
* @param string $key The key name to push records to
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param int $capSize Number of entries to limit list size to
*/
public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false)
{
if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) {
throw new \InvalidArgumentException('Predis\Client or Redis instance required');
}
$this->redisClient = $redis;
$this->redisKey = $key;
$this->capSize = $capSize;
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
if ($this->capSize) {
$this->writeCapped($record);
} else {
$this->redisClient->rpush($this->redisKey, $record["formatted"]);
}
}
/**
* Write and cap the collection
* Writes the record to the redis list and caps its
*
* @param array $record associative record array
* @return void
*/
protected function writeCapped(array $record)
{
if ($this->redisClient instanceof \Redis) {
$this->redisClient->multi()
->rpush($this->redisKey, $record["formatted"])
->ltrim($this->redisKey, -$this->capSize, -1);
} else {
$redisKey = $this->redisKey;
$capSize = $this->capSize;
$this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) {
$tx->rpush($redisKey, $record["formatted"]);
$tx->ltrim($redisKey, -$capSize, -1);
});
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter();
}
}

View File

@@ -0,0 +1,132 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use RollbarNotifier;
use Exception;
use Monolog\Logger;
/**
* Sends errors to Rollbar
*
* If the context data contains a `payload` key, that is used as an array
* of payload options to RollbarNotifier's report_message/report_exception methods.
*
* Rollbar's context info will contain the context + extra keys from the log record
* merged, and then on top of that a few keys:
*
* - level (rollbar level name)
* - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8)
* - channel
* - datetime (unix timestamp)
*
* @author Paul Statezny <paulstatezny@gmail.com>
*/
class RollbarHandler extends AbstractProcessingHandler
{
/**
* Rollbar notifier
*
* @var RollbarNotifier
*/
protected $rollbarNotifier;
protected $levelMap = array(
Logger::DEBUG => 'debug',
Logger::INFO => 'info',
Logger::NOTICE => 'info',
Logger::WARNING => 'warning',
Logger::ERROR => 'error',
Logger::CRITICAL => 'critical',
Logger::ALERT => 'critical',
Logger::EMERGENCY => 'critical',
);
/**
* Records whether any log records have been added since the last flush of the rollbar notifier
*
* @var bool
*/
private $hasRecords = false;
protected $initialized = false;
/**
* @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true)
{
$this->rollbarNotifier = $rollbarNotifier;
parent::__construct($level, $bubble);
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
if (!$this->initialized) {
// __destructor() doesn't get called on Fatal errors
register_shutdown_function(array($this, 'close'));
$this->initialized = true;
}
$context = $record['context'];
$payload = array();
if (isset($context['payload'])) {
$payload = $context['payload'];
unset($context['payload']);
}
$context = array_merge($context, $record['extra'], array(
'level' => $this->levelMap[$record['level']],
'monolog_level' => $record['level_name'],
'channel' => $record['channel'],
'datetime' => $record['datetime']->format('U'),
));
if (isset($context['exception']) && $context['exception'] instanceof Exception) {
$payload['level'] = $context['level'];
$exception = $context['exception'];
unset($context['exception']);
$this->rollbarNotifier->report_exception($exception, $context, $payload);
} else {
$this->rollbarNotifier->report_message(
$record['message'],
$context['level'],
$context,
$payload
);
}
$this->hasRecords = true;
}
public function flush()
{
if ($this->hasRecords) {
$this->rollbarNotifier->flush();
$this->hasRecords = false;
}
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->flush();
}
}

View File

@@ -0,0 +1,178 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Stores logs to files that are rotated every day and a limited number of files are kept.
*
* This rotation is only intended to be used as a workaround. Using logrotate to
* handle the rotation is strongly encouraged when you can use it.
*
* @author Christophe Coevoet <stof@notk.org>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class RotatingFileHandler extends StreamHandler
{
const FILE_PER_DAY = 'Y-m-d';
const FILE_PER_MONTH = 'Y-m';
const FILE_PER_YEAR = 'Y';
protected $filename;
protected $maxFiles;
protected $mustRotate;
protected $nextRotation;
protected $filenameFormat;
protected $dateFormat;
/**
* @param string $filename
* @param int $maxFiles The maximal amount of files to keep (0 means unlimited)
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write)
* @param Boolean $useLocking Try to lock log file before doing any writes
*/
public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false)
{
$this->filename = $filename;
$this->maxFiles = (int) $maxFiles;
$this->nextRotation = new \DateTime('tomorrow');
$this->filenameFormat = '{filename}-{date}';
$this->dateFormat = 'Y-m-d';
parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking);
}
/**
* {@inheritdoc}
*/
public function close()
{
parent::close();
if (true === $this->mustRotate) {
$this->rotate();
}
}
public function setFilenameFormat($filenameFormat, $dateFormat)
{
if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) {
trigger_error(
'Invalid date format - format must be one of '.
'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '.
'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '.
'date formats using slashes, underscores and/or dots instead of dashes.',
E_USER_DEPRECATED
);
}
if (substr_count($filenameFormat, '{date}') === 0) {
trigger_error(
'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.',
E_USER_DEPRECATED
);
}
$this->filenameFormat = $filenameFormat;
$this->dateFormat = $dateFormat;
$this->url = $this->getTimedFilename();
$this->close();
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
// on the first record written, if the log is new, we should rotate (once per day)
if (null === $this->mustRotate) {
$this->mustRotate = !file_exists($this->url);
}
if ($this->nextRotation < $record['datetime']) {
$this->mustRotate = true;
$this->close();
}
parent::write($record);
}
/**
* Rotates the files.
*/
protected function rotate()
{
// update filename
$this->url = $this->getTimedFilename();
$this->nextRotation = new \DateTime('tomorrow');
// skip GC of old logs if files are unlimited
if (0 === $this->maxFiles) {
return;
}
$logFiles = glob($this->getGlobPattern());
if ($this->maxFiles >= count($logFiles)) {
// no files to remove
return;
}
// Sorting the files by name to remove the older ones
usort($logFiles, function ($a, $b) {
return strcmp($b, $a);
});
foreach (array_slice($logFiles, $this->maxFiles) as $file) {
if (is_writable($file)) {
// suppress errors here as unlink() might fail if two processes
// are cleaning up/rotating at the same time
set_error_handler(function ($errno, $errstr, $errfile, $errline) {});
unlink($file);
restore_error_handler();
}
}
$this->mustRotate = false;
}
protected function getTimedFilename()
{
$fileInfo = pathinfo($this->filename);
$timedFilename = str_replace(
array('{filename}', '{date}'),
array($fileInfo['filename'], date($this->dateFormat)),
$fileInfo['dirname'] . '/' . $this->filenameFormat
);
if (!empty($fileInfo['extension'])) {
$timedFilename .= '.'.$fileInfo['extension'];
}
return $timedFilename;
}
protected function getGlobPattern()
{
$fileInfo = pathinfo($this->filename);
$glob = str_replace(
array('{filename}', '{date}'),
array($fileInfo['filename'], '*'),
$fileInfo['dirname'] . '/' . $this->filenameFormat
);
if (!empty($fileInfo['extension'])) {
$glob .= '.'.$fileInfo['extension'];
}
return $glob;
}
}

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