Compare commits
1518 Commits
0.1
...
2.0.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
| 1db9d411c5 | |||
| 451bad02f0 | |||
| b004a236ba | |||
| 1a5f7e2d88 | |||
| 5e98404dfb | |||
| 463573bf69 | |||
| 5def3f5862 | |||
| 71e51207ce | |||
| 1137fae94d | |||
| d2fcbf5d84 | |||
| 19c283140e | |||
| 89ee994f77 | |||
| a78d6afeaa | |||
| 4d5fd9be81 | |||
| 40f59b219b | |||
| 497e0cad7c | |||
| b026d3b115 | |||
| 616f9fea26 | |||
| a3bcd60a37 | |||
| fb96ea8845 | |||
| e610143f51 | |||
| af43bd3767 | |||
| 3f7a62908c | |||
| a1691859ca | |||
| 9c08a891f9 | |||
| f1e29e69cb | |||
| 558d9aabab | |||
| 75c3478a0c | |||
| 49e564ec15 | |||
| e643992350 | |||
| b125ed0394 | |||
| c6da9bea71 | |||
| 50243f0e34 | |||
| d13de40db6 | |||
| a1413a3da9 | |||
| fad316151c | |||
| 8c55a9e6c9 | |||
| 230413bdd1 | |||
| dc1c2debfb | |||
| a754db33c9 | |||
| 7083d183b9 | |||
| 8bb1f3d69a | |||
| f90af145ca | |||
| bccb5bba75 | |||
| 7d6c3edcdd | |||
| ec00964de2 | |||
| 7244d6cb61 | |||
| 83aaf84195 | |||
| 16a3d04cbd | |||
| f9d5155abf | |||
| b45c188516 | |||
| f506da40e2 | |||
| 3b84dc08fc | |||
| 78f66dcc52 | |||
| c937de3443 | |||
| 2b7a488917 | |||
| e177976099 | |||
| 89659c9eae | |||
| 109d67dbb1 | |||
| 6682139ec5 | |||
| e6f55346fd | |||
| eccf5eb2e0 | |||
| 1dbcd63b59 | |||
| 4793ee6509 | |||
| 6eebd8c909 | |||
| 0ab7404f93 | |||
| 4fcb7eaf13 | |||
| 8ce32af612 | |||
| 34437f408c | |||
| ab4aeb8bd8 | |||
| fdab81e910 | |||
| c3fdbcc60f | |||
| e62d27ff9b | |||
| 8b8cdabc89 | |||
| bdd23b076a | |||
| 7b2d336893 | |||
| 443cecd2d8 | |||
| b71ebd9af9 | |||
| 22ed64522a | |||
| 2e02b50409 | |||
| a3b4e8a2c0 | |||
| d5e9a99ce6 | |||
| 68568bf0b7 | |||
| 02947111c0 | |||
| e68d9179a1 | |||
| c713273619 | |||
| 3f357ee241 | |||
| c3cb46809b | |||
| 72ea6dd1a7 | |||
| 54f2b4a254 | |||
| 3162601c59 | |||
| 6819604185 | |||
| f967422fae | |||
| 428af5a8c3 | |||
| fd72e99d53 | |||
| 2a2903ceaa | |||
| fac3f8abfa | |||
| e40ff14d7d | |||
| a2cdaa8cdd | |||
| 4a230f9844 | |||
| 5f246a5543 | |||
| 2ba06b1ee2 | |||
| 3c5b025ac3 | |||
| ceb79aa016 | |||
| 925d8ab316 | |||
| d5b28518e9 | |||
| 0a3a5f6cd7 | |||
| ab2c93c7eb | |||
| 727b39a90e | |||
| c14a7c4251 | |||
| c37381b946 | |||
| 790d18a319 | |||
| 6fd3d82c01 | |||
| fcb3faf109 | |||
| d75a9fa38b | |||
| 0e7cf1fc50 | |||
| 9cc6bd87fe | |||
| 80127e4fb1 | |||
| 4ca0c9139c | |||
| a64f1d9f2d | |||
| bac5a34552 | |||
| a737d2a0ee | |||
| 545b852f46 | |||
| 392f4a2681 | |||
| 120544cccd | |||
| 82b07eb272 | |||
| c2257428b2 | |||
| 8ba913d87a | |||
| 06fdfd025e | |||
| d990dc6f05 | |||
| bdf39ff10d | |||
| 2ac2e0bc2b | |||
| 1a0ea1f35a | |||
| 43e6f47d5b | |||
| 9f1d650ae8 | |||
| b69fc0628a | |||
| a8596c35c8 | |||
| a15692b1b7 | |||
| 6622cf9968 | |||
| 7fc14130c7 | |||
| 579764b264 | |||
| 3e1f25e22f | |||
| 5a58461491 | |||
| 773ed2e7a6 | |||
| 496f21e6fa | |||
| 053b9568b2 | |||
| 930334cd6d | |||
| 2686457448 | |||
| fedaf00537 | |||
| ea8da8c6d5 | |||
| 95135988f9 | |||
| d85454fbf8 | |||
| 728a432850 | |||
| f59601fced | |||
| 0e654f6273 | |||
| f48a018929 | |||
| b958d9e59b | |||
| 4919584b87 | |||
| 1473e8c9fb | |||
| 47cadf36c8 | |||
| 20a69dffe7 | |||
| 009696d0a8 | |||
| e9d5c2bf02 | |||
| 946d6a5124 | |||
| a6523cfe86 | |||
| c54ea989a3 | |||
| 342f0cc55a | |||
| 0e7650683d | |||
| 768303a573 | |||
| 0bc2baa65c | |||
| b7dd5f824c | |||
| 54139268f8 | |||
| 44a16e82d8 | |||
| d0b90fbe18 | |||
| 55333dfd21 | |||
| 47e12c3677 | |||
| 3f3fbef11f | |||
| 48ffc5a4b6 | |||
| 6ecdd48a3f | |||
| b17874a7d5 | |||
| 308db01684 | |||
| b1a65df9df | |||
| 8d9b8912af | |||
| 4ab58dcf6c | |||
| 9e07dc982c | |||
| 9948d899d3 | |||
| 53e121881b | |||
| 7e63b892f9 | |||
| 9fb6ac830f | |||
| 9b9b05008a | |||
| ac9d58211e | |||
| 3cf22a0541 | |||
| 82d6d9cb06 | |||
| 75e9d1df03 | |||
| 170746f99d | |||
| 51d9699fa1 | |||
| 2878416f8b | |||
| 4346a86068 | |||
| 399bd777d7 | |||
| 2f3c816579 | |||
| 9a014e48d6 | |||
| 98510a4189 | |||
| c7d3bf1671 | |||
| 4d6e818e40 | |||
| 1cbef2d3b5 | |||
| 04aaa199b7 | |||
| 132f614dee | |||
| 9744e97131 | |||
| 769e19dc4a | |||
| e3c34bfc06 | |||
| d6fa2f70ac | |||
| 276a1e9d3f | |||
| 164bd80118 | |||
| c844dc0c50 | |||
| 1a93ee423b | |||
| 7d74a2f32b | |||
| 14d7a69b8c | |||
| 34c06cabef | |||
| 0ee043f745 | |||
| 371ac69a6b | |||
| 2385f891e5 | |||
| 0c83fd5994 | |||
| f98a2a0fc3 | |||
| bdf2add2e8 | |||
| 3e6b7ea0e9 | |||
| 6141388969 | |||
| 2d733277e6 | |||
| a05be8abec | |||
| d0c2243b10 | |||
| 6894d48e03 | |||
| f37d1427a1 | |||
| 6e22bd737b | |||
| bcf53ab75b | |||
| a24f2c8808 | |||
| 48b67328e2 | |||
| efad7e53a1 | |||
| 2ab8cb6816 | |||
| db2b4bf678 | |||
| b0cce9e636 | |||
| 1df1204d94 | |||
| 6ee416a069 | |||
| 0ca374e6a1 | |||
| d8f9f37ab2 | |||
| 092ca70725 | |||
| a36737f485 | |||
| 46bbd8d321 | |||
| 6c87418ff0 | |||
| 1bd12b6229 | |||
| 0a018fe039 | |||
| 6d37a7e6c1 | |||
| b3dc0749d3 | |||
| 2691cf0438 | |||
| 1d14779154 | |||
| aa4d6562c1 | |||
| 0ed6302212 | |||
| 0f00688096 | |||
| 73b7744383 | |||
| 8378485e33 | |||
| ba7b9d48d4 | |||
| 3e30422cda | |||
| 495aecfe74 | |||
| 71798e4ec4 | |||
| 32da2a70ef | |||
| fca3c75723 | |||
| dcae2fc25d | |||
| 0e7971d835 | |||
| c641baad0e | |||
| 732c2ad897 | |||
| 0bf99bb144 | |||
| 0bd2cb1ecd | |||
| e4977b8a86 | |||
| c0d9eba07f | |||
| d9085c63e3 | |||
| 7781faa0b0 | |||
| 78cedc2262 | |||
| 4d85d7e9ba | |||
| 7a577c519f | |||
| 55f58c9c5e | |||
| 2f6a596760 | |||
| 8c7e0f95b9 | |||
| d9b7175593 | |||
| 59f18f9a85 | |||
| 9ca5fd43f9 | |||
| f170f31594 | |||
| 874e3e10a4 | |||
| f8c2736a10 | |||
| 70b54da2b1 | |||
| c9fa9677c1 | |||
| fa8d563934 | |||
| 19aee7cd54 | |||
| 2725de8efb | |||
| 91f78f26f2 | |||
| 2734044aca | |||
| 0536b809b0 | |||
| e1dd7f70c5 | |||
| f5deb024a2 | |||
| 68c6f1bd7f | |||
| da93261a7d | |||
| 3d2b2d62be | |||
| eb3bd7efb7 | |||
| f59f45d740 | |||
| 017e20895f | |||
| 2c0ffcf397 | |||
| 3d3368cfd5 | |||
| 92504e0dd4 | |||
| 11204db45a | |||
| d4c029f46e | |||
| c5e8ba25bb | |||
| eaf95758dc | |||
| 7ffb1e80bf | |||
| 5078e8360a | |||
| d29bfaf139 | |||
| 2a94b1d1b7 | |||
| cbce162b40 | |||
| fb8389f463 | |||
| 970c40bb93 | |||
| 0ac38198ab | |||
| 653e8be4c1 | |||
| 89c03230c3 | |||
| 3b815d2de5 | |||
| d91691573f | |||
| 7812f508bc | |||
| 5f09650eef | |||
| 8af35ad932 | |||
| 9c0c882006 | |||
| 94f2364cd8 | |||
| 8125b415d8 | |||
| d01db0c71d | |||
| ed4d5cf2d7 | |||
| 427b61a35b | |||
| 7f2d9f9613 | |||
| c64a14787d | |||
| 02b225a82e | |||
| 15d33c24dc | |||
| 2f69eb4afa | |||
| 29c4517f7a | |||
| be463487cc | |||
| 905ae369bd | |||
| c8dee95396 | |||
| c0284f6182 | |||
| 93e28e4d2e | |||
| 5644c2d88e | |||
| de00c9208d | |||
| 2c093b03de | |||
| 9e0fff7cb0 | |||
| 77bb7b92a2 | |||
| 3bb7c5ffec | |||
| c2e2906c8d | |||
| bc782eaa72 | |||
| 34d15eb4d0 | |||
| 1b0e6e9ae6 | |||
| 61b9fdd5e4 | |||
| eacaf7f864 | |||
| 6079aaa33d | |||
| 6e334aba68 | |||
| 42a9064620 | |||
| 889249804f | |||
| c5772d118f | |||
| 2e45e7bebc | |||
| 8394ab4619 | |||
| 4cfbd5d893 | |||
| 7df80cb32c | |||
| daacffefa6 | |||
| 1990517b22 | |||
| c3235553dd | |||
| 71691fe44a | |||
| aa6e27cf4f | |||
| 4ffc77d9f5 | |||
| fdcbdda1ad | |||
| 367664ee87 | |||
| e11e03cb32 | |||
| 3ba208b205 | |||
| 127915f4ea | |||
| 03493be075 | |||
| 843dbe5195 | |||
| a8c90c5c1b | |||
| a65f5d5563 | |||
| 27f15aa4ca | |||
| f8bf895254 | |||
| 589dce52c6 | |||
| 38ba7ed972 | |||
| 3e5a342f65 | |||
| e4788de51e | |||
| 569f8d6851 | |||
| 19f2f11ee8 | |||
| 9e11bfa4a6 | |||
| 7dfc3c2b58 | |||
| 80709502c7 | |||
| 2cdb0b8f40 | |||
| b2d9357c78 | |||
| c4b1e79018 | |||
| dc61832a9e | |||
| 33767049a5 | |||
| 0c678cf24a | |||
| b9ec99e25b | |||
| d692b3b08d | |||
| 6b767d1cc0 | |||
| ad4d1caa9e | |||
| b84a80559a | |||
| 163eae0bb1 | |||
| bd9f08157c | |||
| 9d50517cea | |||
| 2b9fe72b39 | |||
| 93fd4692f6 | |||
| 0440249631 | |||
| 3eb951572d | |||
| 9de6a0a7cc | |||
| 19875ef0da | |||
| 00fcfd299b | |||
| 79e051a1f2 | |||
| 97a2dd74c8 | |||
| 8d6ff10e8e | |||
| 9a5c1bc62a | |||
| 6ad93dff69 | |||
| c78c1a3f08 | |||
| 9e7f6caf03 | |||
| 820d81aa61 | |||
| 3329f1bf3d | |||
| 90c67dbd12 | |||
| 96b2c59c04 | |||
| 3d99ce9dad | |||
| 99410a21eb | |||
| 1345a10788 | |||
| f3052b4542 | |||
| e342acf7ba | |||
| 2b17e0aa77 | |||
| dda7884ace | |||
| 10939766de | |||
| a20f96b76d | |||
| adf17b677e | |||
| 894cd087f4 | |||
| 44f1fef018 | |||
| 170a1407fe | |||
| b68f0a81e5 | |||
| 7fe8a9adc4 | |||
| af5c371e95 | |||
| 13c7f9a462 | |||
| 512e5e5bd1 | |||
| 7f782e4496 | |||
| c86b40f014 | |||
| 8ae45e7fe2 | |||
| 166ff0a093 | |||
| 82978fbd57 | |||
| 1186b3b67a | |||
| 30b948e68b | |||
| 1aa1461a2e | |||
| 92ae99bd29 | |||
| d4d33a4130 | |||
| 7a21c308be | |||
| 37cad52229 | |||
| 81315897f0 | |||
| 9254b6cf46 | |||
| 0e65fa85d3 | |||
| 4eb71ab555 | |||
| e9a64ef8a9 | |||
| a50d7f0f20 | |||
| 2903ffc54f | |||
| 5ea5310ab4 | |||
| 1256e4c645 | |||
| dc69d3e8d8 | |||
| 9c55ed0923 | |||
| 7c2c49d9b1 | |||
| 41bd2be68a | |||
| 66d7a4dcd8 | |||
| b9c026ce32 | |||
| c4457fba85 | |||
| d4b42995f7 | |||
| 485d57972e | |||
| 85c5a1ff8d | |||
| 7b8bb75228 | |||
| fe16457efc | |||
| 46533cac7d | |||
| 747a15841d | |||
| fc01f94387 | |||
| e9d4d17693 | |||
| 25f9c66834 | |||
| d25a3f13c2 | |||
| b13376e918 | |||
| c8b4ef7fed | |||
| b0f9f5ac21 | |||
| 6062f74c6b | |||
| eb365a01fb | |||
| eb0c88a9d4 | |||
| db3bffa284 | |||
| ca6c0de380 | |||
| d91ff81ca6 | |||
| 9d2140c9a1 | |||
| 7a0f454d39 | |||
| d5b717dc77 | |||
| 7cb517ce54 | |||
| a460404252 | |||
| d0287608b6 | |||
| 1532376710 | |||
| d3122db7b2 | |||
| b46b8933ab | |||
| 62f3e6db75 | |||
| a0c57b35a3 | |||
| 217f3ca0b4 | |||
| 3eba7538a4 | |||
| fa6f5db97f | |||
| ebea829d80 | |||
| e319c49891 | |||
| efd0a9f5f1 | |||
| 94888d5fd4 | |||
| ac8b064f47 | |||
| 3c133bff49 | |||
| 20bb3f7f2a | |||
| cc1f78a83d | |||
| ff02fd8aca | |||
| 063a2fadaa | |||
| 266b7328ef | |||
| 893b8e4cef | |||
| 1772de2531 | |||
| 75dc3a71b7 | |||
| 0be82dedb6 | |||
| 8a76674568 | |||
| 40800c97b2 | |||
| 6926f6dcc7 | |||
| a63cd1b06f | |||
| 9cf370cfb6 | |||
| ccaefcf69a | |||
| 15eb5ca4b8 | |||
| 224528f1de | |||
| ad2b61db80 | |||
| 344c8f6b5c | |||
| 4bc70ed401 | |||
| b95a6f57bf | |||
| 87e37e82fd | |||
| 8519cc796f | |||
| 827bd1f899 | |||
| ed0436d21e | |||
| 242746fd17 | |||
| f23fd0ee5e | |||
| 1087b3cb4e | |||
| f60c9b00ab | |||
| 6fe9b616aa | |||
| 655550e23a | |||
| 4bada2b954 | |||
| a87a1b7d3b | |||
| 4fae3b0a85 | |||
| 052bdfc17e | |||
| 476b8902bb | |||
| 6f0b92138f | |||
| cd271fc485 | |||
| 0bf65303ca | |||
| c4800fc6da | |||
| d51c2e05d3 | |||
| ce096afed7 | |||
| 06e7e7ff7b | |||
| bbbda080bf | |||
| 574f3faf06 | |||
| b56c86457c | |||
| 7212386e98 | |||
| b73a175386 | |||
| c9e6fec4bf | |||
| fcd37d0c7b | |||
| b40cd4e73f | |||
| 1b6e21d7a6 | |||
| 7ee1972599 | |||
| 24479b479d | |||
| 90a1a78b1e | |||
| 4a50075784 | |||
| 606bea72e1 | |||
| 4eb603430d | |||
| 76b1e0babe | |||
| f2248e604d | |||
| f56791e6c4 | |||
| 750d904a16 | |||
| 691a03f176 | |||
| 48fb171d7a | |||
| 8fd0512a3c | |||
| 5b16d508b5 | |||
| 05e313ad28 | |||
| b9fa7d2c9c | |||
| 8ce508cab0 | |||
| dffbec1c44 | |||
| ad0eccb4cd | |||
| 44d35257e8 | |||
| cf8a5e1eed | |||
| 6b0894c66a | |||
| a7058a5a13 | |||
| 1403af5be3 | |||
| 20b4d7d621 | |||
| 7331ed3e80 | |||
| 79dd109e37 | |||
| a305326973 | |||
| 3dca040a0b | |||
| 8327f1c371 | |||
| 73c833780c | |||
| f2cc1db1a8 | |||
| 34c2d1bdd1 | |||
| 29e95769b5 | |||
| e3c44f9c0f | |||
| 40d2042228 | |||
| ab494e4ede | |||
| 1cd02d55fb | |||
| f183f72bf4 | |||
| 8b6c710b09 | |||
| 04b589420e | |||
| e38e46ecdb | |||
| ace428669b | |||
| b37110cc82 | |||
| cde2fc3842 | |||
| ffcd442989 | |||
| 76dd27e7f7 | |||
| 9f86454b48 | |||
| b852df020c | |||
| fa926fb47c | |||
| 6fc2c29daa | |||
| a1b31d93b6 | |||
| 824f8c45ed | |||
| a0822259e7 | |||
| 9b8283d0fc | |||
| 04a7674bdd | |||
| 2d4cfc58ec | |||
| 0dc4797a4c | |||
| b668db242d | |||
| bbfe6fa50b | |||
| a15108e65b | |||
| aa1083bdac | |||
| b3c720b1c3 | |||
| 657245dcbd | |||
| 5af2555f59 | |||
| 49882dc151 | |||
| 19438d3021 | |||
| d5c481c2f4 | |||
| 8763e4efde | |||
| ecb8c1389c | |||
| d4690a8fa1 | |||
| d05f5eeb1d | |||
| 4362417495 | |||
| a9bbe11169 | |||
| 45e60cb52a | |||
| 211068ce50 | |||
| 051f7fb28c | |||
| 79666a3046 | |||
| 78abff6a52 | |||
| 1daa8e4a0f | |||
| dc76489221 | |||
| 7c503c4438 | |||
| b83690ebd8 | |||
| a34d920847 | |||
| 358c689cec | |||
| 2e8625c25f | |||
| ab86a5124a | |||
| 280972a66c | |||
| 200c758ff4 | |||
| 8492f37323 | |||
| f8c3798522 | |||
| 9f3477a279 | |||
| 046b931624 | |||
| 70549136ba | |||
| 6c0c750000 | |||
| 2f3c05651e | |||
| fa9a7bbb3c | |||
| 830612f555 | |||
| d49446ff98 | |||
| dc59f164a9 | |||
| deab6280d3 | |||
| d07abb5c42 | |||
| fb9df0c269 | |||
| af8292c1de | |||
| 38cf3413df | |||
| 800868e27e | |||
| c70bfefc68 | |||
| 15317991f3 | |||
| 4188f38ad5 | |||
| 7dd8b5026d | |||
| 6da20812ce | |||
| 887b015def | |||
| 505a74ad1d | |||
| 83cac9ac05 | |||
| a818ff2000 | |||
| 0ce85e0a7f | |||
| 86edff4447 | |||
| ebd6bf6007 | |||
| 1f78bd8471 | |||
| f83ffc3ac3 | |||
| 392f9a1b9c | |||
| 9f8541ef2a | |||
| cca9284b6a | |||
| 3e87066506 | |||
| 9cf6bac1a5 | |||
| b738bea9ca | |||
| 9c67b1b829 | |||
| 955fc67438 | |||
| 91b6be3186 | |||
| 17065e613f | |||
| cec19bd866 | |||
| 5594d7d054 | |||
| 2b58426b2d | |||
| 6a4bbf0fe5 | |||
| 8e68391a57 | |||
| 93edcab52e | |||
| ccd0b381b6 | |||
| d259f73665 | |||
| 0f6273cdb8 | |||
| 4e067ceabd | |||
| 58dbe10388 | |||
| d423113b00 | |||
| 26452f891f | |||
| 2f26729c84 | |||
| b6a3c8866a | |||
| d610968932 | |||
| 26b77483ee | |||
| d14e3f1e22 | |||
| b3cda72e93 | |||
| 3602405ec0 | |||
| d59536deea | |||
| 6400371ff9 | |||
| c1aad6d574 | |||
| cc1ec61b85 | |||
| c710f977b2 | |||
| 5425b0dd82 | |||
| 4247b37551 | |||
| 82980a148b | |||
| c13aac1bc3 | |||
| da87848cee | |||
| 25052a76ca | |||
| a13ff95777 | |||
| cdda041a90 | |||
| 6924253423 | |||
| 69213014d1 | |||
| aa126ba458 | |||
| c9563378ea | |||
| ba22fb1cef | |||
| 29cd317aff | |||
| 0bf95d865a | |||
| ae43ec99d9 | |||
| 7f186e21e0 | |||
| bca2853ade | |||
| 97d54f2ac8 | |||
| 8142d4b1e6 | |||
| 35d4e27588 | |||
| ec15d0a784 | |||
| c93a5c137f | |||
| 752cd4a8ef | |||
| 5d198e2b98 | |||
| 1d14e65315 | |||
| 67a8848aed | |||
| 30bd273580 | |||
| cbc75befb5 | |||
| a9f5e572dd | |||
| 8038b38802 | |||
| 79024eb004 | |||
| 0c3db64585 | |||
| 3dc8d84229 | |||
| 87f01ea2e9 | |||
| 0b9bb8cb78 | |||
| 009669360d | |||
| a342945b61 | |||
| 1fce49fac7 | |||
| a50583fb97 | |||
| d18ff7d956 | |||
| 3ec62cf95a | |||
| ab157bbb75 | |||
| f61ffec352 | |||
| 88f0e31622 | |||
| 38eecef26b | |||
| 99408dfcf3 | |||
| 0bf0dfe10d | |||
| e3b00bcaf5 | |||
| 6caba976ec | |||
| 1d6a9ac25a | |||
| 03e501dedd | |||
| 18209292a4 | |||
| 007f26e582 | |||
| 04b43dc097 | |||
| becc5bfbf2 | |||
| 230fa05eb7 | |||
| 96834a47b0 | |||
| e212e6b12a | |||
| 404adf970d | |||
| 7d5d9ea449 | |||
| 74e09e562b | |||
| 60c3a4d3e1 | |||
| f3f0b11393 | |||
| f2b6b4e230 | |||
| 6a3c510157 | |||
| 4555c38d3b | |||
| 24696800e5 | |||
| 818b186f8a | |||
| 4910af33ff | |||
| 0626e52f3c | |||
| f034640ca3 | |||
| 1829b362fc | |||
| 34acb02cbb | |||
| 4877836b12 | |||
| 07ed2b0231 | |||
| 9c743ab965 | |||
| 78bddb22be | |||
| decc23aaf2 | |||
| 2395a3802a | |||
| 7ec445b06e | |||
| 21f29fe492 | |||
| 5b5e47c3ae | |||
| b6413975c3 | |||
| d151b51c67 | |||
| 827f5b42a6 | |||
| ef17914960 | |||
| 72a857158c | |||
| 87090d8ae7 | |||
| 8af31ae0f7 | |||
| feecea2806 | |||
| 07da861126 | |||
| c97d23c533 | |||
| 4d99bae893 | |||
| 2a6440c134 | |||
| 4dbba60439 | |||
| 29cf52b677 | |||
| fd86559a5b | |||
| d70dd7ac69 | |||
| 43c7b978c3 | |||
| dfff18f81b | |||
| a4a870e1ec | |||
| 03303cd71b | |||
| c2cf7075c2 | |||
| bfe1ad6dbc | |||
| 6212acfc81 | |||
| 847f57686e | |||
| 44fd0faa23 | |||
| df6c8b3be9 | |||
| a192c21c6a | |||
| f3312ce58d | |||
| 603ecb0052 | |||
| 4a74d9857c | |||
| 7256e9e139 | |||
| f09d76b0ea | |||
| 0f859c6f32 | |||
| 389d751e92 | |||
| 5ce3978472 | |||
| d5f36a8d9e | |||
| 08f539f738 | |||
| 6e8030a0db | |||
| b96b075b55 | |||
| f8e9d8bdbc | |||
| bf20b541ae | |||
| 3945335f39 | |||
| 2c534c184d | |||
| a8ef1f3f43 | |||
| 86da39886d | |||
| a7f39918bf | |||
| 69c57493e7 | |||
| 5fe1948097 | |||
| d9b51a21fa | |||
| 9cb9ab552b | |||
| 7a873ef1d7 | |||
| 292cd0dbd5 | |||
| f86784c22d | |||
| 2dd5c1e4a3 | |||
| 29d9c0ffe1 | |||
| d7ee9f986b | |||
| 1bcbe8bebf | |||
| db117db3c5 | |||
| 4a16f33dcc | |||
| 0d67b00d5d | |||
| 7d2f1aa279 | |||
| bfa32856bc | |||
| 06e1a9a98a | |||
| d9bb0cdeb8 | |||
| 4d2bd6e507 | |||
| a297fb1e38 | |||
| a4585f7eaa | |||
| 182faf2696 | |||
| d967a1fa14 | |||
| 22db488d21 | |||
| 1be13ba1fc | |||
| d6d8a045e6 | |||
| 1d0995bb8e | |||
| 3345c9dc39 | |||
| fecb62a396 | |||
| ad697686c0 | |||
| cdada41505 | |||
| 08718c01e4 | |||
| b152f2b6ba | |||
| 04fbe8f5ef | |||
| e68348f627 | |||
| 9591ee2603 | |||
| 5814ef0d25 | |||
| d607330557 | |||
| ad03eb6286 | |||
| cc60cbbbab | |||
| d619120fc4 | |||
| 2c4e7a1cea | |||
| a33a3d2afb | |||
| 03832b45e1 | |||
| 028e34b6c4 | |||
| ad53faf25c | |||
| 0c51bfea6f | |||
| 6fa3f70bc2 | |||
| db41c907aa | |||
| 2a97194253 | |||
| a7048bc45d | |||
| eb5b677250 | |||
| 897b2b5302 | |||
| 5805ac4574 | |||
| 07e028fe5c | |||
| 08dde123b1 | |||
| 82cff5af70 | |||
| 8754bd88a5 | |||
| 6a915551ab | |||
| ed02e38e1d | |||
| 8d7cd2ccd5 | |||
| c0586a906c | |||
| 181d16fe22 | |||
| 3ee27ee6ba | |||
| 6775da70a8 | |||
| f7382cd8c3 | |||
| 7339b0b08d | |||
| 1acd18510a | |||
| fb26cc9375 | |||
| d47a05a9a5 | |||
| 17b2afefad | |||
| 4744cb0e1d | |||
| 7c6aa8d826 | |||
| b3f7b7d200 | |||
| 1ab567f6e3 | |||
| f0d584503f | |||
| 042486c511 | |||
| ded2c63312 | |||
| bf79463070 | |||
| f98373cc34 | |||
| 25114854b3 | |||
| 4ca17924a1 | |||
| 9a010227d7 | |||
| e1a625ad35 | |||
| eace9f914d | |||
| d3b52886f5 | |||
| 71b0d53c5e | |||
| 11c680f97a | |||
| 223268c2fa | |||
| 53e3158dfe | |||
| 31a10069a5 | |||
| 4c14936353 | |||
| 2bb207d005 | |||
| d429305836 | |||
| 49c803425c | |||
| cf75bb31e9 | |||
| fc52df0677 | |||
| 6065553c13 | |||
| 346380e131 | |||
| affbd83b48 | |||
| 381d182726 | |||
| 2048661b0c | |||
| 6a0329f756 | |||
| 35c7e0a69c | |||
| decb9a5814 | |||
| 61e79d9344 | |||
| 36a733af8d | |||
| a8464c9719 | |||
| e145f767f3 | |||
| 56532c4e72 | |||
| fa37042b32 | |||
| 0e7f04b04e | |||
| cbcae4037c | |||
| 72f7ff0589 | |||
| 2c83741171 | |||
| 78dd437928 | |||
| f5b5622a89 | |||
| bb75d2b01a | |||
| 2cf87a4da1 | |||
| 9fad46bd0e | |||
| dcc73856a9 | |||
| 4b842b20ce | |||
| 49b56f19d8 | |||
| 25e1213d1b | |||
| 92fc97eeb3 | |||
| 48e063904d | |||
| 99679d0688 | |||
| d3b47e9470 | |||
| 1570a65381 | |||
| d4949327ef | |||
| c9bd17a100 | |||
| 0a022f9a39 | |||
| 565bb72d99 | |||
| e5382002b4 | |||
| 3628b24d12 | |||
| cd425599ce | |||
| e7345a2c4f | |||
| 032e0ca13a | |||
| 3ade95a3d7 | |||
| fddf4fbacc | |||
| 926acd7bba | |||
| 8975653d4c | |||
| 689de3dbcc | |||
| ab5bb94b12 | |||
| 6203ef8e51 | |||
| e83cf5a787 | |||
| d09a5674e9 | |||
| d0a599bbae | |||
| 30c12d3927 | |||
| 860473f33c | |||
| e1cfef7bf1 | |||
| b4fd2154fe | |||
| f37891fdb6 | |||
| aad8fbab09 | |||
| 7785f0c75f | |||
| a71dc5d7d0 | |||
| 655214ab30 | |||
| fb5a9666ed | |||
| 60ca369cd3 | |||
| b89d5a2bf4 | |||
| 53ae58e1a1 | |||
| 792097fb6a | |||
| 970cfb1166 | |||
| 01cd443441 | |||
| 488fc63b67 | |||
| 6285e57c49 | |||
| 243e13ab59 | |||
| 5e98c2183a | |||
| 9f3148fec7 | |||
| c9357429fd | |||
| 41265e07d4 | |||
| 2e4440c3f8 | |||
| 3141347214 | |||
| 943ac3c77e | |||
| 83b47311ba | |||
| 16fd1cce61 | |||
| f14807de06 | |||
| ed2853564e | |||
| 26170f4613 | |||
| 68268c0199 | |||
| 5966d2c2d3 | |||
| 26929c08d3 | |||
| 044bf638a8 | |||
| 58f6269f36 | |||
| 3a68883ae9 | |||
| 211ed48361 | |||
| c515ffec9c | |||
| 4e09039d2c | |||
| 1e1e4e4eca | |||
| c8265d95b0 | |||
| 2e384abab6 | |||
| 736a4fb77e | |||
| 38dafee05d | |||
| fa0bfb775a | |||
| 445a1a1c8d | |||
| 1e1fd6f24d | |||
| a678f9df38 | |||
| f85bfdf186 | |||
| cae70cdbdb | |||
| ebae8c8315 | |||
| 6af66b1106 | |||
| f355d2c87f | |||
| f4fbfaa7cb | |||
| 21f50d5a08 | |||
| 1b539ba1ec | |||
| 3e0e7e1208 | |||
| b8fdd2d85f | |||
| c95b78a8ce | |||
| 3ae345b3d7 | |||
| d1d3498b62 | |||
| f878daeb8b | |||
| 9ba98a0abe | |||
| 1cecaa7926 | |||
| 2744d07d71 | |||
| 5ed8050791 | |||
| c3b261e321 | |||
| 9ffce01e0c | |||
| 8905191413 | |||
| b4b22940df | |||
| e1cf0fda27 | |||
| f41d00ed8a | |||
| d866e8be91 | |||
| be4c8197eb | |||
| fcb5fd27e2 | |||
| 9c9b226589 | |||
| a562e3905a | |||
| 607e12b4f2 | |||
| 7f66783976 | |||
| 2abcccb371 | |||
| 9bc32632af | |||
| 52e3f58c72 | |||
| b5c1ed1227 | |||
| 4d058d4824 | |||
| cb4fba5a33 | |||
| 4a84d94e91 | |||
| 0b57c6825a | |||
| 529db4861d | |||
| 9de34d4e84 | |||
| d7ad5d6560 | |||
| aeea7c6af0 | |||
| b1bfd4cb0c | |||
| 2eb111a300 | |||
| 76e487cd7e | |||
| 60fc4f4b1a | |||
| da5fc42f61 | |||
| 1151e9cc8f | |||
| dfde415198 | |||
| e2b83a8298 | |||
| 5cfafc6110 | |||
| 1810c13b55 | |||
| a0aa150418 | |||
| 5c8d438c08 | |||
| 17bd2cc94c | |||
| cbfd5a1019 | |||
| 04fcbad8c5 | |||
| 5df72bb4a4 | |||
| 41acd466ce | |||
| 43c1115eeb | |||
| db75a4425c | |||
| 0f9d3ef173 | |||
| 05d6dd487c | |||
| d460914f65 | |||
| 6bf4702608 | |||
| f014856424 | |||
| c432fa1674 | |||
| f778e47283 | |||
| 4886ed6d36 | |||
| 74ec445a66 | |||
| 6cab59c340 | |||
| 2e2ebe5ec7 | |||
| 68e2061666 | |||
| 7b171c7340 | |||
| 5bea1a4d31 | |||
| 9e7c840b18 | |||
| ac4d114214 | |||
| d5501950e2 | |||
| 42c80841c8 | |||
| 0b5c6ff319 | |||
| 05b6401817 | |||
| 59cc585271 | |||
| f0133fe5f4 | |||
| b8bb2b4ab6 | |||
| 72c20a5297 | |||
| 5846b0f1b3 | |||
| 39cc09dfc3 | |||
| 678f2cb6ee | |||
| 13991e5288 | |||
| 5c0ad7376a | |||
| af1daea191 | |||
| 9772578ee2 | |||
| 16ac4e3dbe | |||
| 2ab37d6205 | |||
| e070b9b511 | |||
| e232d5532a | |||
| 74dc587452 | |||
| 87a44f4704 | |||
| c2b7a11c77 | |||
| 7a4482b8a4 | |||
| defa7754a4 | |||
| 1bf152a551 | |||
| 99c8761b75 | |||
| 0c2f453750 | |||
| d7aec74403 | |||
| 760d44d194 | |||
| f2d3ee98a6 | |||
| 705f04937f | |||
| 5862c872c6 | |||
| b7066c0333 | |||
| e4178da65c | |||
| c7abf20bbe | |||
| b3a2586ecf | |||
| 45e9e0f565 | |||
| 352523640d | |||
| 05824223a9 | |||
| f616ab60ef | |||
| 363bc4eb86 | |||
| 2b191d8c37 | |||
| 7d7ed6a0ee | |||
| 83020b993c | |||
| 985ce3ec53 | |||
| 33fe6a46a4 | |||
| cd8a344156 | |||
| 6ae804853f | |||
| b6d859fa92 | |||
| 1a0d776394 | |||
| 1ba1628ed6 | |||
| 027b4e1568 | |||
| df6afaf090 | |||
| c1e24b0461 | |||
| 36b9219842 | |||
| 8e76eaad19 | |||
| fad925644f | |||
| a84f77d6ba | |||
| 0c994bd934 | |||
| 18889e230a | |||
| e557b7bb44 | |||
| 3ae9190e78 | |||
| e231f3f023 | |||
| 2502e1359c | |||
| 20f00edd15 | |||
| 2f3425dff6 | |||
| 272600ff80 | |||
| 1745ebc832 | |||
| 5188585864 | |||
| 1e98ee1de0 | |||
| 5011388f39 | |||
| 5e07dc8b51 | |||
| 894c36ea32 | |||
| 031df528b6 | |||
| 9d3b88b379 | |||
| 5eebe4e50d | |||
| 2916d24b20 | |||
| 3352332f3f | |||
| 2621569200 | |||
| 93eed12505 | |||
| 125f9ee838 | |||
| 8df8eb6c95 | |||
| bf7516112f | |||
| 5a26af476e | |||
| 2287bf06f5 | |||
| 89812ec81c | |||
| 06fef43180 | |||
| 01e671f4d1 | |||
| d47d2533ac | |||
| 27a74816da | |||
| 47baa1077e | |||
| f0f7b94362 | |||
| 747c6698b6 | |||
| 8f91e10faa | |||
| 6cd8af85da | |||
| 34d67c835e | |||
| 7f17a38d35 | |||
| 07ae20eeed | |||
| 4cc3c2ac17 | |||
| 66e074b43d | |||
| eb44ca4213 | |||
| dfbbc14b33 | |||
| 34bf601a56 | |||
| 969a91a1e3 | |||
| fbe8e27568 | |||
| 4ee705a79c | |||
| 8623a53200 | |||
| 4e5b04113d | |||
| 3cc22aab82 | |||
| be2b9055b9 | |||
| 92cd6e9af8 | |||
| 58ace4941e | |||
| 37527034ab | |||
| 88e1108f11 | |||
| 3408ed48ba | |||
| b8c67f8068 | |||
| 79026b73a8 | |||
| 6a6c1c1172 | |||
| 18bd1cc3b1 | |||
| 74c0733c1e | |||
| f55f734fda | |||
| d477db2cfb | |||
| 6fe07ab815 | |||
| 1a1142893f | |||
| 7bda34c66b | |||
| 2af5015668 | |||
| 5801355cbc | |||
| d6b28d43c8 | |||
| 6d7e6f5e36 | |||
| 469590fde5 | |||
| 00dbaf90bc | |||
| 705250b93d | |||
| 0d64be15de | |||
| f6597c7cb9 | |||
| a8778dc23e | |||
| d62dfd88d2 | |||
| d081f272b0 | |||
| 1b2abab6dd | |||
| 10ab20d8e2 | |||
| 48207b6814 | |||
| 1a08e7b6f6 | |||
| b9523a0ba0 | |||
| 084ec2a63d | |||
| b9e0514783 | |||
| 3e05742568 | |||
| 660b998eb7 | |||
| b084cde854 | |||
| 8ca368e7e3 | |||
| a0fd7c5b44 | |||
| 7eb64927cc | |||
| 9074534c93 | |||
| 12d9cfbcaa | |||
| f3a6080fce | |||
| f16b0747a4 | |||
| 964481d023 | |||
| d143cae25a | |||
| d11e2bcf48 | |||
| 3c33e40b61 | |||
| eaf2c769be | |||
| ce4a1dcc19 | |||
| 2230a38cd6 | |||
| ef6051b95e | |||
| af1d279226 | |||
| d5ce28df67 | |||
| 693b3f8677 | |||
| a322312740 | |||
| 2edde7fe33 | |||
| 08a12b6dbb | |||
| 876bb3af42 | |||
| 7f9f5281e5 | |||
| 3eb049036e | |||
| 093f1efb21 | |||
| 7d1778bbd2 | |||
| 5ebf6eeca1 | |||
| 6fb4600334 | |||
| b6b36e1b5a | |||
| c51be6b697 | |||
| 063fc1a7ba | |||
| 07a5909d73 | |||
| ec3972361d | |||
| fccd59e2e3 | |||
| eb16ab407b | |||
| e6ba035911 | |||
| 8e1ed353bb | |||
| a4990dd294 | |||
| b4f5c46ab0 | |||
| 6fa7d08860 | |||
| 3044b21eb6 | |||
| d7e8dd590c | |||
| b66802446d | |||
| d7c2f0cc47 | |||
| 27574abf46 | |||
| e6a8dd60af | |||
| 1cbd70639c | |||
| 217bacc663 | |||
| 83954e711d | |||
| 149df445d6 | |||
| 38b95e7be9 | |||
| db4767e22b | |||
| 66b6a3b5e2 | |||
| 7374ff30ef | |||
| 06cf8fb963 | |||
| 6a7d779024 | |||
| 5ae45084dc | |||
| 38b418b7d6 | |||
| fa5f9764ae | |||
| fd99a8c02d | |||
| 0269cd8213 | |||
| d29b3e5f9e | |||
| cb844c4238 | |||
| 679b6b85ad | |||
| 258d7032bb | |||
| 6d7defc87c | |||
| b548b2192c | |||
| f3e4f109a3 | |||
| 8413a63f7d | |||
| 76beb27135 | |||
| 12c9c72984 | |||
| cf9e886076 | |||
| 3008e36a3e | |||
| b64a882696 | |||
| 8be8c44596 | |||
| b48fd706b7 | |||
| d9afe1abd7 | |||
| 70c39d1618 | |||
| ffc966d72f | |||
| 1e0f9166cc | |||
| c2618ca791 | |||
| 4b4afc7322 | |||
| 8340fedd64 | |||
| b194cf21dc | |||
| 29773c9729 | |||
| fd21828080 | |||
| a458cff25e | |||
| e93c7c9c46 | |||
| 00e4700abb | |||
| 096fb74bf1 | |||
| 64fa45e262 | |||
| 57c91e427d | |||
| 2bf93dc034 | |||
| 746f93c290 | |||
| 38330bfd65 | |||
| 2fdcbd1ac3 | |||
| 8d52e6b4e3 | |||
| e8d1f1e5f0 | |||
| 7ba37bd91a | |||
| 9067b484ce | |||
| c3df0ebdc2 | |||
| abed0f2122 | |||
| 12c4098773 | |||
| bb5a7d9ede | |||
| 1c9cd3463b | |||
| 6bc2da5191 | |||
| 042b590606 | |||
| 0b05568c9f | |||
| 2021fc614e | |||
| c0d321c1bf | |||
| 20ced8a806 | |||
| b448a9214c | |||
| 4a2912880f | |||
| ca1b0a1a6f | |||
| 86848edc84 | |||
| f93aaa65c4 | |||
| cd03b575df | |||
| 667009727a | |||
| 6a9e4bfc90 | |||
| 15493df62d | |||
| 82051a6a6a | |||
| 5bf30b0060 | |||
| baea9b6de5 | |||
| fa0483b361 | |||
| b58e261db9 | |||
| d8d1542e52 | |||
| 56ab22d02c | |||
| 6c15854448 | |||
| d91787589b | |||
| 3d8bded89e | |||
| 5f9bff0f71 | |||
| 301e4c5acb | |||
| 82757801b9 | |||
| 9a8b4ff4ed | |||
| 572e758bf2 | |||
| 64f8970215 | |||
| a3436d4cba | |||
| 07ee09f49a | |||
| 25b5caeed1 | |||
| 313f4ca785 | |||
| ed06f04077 | |||
| 7aa8ccc47d | |||
| 04b55e97de | |||
| 76e9ca2acf | |||
| 77dea8a234 | |||
| 01c0e050ad | |||
| 339d510fda | |||
| 6e5f8db8bb | |||
| 145f50402e | |||
| 1d517de67b | |||
| c7ec97ce98 | |||
| b916bcfccc | |||
| 580d60b941 | |||
| 68857cea8c | |||
| bc1ee8524e | |||
| 8d3275bee4 | |||
| 7ce7ec4c94 | |||
| 17a9cb9608 | |||
| f6df40db46 | |||
| 6fb3a2a185 | |||
| d28a7ca30f | |||
| 6a361945ea | |||
| 55821e04c1 | |||
| b161295d0b | |||
| 4d0e254491 | |||
| 3252078501 | |||
| 21e0af98eb | |||
| 7f959169b7 | |||
| e4ed594d82 | |||
| a12832488d | |||
| a62788c61e | |||
| 3208d538a7 | |||
| 2a1791a4b1 | |||
| 63c35580c7 | |||
| c765c3679f | |||
| eb1af59219 | |||
| 3ba5f81b7b | |||
| 07b9821e24 | |||
| 15771f4452 | |||
| 3dcab1a782 | |||
| 46b77928f7 | |||
| 68abd9c71b | |||
| d51ecb6ea8 | |||
| 7d2eb7a7b9 | |||
| 4f5b44bd3b | |||
| 2b840e0cfb | |||
| 8cbb2a8802 | |||
| c67e13e04b | |||
| afe60d614b | |||
| 161395d709 | |||
| 5ffe5cf541 | |||
| 8069e235fd | |||
| 45161a6402 | |||
| a4565e88ed | |||
| f6c9baab3e | |||
| da2c5d6fc3 | |||
| 408f3df28e | |||
| 2ee436eaa1 | |||
| 02ea9f0769 | |||
| 99dd7d3877 | |||
| 4d8bcc0c6b | |||
| 538cdfa883 | |||
| 70b5d24f72 | |||
| 3db95a85de | |||
| 95a7596cb4 | |||
| 85ebc80c7e | |||
| 6588517b2d | |||
| 6d37de5f82 | |||
| 9c902708da | |||
| 66ce09ba03 | |||
| 6499b26ae8 | |||
| da368cc84f | |||
| aa8c9f2a32 | |||
| baa8617364 | |||
| 494e21b4da | |||
| 0ace6cab0b | |||
| c1c9f252f7 | |||
| 2987031bc3 | |||
| 3e7188185d | |||
| 8d5aab49c1 | |||
| cdcc8d2533 | |||
| ae9571694f | |||
| 4ddbd26787 | |||
| 8444bb0600 | |||
| 29c6fd4607 | |||
| 37f2773aa5 | |||
| e682978e5b | |||
| 69ca1455d1 | |||
| 88958248fa | |||
| 2761de0184 | |||
| d2beefd27c | |||
| 8af1077265 | |||
| 9acfade829 | |||
| d3607de929 | |||
| 176d733a44 | |||
| e858ac2776 | |||
| 37c6ed4e7a | |||
| f0070a15e4 | |||
| 6f87a19714 | |||
| 44e77bfa24 | |||
| 016989b79a | |||
| a58cd56c4f | |||
| cddd8e90f4 | |||
| bbff4b57a9 | |||
| f281635c9a | |||
| 8c72b98d9d | |||
| 6a02410a9b | |||
| 2cd263e0bd | |||
| 4e97e6e8b9 | |||
| b4397510e7 | |||
| 682536a9e7 | |||
| 14890de35a | |||
| ff4d8c8c1e | |||
| bf18db78b3 |
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
; top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
; Unix-style newlines
|
||||
[*]
|
||||
end_of_line = LF
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# Cache and logs (Symfony2)
|
||||
/app/cache/*
|
||||
/app/logs/*
|
||||
!app/cache/.gitkeep
|
||||
!app/logs/.gitkeep
|
||||
|
||||
# Cache and logs (Symfony3)
|
||||
/var/cache/*
|
||||
/var/logs/*
|
||||
!var/cache/.gitkeep
|
||||
!var/logs/.gitkeep
|
||||
|
||||
# Parameters
|
||||
/app/config/parameters.yml
|
||||
/app/config/parameters.ini
|
||||
|
||||
# Managed by Composer
|
||||
/app/bootstrap.php.cache
|
||||
/var/bootstrap.php.cache
|
||||
/bin/*
|
||||
!bin/console
|
||||
!bin/symfony_requirements
|
||||
/vendor/
|
||||
|
||||
# Assets and user uploads
|
||||
/web/bundles/
|
||||
/web/uploads/
|
||||
|
||||
# Build
|
||||
/app/build
|
||||
/build
|
||||
|
||||
# Composer PHAR
|
||||
/composer.phar
|
||||
|
||||
# Data for wallabag
|
||||
data/assets/*
|
||||
data/db/wallabag*.sqlite
|
||||
27
.scrutinizer.yml
Normal file
@ -0,0 +1,27 @@
|
||||
filter:
|
||||
paths:
|
||||
- src/*
|
||||
excluded_paths:
|
||||
- 'vendor/*'
|
||||
- 'app/*'
|
||||
- 'web/*'
|
||||
- 'src/Wallabag/*Bundle/Tests/*'
|
||||
- '*Test.php'
|
||||
|
||||
tools:
|
||||
php_cs_fixer: true
|
||||
php_analyzer: true
|
||||
php_mess_detector: true
|
||||
php_changetracking: true
|
||||
php_code_sniffer: true
|
||||
php_pdepend: true
|
||||
sensiolabs_security_checker: true
|
||||
#external_code_coverage:
|
||||
# timeout: 3600
|
||||
php_code_coverage: true
|
||||
php_sim: false
|
||||
php_cpd: false
|
||||
|
||||
checks:
|
||||
php:
|
||||
code_rating: true
|
||||
45
.travis.yml
Normal file
@ -0,0 +1,45 @@
|
||||
language: php
|
||||
|
||||
# faster builds on docker-container setup
|
||||
sudo: false
|
||||
|
||||
# cache vendor dirs
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
- $HOME/.composer/cache
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- hhvm
|
||||
- nightly
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
- php: nightly
|
||||
|
||||
branches:
|
||||
only:
|
||||
- v2
|
||||
|
||||
install:
|
||||
- composer self-update
|
||||
|
||||
# build coverage only on one build, to speed up results feedbacks
|
||||
# before_script:
|
||||
# - if [[ "$TRAVIS_PHP_VERSION" = "5.6" ]]; then PHPUNIT_FLAGS="--coverage-clover=coverage.clover"; else PHPUNIT_FLAGS=""; fi;
|
||||
|
||||
script:
|
||||
- ant prepare
|
||||
- bin/phpunit --exclude-group command-doctrine --debug $PHPUNIT_FLAGS
|
||||
|
||||
# after_script:
|
||||
# - |
|
||||
# if [ $TRAVIS_PHP_VERSION = '5.6' ]; then
|
||||
# wget https://scrutinizer-ci.com/ocular.phar
|
||||
# php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
||||
# fi
|
||||
30
CONTRIBUTING.md
Normal file
@ -0,0 +1,30 @@
|
||||
# How to contribute
|
||||
|
||||
## You found a bug
|
||||
Please [open a new issue](https://github.com/wallabag/wallabag/issues/new).
|
||||
|
||||
To fix the bug quickly, we need some infos:
|
||||
* your wallabag version (on top of the ./index.php file, and also on config page)
|
||||
* your webserver installation :
|
||||
* type of hosting (shared or dedicated)
|
||||
* in case of a dedicated server, the server and OS used
|
||||
* the php version used, eventually `phpinfo()`
|
||||
* which storage system you choose at install (SQLite, MySQL/MariaDB or PostgreSQL)
|
||||
* any problem on the `wallabag_compatibility_test.php` page
|
||||
* any particular details which could be related
|
||||
|
||||
|
||||
If relevant :
|
||||
* the link you want to save and which causes problem
|
||||
* the file you want to import into wallabag, or just an extract
|
||||
|
||||
If you have the skills :
|
||||
* enable DEBUG mode and look the output at cache/log.txt
|
||||
* look for errors into php and server logs
|
||||
|
||||
Note : If you have large portions of text, use [Github's Gist service](https://gist.github.com/) or other pastebin-like.
|
||||
|
||||
## You want to fix a bug or to add a feature
|
||||
Please fork wallabag and work with **the dev branch** only. **Do not work on master branch**.
|
||||
|
||||
[Don't forget to read our guidelines](https://github.com/wallabag/wallabag/blob/dev/GUIDELINES.md).
|
||||
14
COPYING
@ -1,14 +0,0 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
19
COPYING.md
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2013-2015 Nicolas Lœuillet
|
||||
|
||||
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.
|
||||
16
CREDITS
@ -1,16 +0,0 @@
|
||||
poche is based on :
|
||||
* ReadItYourself http://www.memiks.fr/readityourself/
|
||||
* PHP Readability http://www.keyvan.net/2010/08/php-readability/
|
||||
* Encoding https://github.com/neitanod/forceutf8
|
||||
* logo by Brightmix http://www.iconfinder.com/icondetails/43256/128/jeans_monotone_pocket_icon
|
||||
* icons http://icomoon.io
|
||||
* PHP Simple HTML DOM Parser (for Pocket import) http://simplehtmldom.sourceforge.net/
|
||||
* Session https://github.com/tontof/kriss_feed/blob/master/src/class/Session.php
|
||||
|
||||
poche is developed by Nicolas Lœuillet under the Do What the Fuck You Want to Public License
|
||||
|
||||
Contributors :
|
||||
Nicolas Lœuillet aka nico_somb
|
||||
Tom.C. aka tmos
|
||||
PeaceCopathe
|
||||
Gregoire_M
|
||||
6
CREDITS.md
Normal file
@ -0,0 +1,6 @@
|
||||
wallabag is mainly developed by [Nicolas Lœuillet](https://github.com/nicosomb) under the MIT License.
|
||||
|
||||
Thank you so much to [@tcitworld](https://github.com/tcitworld) and [@j0k3r](https://github.com/j0k3r).
|
||||
|
||||
Thank you [to others contributors](https://github.com/wallabag/wallabag/graphs/contributors
|
||||
).
|
||||
29
Capfile
Normal file
@ -0,0 +1,29 @@
|
||||
# Load DSL and set up stages
|
||||
require 'capistrano/setup'
|
||||
|
||||
# Include default deployment tasks
|
||||
require 'capistrano/deploy'
|
||||
|
||||
require 'capistrano/symfony'
|
||||
|
||||
# Include tasks from other gems included in your Gemfile
|
||||
#
|
||||
# For documentation on these, see for example:
|
||||
#
|
||||
# https://github.com/capistrano/rvm
|
||||
# https://github.com/capistrano/rbenv
|
||||
# https://github.com/capistrano/chruby
|
||||
# https://github.com/capistrano/bundler
|
||||
# https://github.com/capistrano/rails
|
||||
# https://github.com/capistrano/passenger
|
||||
#
|
||||
# require 'capistrano/rvm'
|
||||
# require 'capistrano/rbenv'
|
||||
# require 'capistrano/chruby'
|
||||
# require 'capistrano/bundler'
|
||||
# require 'capistrano/rails/assets'
|
||||
# require 'capistrano/rails/migrations'
|
||||
# require 'capistrano/passenger'
|
||||
|
||||
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
|
||||
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
|
||||
53
GUIDELINES.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Guidelines for wallabag
|
||||
|
||||
If you want to contribute to wallabag, you have some rules to respect. These rules were defined by [PHP Framework Interop Group](http://www.php-fig.org).
|
||||
|
||||
## Basic Coding Standard (PSR-1)
|
||||
|
||||
This section of the standard comprises what should be considered the standard coding elements that are required to ensure a high level of technical interoperability between shared PHP code.
|
||||
|
||||
* Files MUST use only `<?php` and `<?=` tags.
|
||||
|
||||
* Files MUST use only UTF-8 without BOM for PHP code.
|
||||
|
||||
* Files SHOULD either declare symbols (classes, functions, constants, etc.) or cause side-effects (e.g. generate output, change .ini settings, etc.) but SHOULD NOT do both.
|
||||
|
||||
* Namespaces and classes MUST follow [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md).
|
||||
|
||||
* Class names MUST be declared in `StudlyCaps`.
|
||||
|
||||
* Class constants MUST be declared in all upper case with underscore separators.
|
||||
|
||||
* Method names MUST be declared in `camelCase`.
|
||||
|
||||
You can read details on [PHP FIG website](http://www.php-fig.org/psr/psr-1/).
|
||||
|
||||
## Coding Style Guide (PSR-2)
|
||||
|
||||
This guide extends and expands on PSR-1, the basic coding standard.
|
||||
|
||||
The intent of this guide is to reduce cognitive friction when scanning code from different authors. It does so by enumerating a shared set of rules and expectations about how to format PHP code.
|
||||
|
||||
The style rules herein are derived from commonalities among the various member projects. When various authors collaborate across multiple projects, it helps to have one set of guidelines to be used among all those projects. Thus, the benefit of this guide is not in the rules themselves, but in the sharing of those rules.
|
||||
|
||||
* Code MUST follow PSR-1.
|
||||
|
||||
* Code MUST use 4 spaces for indenting, not tabs.
|
||||
|
||||
* There MUST NOT be a hard limit on line length; the soft limit MUST be 120 characters; lines SHOULD be 80 characters or less.
|
||||
|
||||
* There MUST be one blank line after the `namespace` declaration, and there MUST be one blank line after the block of `use` declarations.
|
||||
|
||||
* Opening braces for classes MUST go on the next line, and closing braces MUST go on the next line after the body.
|
||||
|
||||
* Opening braces for methods MUST go on the next line, and closing braces MUST go on the next line after the body.
|
||||
|
||||
* Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility.
|
||||
|
||||
* Control structure keywords MUST have one space after them; method and function calls MUST NOT.
|
||||
|
||||
* Opening braces for control structures MUST go on the same line, and closing braces MUST go on the next line after the body.
|
||||
|
||||
* Opening parentheses for control structures MUST NOT have a space after them, and closing parentheses for control structures MUST NOT have a space before.
|
||||
|
||||
You can read details on [PHP FIG website](http://www.php-fig.org/psr/psr-2/).
|
||||
5
Gemfile
Normal file
@ -0,0 +1,5 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem 'capistrano', '~> 3.1'
|
||||
gem 'capistrano-symfony', '~> 0.1', :github => 'capistrano/symfony'
|
||||
gem 'capistrano-composer', '~> 0.0.3'
|
||||
41
Gemfile.lock
Normal file
@ -0,0 +1,41 @@
|
||||
GIT
|
||||
remote: git://github.com/capistrano/symfony.git
|
||||
revision: ca56a01b817097d2831400ef9b1867fc8e07dcf8
|
||||
specs:
|
||||
capistrano-symfony (0.4.0)
|
||||
capistrano (~> 3.1)
|
||||
capistrano-composer (~> 0.0.3)
|
||||
capistrano-file-permissions (~> 0.1.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
capistrano (3.4.0)
|
||||
i18n
|
||||
rake (>= 10.0.0)
|
||||
sshkit (~> 1.3)
|
||||
capistrano-composer (0.0.6)
|
||||
capistrano (>= 3.0.0.pre)
|
||||
capistrano-file-permissions (0.1.1)
|
||||
capistrano (~> 3.1)
|
||||
colorize (0.7.7)
|
||||
i18n (0.7.0)
|
||||
net-scp (1.2.1)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (2.9.2)
|
||||
rake (10.4.2)
|
||||
sshkit (1.7.1)
|
||||
colorize (>= 0.7.0)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
capistrano (~> 3.1)
|
||||
capistrano-composer (~> 0.0.3)
|
||||
capistrano-symfony (~> 0.1)!
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.6
|
||||
61
README.md
@ -1,55 +1,26 @@
|
||||
# poche
|
||||
Abandon Pocket, Instapaper and other Readability service : adopt poche. It is the same, but it is open source.
|
||||
[](https://travis-ci.org/wallabag/wallabag)
|
||||
[](https://scrutinizer-ci.com/g/wallabag/wallabag/?branch=v2)
|
||||
[](https://scrutinizer-ci.com/g/wallabag/wallabag/?branch=v2)
|
||||
|
||||

|
||||
# What is wallabag ?
|
||||
wallabag is a self hostable application allowing you to not miss any content anymore.
|
||||
Click, save, read it when you can. It extracts content so that you can read it when you have time.
|
||||
|
||||
The website of poche is [inthepoche.com](http://inthepoche.com).
|
||||
More informations on our website: [wallabag.org](http://wallabag.org)
|
||||
|
||||
To get news from poche, [follow us on twitter](http://twitter.com/getpoche) or [read the poche blog](http://inthepoche.com/blog).
|
||||
# Want to test the v2 ?
|
||||
|
||||
[](http://flattr.com/thing/1265480/poche-a-read-it-later-open-source-system)
|
||||
Keep in mind it's an **instable** branch, everything can be broken :)
|
||||
|
||||
## Usage
|
||||
You can easily add a "poched" page with the bookmarklet.
|
||||
|
||||
poche save the entire content of a poched links : text and pictures are stored on your server.
|
||||
|
||||
You can :
|
||||
* read a page in a comfortable reading view
|
||||
* archive a link
|
||||
* put a link in favorite
|
||||
* delete a link
|
||||
|
||||
## Requirements & installation
|
||||
You have to install [sqlite for php](http://www.php.net/manual/en/book.sqlite.php) on your server.
|
||||
|
||||
Get the [latest version](https://github.com/nicosomb/poche) of poche on github. Unzip it and upload it on your server. poche must have write access on assets, cache and db directories.
|
||||
|
||||
That's all, **poche works** !
|
||||
|
||||
## Security
|
||||
You **have** to protect your db/poche.sqlite file. Modify the virtual host of your website to add this condition :
|
||||
```apache
|
||||
<Files ~ "\.sqlite$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</Files>
|
||||
```
|
||||
|
||||
Nginx version:
|
||||
```nginx
|
||||
location ~ /(db) {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
git clone https://github.com/wallabag/wallabag.git -b v2
|
||||
cd wallabag
|
||||
composer install
|
||||
php app/console wallabag:install
|
||||
php app/console server:run
|
||||
```
|
||||
|
||||
## Import from Pocket
|
||||
|
||||
If you want to import your Pocket datas, [export them here](https://getpocket.com/export). Put the HTML file in your poche directory, execute import.php file locally by following instructions. Be careful, the script can take a very long time.
|
||||
|
||||
## License
|
||||
Copyright © 2010-2013 Nicolas Lœuillet <nicolas@loeuillet.org>
|
||||
Copyright © 2013-2015 Nicolas Lœuillet <nicolas@loeuillet.org>
|
||||
This work is free. You can redistribute it and/or modify it under the
|
||||
terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
as published by Sam Hocevar. See the COPYING file for more details.
|
||||
terms of the MIT License. See the COPYING file for more details.
|
||||
|
||||
67
TRANSLATION.md
Normal file
@ -0,0 +1,67 @@
|
||||
# How to manage translations for wallabag
|
||||
|
||||
This guide will describe the procedure of translation management of the wallabag web application.
|
||||
|
||||
All translations are made using [gettext](http://en.wikipedia.org/wiki/Gettext) system and tools.
|
||||
|
||||
You will need the [Poedit](http://www.poedit.net/download.php) editor to update, edit and create your translation files easily. However, you can also handle translations also without it: all can be done using gettext tools and your favorite plain text editor only. This guide, however, describes editing with Poedit. If you want to use gettext only, please refer to the xgettext manual page to update po files from sources (see also how it is used by Poedit below) and use msgunfmt tool to compile .mo files manually.
|
||||
|
||||
You need to know, that translation phrases are stored in **".po"** files (for example: `locale/pl_PL.utf8/LC_MESSAGES/pl_PL.utf8.po`), which are then complied in **".mo"** files using **msgfmt** gettext tool or by Poedit, which will run msgfmt for you in background.
|
||||
|
||||
**It's assumed, that you have wallabag installed locally on your computer or on the server you have access to.**
|
||||
|
||||
## To change existing translation you will need to do:
|
||||
|
||||
### 1. Clear cache
|
||||
You can do this using **http://your-wallabag-host.com/?empty-cache** link (replace http://your-wallabag-host.com/ with real url of your wallabag application)
|
||||
|
||||
OR
|
||||
|
||||
from command line:
|
||||
go to root of your installation of wallabag project and run next command:
|
||||
|
||||
`rm -rf ./cache/*`
|
||||
|
||||
(this may require root privileges if you run, for example Apache web server with mod_php)
|
||||
|
||||
### 2. Generate php files from all twig templates
|
||||
Do this using next command:
|
||||
|
||||
`php ./locale/tools/fillCache.php`
|
||||
|
||||
OR
|
||||
|
||||
from your browser: **http://your-wallabag-host.com/locale/tools/fillCache.php** (this may require removal of .htaccess file in locale/ directory).
|
||||
|
||||
### 3. Configure your Poedit
|
||||
Open Poedit editor, open Edit->Preferences. Go to "Parsers" tab, click on PHP and press "Edit" button. Make sure your "Parser command:" looks like
|
||||
|
||||
`xgettext --no-location --force-po -o %o %C %K %F`
|
||||
|
||||
Usually it is required to add "--no-location" to default value.
|
||||
|
||||
### 4. Open .po file you want to edit in Poedit and change its settings
|
||||
Open, for example `locale/pl_PL.utf8/LC_MESSAGES/pl_PL.utf8.po` file in your Poedit.
|
||||
|
||||
Go to "Catalog"->"Settings..." menu. Then go to "Path" tab and add path to wallabag installation in your local file system. This step can't be omitted as you will not be able to update phrases otherwise.
|
||||
|
||||
You can also check "project into" tab to be sure, that "Language" is set correctly (this will allow you to spell check your translation).
|
||||
|
||||
### 5. Update opened .po file from sources
|
||||
Once you have set your path correctly, you are able to update phrases from sources. Press "Update catalog - synchronize it with sources" button or go to "Catalog"->"Update from sources" menu.
|
||||
|
||||
As a result you will see confirmation popup with two tabs: "New strings" and "Obsolete strings". Please review and accept changes (or press "Undo" if you see too many obsolete strings, as Poedit will remove them all - in this case please make sure all previous steps are performed w/o errors).
|
||||
|
||||
### 6. Translate and save your .po file
|
||||
If you have any difficulties on this step, please consult with Poedit manual.
|
||||
Every time you save your .po file, Poedit will also compile appropriate .mo file by default (of course, if not disabled in preferences).
|
||||
|
||||
You are now almost done.
|
||||
|
||||
### 7. Clear cache again
|
||||
This step may be required if your web server runs php scripts in name of, say, www user (i.e. Apache with mod_php, not cgi).
|
||||
|
||||
|
||||
##To create new translation
|
||||
You just have to copy the folder corresponding to the language you want to translate from, change language in the project settings and for the folder and files names. Then start replacing all existing translations with your own.
|
||||
|
||||
71
Vagrantfile
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
$script_sqlite = <<SCRIPT
|
||||
apt-get update
|
||||
apt-get install -y apache2 php5 php5-sqlite php5-xdebug
|
||||
apt-get clean -y
|
||||
echo "ServerName localhost" >> /etc/apache2/apache2.conf
|
||||
service apache2 restart
|
||||
rm -f /var/www/html/index.html
|
||||
date > /etc/vagrant_provisioned_at
|
||||
SCRIPT
|
||||
|
||||
$script_mysql = <<SCRIPT
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y apache2 php5 php5-mysql php5-xdebug mysql-server mysql-client
|
||||
apt-get clean -y
|
||||
echo "ServerName localhost" >> /etc/apache2/apache2.conf
|
||||
service apache2 restart
|
||||
service mysql restart
|
||||
echo "create database wallabag;" | mysql -u root
|
||||
rm -f /var/www/html/index.html
|
||||
date > /etc/vagrant_provisioned_at
|
||||
SCRIPT
|
||||
|
||||
$script_postgres = <<SCRIPT
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y apache2 php5 php5-pgsql php5-xdebug postgresql postgresql-contrib
|
||||
apt-get clean -y
|
||||
echo "ServerName localhost" >> /etc/apache2/apache2.conf
|
||||
service apache2 restart
|
||||
service postgresql restart
|
||||
rm -f /var/www/html/index.html
|
||||
date > /etc/vagrant_provisioned_at
|
||||
SCRIPT
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
|
||||
config.vm.define "sqlite" do |m|
|
||||
m.vm.box = "ubuntu/trusty64"
|
||||
m.vm.provision "shell", inline: $script_sqlite
|
||||
m.vm.synced_folder ".", "/var/www/html", owner: "www-data", group: "www-data"
|
||||
end
|
||||
|
||||
config.vm.define "mysql" do |m|
|
||||
m.vm.box = "ubuntu/trusty64"
|
||||
m.vm.provision "shell", inline: $script_mysql
|
||||
m.vm.synced_folder ".", "/var/www/html", owner: "www-data", group: "www-data"
|
||||
end
|
||||
|
||||
config.vm.define "postgres" do |m|
|
||||
m.vm.box = "ubuntu/trusty64"
|
||||
m.vm.provision "shell", inline: $script_postgres
|
||||
m.vm.synced_folder ".", "/var/www/html", owner: "www-data", group: "www-data"
|
||||
end
|
||||
|
||||
config.vm.define "debian7" do |m|
|
||||
m.vm.box = "chef/debian-7.6"
|
||||
m.vm.provision "shell", inline: $script_sqlite
|
||||
m.vm.synced_folder ".", "/var/www", owner: "www-data", group: "www-data"
|
||||
end
|
||||
|
||||
config.vm.define "debian6" do |m|
|
||||
m.vm.box = "chef/debian-6.0.10"
|
||||
m.vm.provision "shell", inline: $script_sqlite
|
||||
m.vm.synced_folder ".", "/var/www", owner: "www-data", group: "www-data"
|
||||
end
|
||||
|
||||
config.vm.network :forwarded_port, guest: 80, host: 8003
|
||||
#config.vm.network "public_network", :bridge => "en0: Wi-Fi (AirPort)"
|
||||
end
|
||||
7
app/.htaccess
Normal file
@ -0,0 +1,7 @@
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</IfModule>
|
||||
9
app/AppCache.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/AppKernel.php';
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
|
||||
|
||||
class AppCache extends HttpCache
|
||||
{
|
||||
}
|
||||
46
app/AppKernel.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\HttpKernel\Kernel;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
|
||||
class AppKernel extends Kernel
|
||||
{
|
||||
public function registerBundles()
|
||||
{
|
||||
$bundles = array(
|
||||
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
|
||||
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
|
||||
new Symfony\Bundle\TwigBundle\TwigBundle(),
|
||||
new Symfony\Bundle\MonologBundle\MonologBundle(),
|
||||
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
|
||||
new Symfony\Bundle\AsseticBundle\AsseticBundle(),
|
||||
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
|
||||
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
|
||||
new FOS\RestBundle\FOSRestBundle(),
|
||||
new FOS\UserBundle\FOSUserBundle(),
|
||||
new JMS\SerializerBundle\JMSSerializerBundle(),
|
||||
new Nelmio\ApiDocBundle\NelmioApiDocBundle(),
|
||||
new Nelmio\CorsBundle\NelmioCorsBundle(),
|
||||
new Liip\ThemeBundle\LiipThemeBundle(),
|
||||
new Wallabag\CoreBundle\WallabagCoreBundle(),
|
||||
new Wallabag\ApiBundle\WallabagApiBundle(),
|
||||
new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(),
|
||||
new Lexik\Bundle\FormFilterBundle\LexikFormFilterBundle(),
|
||||
);
|
||||
|
||||
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
|
||||
$bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
|
||||
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
|
||||
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
|
||||
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
|
||||
$bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
|
||||
}
|
||||
|
||||
return $bundles;
|
||||
}
|
||||
|
||||
public function registerContainerConfiguration(LoaderInterface $loader)
|
||||
{
|
||||
$loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
|
||||
}
|
||||
}
|
||||
0
app/Resources/views/.gitkeep
Normal file
758
app/SymfonyRequirements.php
Normal file
@ -0,0 +1,758 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Users of PHP 5.2 should be able to run the requirements checks.
|
||||
* This is why the file and all classes must be compatible with PHP 5.2+
|
||||
* (e.g. not using namespaces and closures).
|
||||
*
|
||||
* ************** CAUTION **************
|
||||
*
|
||||
* DO NOT EDIT THIS FILE as it will be overridden by Composer as part of
|
||||
* the installation/update process. The original file resides in the
|
||||
* SensioDistributionBundle.
|
||||
*
|
||||
* ************** CAUTION **************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a single PHP requirement, e.g. an installed extension.
|
||||
* It can be a mandatory requirement or an optional recommendation.
|
||||
* There is a special subclass, named PhpIniRequirement, to check a php.ini configuration.
|
||||
*
|
||||
* @author Tobias Schultze <http://tobion.de>
|
||||
*/
|
||||
class Requirement
|
||||
{
|
||||
private $fulfilled;
|
||||
private $testMessage;
|
||||
private $helpText;
|
||||
private $helpHtml;
|
||||
private $optional;
|
||||
|
||||
/**
|
||||
* Constructor that initializes the requirement.
|
||||
*
|
||||
* @param bool $fulfilled Whether the requirement is fulfilled
|
||||
* @param string $testMessage The message for testing the requirement
|
||||
* @param string $helpHtml The help text formatted in HTML for resolving the problem
|
||||
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
|
||||
* @param bool $optional Whether this is only an optional recommendation not a mandatory requirement
|
||||
*/
|
||||
public function __construct($fulfilled, $testMessage, $helpHtml, $helpText = null, $optional = false)
|
||||
{
|
||||
$this->fulfilled = (bool) $fulfilled;
|
||||
$this->testMessage = (string) $testMessage;
|
||||
$this->helpHtml = (string) $helpHtml;
|
||||
$this->helpText = null === $helpText ? strip_tags($this->helpHtml) : (string) $helpText;
|
||||
$this->optional = (bool) $optional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the requirement is fulfilled.
|
||||
*
|
||||
* @return bool true if fulfilled, otherwise false
|
||||
*/
|
||||
public function isFulfilled()
|
||||
{
|
||||
return $this->fulfilled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message for testing the requirement.
|
||||
*
|
||||
* @return string The test message
|
||||
*/
|
||||
public function getTestMessage()
|
||||
{
|
||||
return $this->testMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the help text for resolving the problem.
|
||||
*
|
||||
* @return string The help text
|
||||
*/
|
||||
public function getHelpText()
|
||||
{
|
||||
return $this->helpText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the help text formatted in HTML.
|
||||
*
|
||||
* @return string The HTML help
|
||||
*/
|
||||
public function getHelpHtml()
|
||||
{
|
||||
return $this->helpHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this is only an optional recommendation and not a mandatory requirement.
|
||||
*
|
||||
* @return bool true if optional, false if mandatory
|
||||
*/
|
||||
public function isOptional()
|
||||
{
|
||||
return $this->optional;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a PHP requirement in form of a php.ini configuration.
|
||||
*
|
||||
* @author Tobias Schultze <http://tobion.de>
|
||||
*/
|
||||
class PhpIniRequirement extends Requirement
|
||||
{
|
||||
/**
|
||||
* Constructor that initializes the requirement.
|
||||
*
|
||||
* @param string $cfgName The configuration name used for ini_get()
|
||||
* @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false,
|
||||
* or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
|
||||
* @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
|
||||
* This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
|
||||
* Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
|
||||
* @param string|null $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
|
||||
* @param string|null $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived)
|
||||
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
|
||||
* @param bool $optional Whether this is only an optional recommendation not a mandatory requirement
|
||||
*/
|
||||
public function __construct($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null, $optional = false)
|
||||
{
|
||||
$cfgValue = ini_get($cfgName);
|
||||
|
||||
if (is_callable($evaluation)) {
|
||||
if (null === $testMessage || null === $helpHtml) {
|
||||
throw new InvalidArgumentException('You must provide the parameters testMessage and helpHtml for a callback evaluation.');
|
||||
}
|
||||
|
||||
$fulfilled = call_user_func($evaluation, $cfgValue);
|
||||
} else {
|
||||
if (null === $testMessage) {
|
||||
$testMessage = sprintf('%s %s be %s in php.ini',
|
||||
$cfgName,
|
||||
$optional ? 'should' : 'must',
|
||||
$evaluation ? 'enabled' : 'disabled'
|
||||
);
|
||||
}
|
||||
|
||||
if (null === $helpHtml) {
|
||||
$helpHtml = sprintf('Set <strong>%s</strong> to <strong>%s</strong> in php.ini<a href="#phpini">*</a>.',
|
||||
$cfgName,
|
||||
$evaluation ? 'on' : 'off'
|
||||
);
|
||||
}
|
||||
|
||||
$fulfilled = $evaluation == $cfgValue;
|
||||
}
|
||||
|
||||
parent::__construct($fulfilled || ($approveCfgAbsence && false === $cfgValue), $testMessage, $helpHtml, $helpText, $optional);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A RequirementCollection represents a set of Requirement instances.
|
||||
*
|
||||
* @author Tobias Schultze <http://tobion.de>
|
||||
*/
|
||||
class RequirementCollection implements IteratorAggregate
|
||||
{
|
||||
private $requirements = array();
|
||||
|
||||
/**
|
||||
* Gets the current RequirementCollection as an Iterator.
|
||||
*
|
||||
* @return Traversable A Traversable interface
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->requirements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Requirement.
|
||||
*
|
||||
* @param Requirement $requirement A Requirement instance
|
||||
*/
|
||||
public function add(Requirement $requirement)
|
||||
{
|
||||
$this->requirements[] = $requirement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mandatory requirement.
|
||||
*
|
||||
* @param bool $fulfilled Whether the requirement is fulfilled
|
||||
* @param string $testMessage The message for testing the requirement
|
||||
* @param string $helpHtml The help text formatted in HTML for resolving the problem
|
||||
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
|
||||
*/
|
||||
public function addRequirement($fulfilled, $testMessage, $helpHtml, $helpText = null)
|
||||
{
|
||||
$this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an optional recommendation.
|
||||
*
|
||||
* @param bool $fulfilled Whether the recommendation is fulfilled
|
||||
* @param string $testMessage The message for testing the recommendation
|
||||
* @param string $helpHtml The help text formatted in HTML for resolving the problem
|
||||
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
|
||||
*/
|
||||
public function addRecommendation($fulfilled, $testMessage, $helpHtml, $helpText = null)
|
||||
{
|
||||
$this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mandatory requirement in form of a php.ini configuration.
|
||||
*
|
||||
* @param string $cfgName The configuration name used for ini_get()
|
||||
* @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false,
|
||||
* or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
|
||||
* @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
|
||||
* This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
|
||||
* Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
|
||||
* @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
|
||||
* @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived)
|
||||
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
|
||||
*/
|
||||
public function addPhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null)
|
||||
{
|
||||
$this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an optional recommendation in form of a php.ini configuration.
|
||||
*
|
||||
* @param string $cfgName The configuration name used for ini_get()
|
||||
* @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false,
|
||||
* or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
|
||||
* @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
|
||||
* This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
|
||||
* Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
|
||||
* @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
|
||||
* @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived)
|
||||
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
|
||||
*/
|
||||
public function addPhpIniRecommendation($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null)
|
||||
{
|
||||
$this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a requirement collection to the current set of requirements.
|
||||
*
|
||||
* @param RequirementCollection $collection A RequirementCollection instance
|
||||
*/
|
||||
public function addCollection(RequirementCollection $collection)
|
||||
{
|
||||
$this->requirements = array_merge($this->requirements, $collection->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns both requirements and recommendations.
|
||||
*
|
||||
* @return array Array of Requirement instances
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all mandatory requirements.
|
||||
*
|
||||
* @return array Array of Requirement instances
|
||||
*/
|
||||
public function getRequirements()
|
||||
{
|
||||
$array = array();
|
||||
foreach ($this->requirements as $req) {
|
||||
if (!$req->isOptional()) {
|
||||
$array[] = $req;
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mandatory requirements that were not met.
|
||||
*
|
||||
* @return array Array of Requirement instances
|
||||
*/
|
||||
public function getFailedRequirements()
|
||||
{
|
||||
$array = array();
|
||||
foreach ($this->requirements as $req) {
|
||||
if (!$req->isFulfilled() && !$req->isOptional()) {
|
||||
$array[] = $req;
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all optional recommendations.
|
||||
*
|
||||
* @return array Array of Requirement instances
|
||||
*/
|
||||
public function getRecommendations()
|
||||
{
|
||||
$array = array();
|
||||
foreach ($this->requirements as $req) {
|
||||
if ($req->isOptional()) {
|
||||
$array[] = $req;
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recommendations that were not met.
|
||||
*
|
||||
* @return array Array of Requirement instances
|
||||
*/
|
||||
public function getFailedRecommendations()
|
||||
{
|
||||
$array = array();
|
||||
foreach ($this->requirements as $req) {
|
||||
if (!$req->isFulfilled() && $req->isOptional()) {
|
||||
$array[] = $req;
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a php.ini configuration is not correct.
|
||||
*
|
||||
* @return bool php.ini configuration problem?
|
||||
*/
|
||||
public function hasPhpIniConfigIssue()
|
||||
{
|
||||
foreach ($this->requirements as $req) {
|
||||
if (!$req->isFulfilled() && $req instanceof PhpIniRequirement) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PHP configuration file (php.ini) path.
|
||||
*
|
||||
* @return string|false php.ini file path
|
||||
*/
|
||||
public function getPhpIniConfigPath()
|
||||
{
|
||||
return get_cfg_var('cfg_file_path');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class specifies all requirements and optional recommendations that
|
||||
* are necessary to run the Symfony Standard Edition.
|
||||
*
|
||||
* @author Tobias Schultze <http://tobion.de>
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class SymfonyRequirements extends RequirementCollection
|
||||
{
|
||||
const REQUIRED_PHP_VERSION = '5.3.3';
|
||||
|
||||
/**
|
||||
* Constructor that initializes the requirements.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
/* mandatory requirements follow */
|
||||
|
||||
$installedPhpVersion = phpversion();
|
||||
|
||||
$this->addRequirement(
|
||||
version_compare($installedPhpVersion, self::REQUIRED_PHP_VERSION, '>='),
|
||||
sprintf('PHP version must be at least %s (%s installed)', self::REQUIRED_PHP_VERSION, $installedPhpVersion),
|
||||
sprintf('You are running PHP version "<strong>%s</strong>", but Symfony needs at least PHP "<strong>%s</strong>" to run.
|
||||
Before using Symfony, upgrade your PHP installation, preferably to the latest version.',
|
||||
$installedPhpVersion, self::REQUIRED_PHP_VERSION),
|
||||
sprintf('Install PHP %s or newer (installed version is %s)', self::REQUIRED_PHP_VERSION, $installedPhpVersion)
|
||||
);
|
||||
|
||||
$this->addRequirement(
|
||||
version_compare($installedPhpVersion, '5.3.16', '!='),
|
||||
'PHP version must not be 5.3.16 as Symfony won\'t work properly with it',
|
||||
'Install PHP 5.3.17 or newer (or downgrade to an earlier PHP version)'
|
||||
);
|
||||
|
||||
$this->addRequirement(
|
||||
is_dir(__DIR__.'/../vendor/composer'),
|
||||
'Vendor libraries must be installed',
|
||||
'Vendor libraries are missing. Install composer following instructions from <a href="http://getcomposer.org/">http://getcomposer.org/</a>. '.
|
||||
'Then run "<strong>php composer.phar install</strong>" to install them.'
|
||||
);
|
||||
|
||||
$cacheDir = is_dir(__DIR__.'/../var/cache') ? __DIR__.'/../var/cache' : __DIR__.'/cache';
|
||||
|
||||
$this->addRequirement(
|
||||
is_writable($cacheDir),
|
||||
'app/cache/ or var/cache/ directory must be writable',
|
||||
'Change the permissions of either "<strong>app/cache/</strong>" or "<strong>var/cache/</strong>" directory so that the web server can write into it.'
|
||||
);
|
||||
|
||||
$logsDir = is_dir(__DIR__.'/../var/logs') ? __DIR__.'/../var/logs' : __DIR__.'/logs';
|
||||
|
||||
$this->addRequirement(
|
||||
is_writable($logsDir),
|
||||
'app/logs/ or var/logs/ directory must be writable',
|
||||
'Change the permissions of either "<strong>app/logs/</strong>" or "<strong>var/logs/</strong>" directory so that the web server can write into it.'
|
||||
);
|
||||
|
||||
$this->addPhpIniRequirement(
|
||||
'date.timezone', true, false,
|
||||
'date.timezone setting must be set',
|
||||
'Set the "<strong>date.timezone</strong>" setting in php.ini<a href="#phpini">*</a> (like Europe/Paris).'
|
||||
);
|
||||
|
||||
if (version_compare($installedPhpVersion, self::REQUIRED_PHP_VERSION, '>=')) {
|
||||
$timezones = array();
|
||||
foreach (DateTimeZone::listAbbreviations() as $abbreviations) {
|
||||
foreach ($abbreviations as $abbreviation) {
|
||||
$timezones[$abbreviation['timezone_id']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->addRequirement(
|
||||
isset($timezones[@date_default_timezone_get()]),
|
||||
sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()),
|
||||
'Your default timezone is not supported by PHP. Check for typos in your <strong>php.ini</strong> file and have a look at the list of deprecated timezones at <a href="http://php.net/manual/en/timezones.others.php">http://php.net/manual/en/timezones.others.php</a>.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->addRequirement(
|
||||
function_exists('json_encode'),
|
||||
'json_encode() must be available',
|
||||
'Install and enable the <strong>JSON</strong> extension.'
|
||||
);
|
||||
|
||||
$this->addRequirement(
|
||||
function_exists('session_start'),
|
||||
'session_start() must be available',
|
||||
'Install and enable the <strong>session</strong> extension.'
|
||||
);
|
||||
|
||||
$this->addRequirement(
|
||||
function_exists('ctype_alpha'),
|
||||
'ctype_alpha() must be available',
|
||||
'Install and enable the <strong>ctype</strong> extension.'
|
||||
);
|
||||
|
||||
$this->addRequirement(
|
||||
function_exists('token_get_all'),
|
||||
'token_get_all() must be available',
|
||||
'Install and enable the <strong>Tokenizer</strong> extension.'
|
||||
);
|
||||
|
||||
$this->addRequirement(
|
||||
function_exists('simplexml_import_dom'),
|
||||
'simplexml_import_dom() must be available',
|
||||
'Install and enable the <strong>SimpleXML</strong> extension.'
|
||||
);
|
||||
|
||||
if (function_exists('apc_store') && ini_get('apc.enabled')) {
|
||||
if (version_compare($installedPhpVersion, '5.4.0', '>=')) {
|
||||
$this->addRequirement(
|
||||
version_compare(phpversion('apc'), '3.1.13', '>='),
|
||||
'APC version must be at least 3.1.13 when using PHP 5.4',
|
||||
'Upgrade your <strong>APC</strong> extension (3.1.13+).'
|
||||
);
|
||||
} else {
|
||||
$this->addRequirement(
|
||||
version_compare(phpversion('apc'), '3.0.17', '>='),
|
||||
'APC version must be at least 3.0.17',
|
||||
'Upgrade your <strong>APC</strong> extension (3.0.17+).'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->addPhpIniRequirement('detect_unicode', false);
|
||||
|
||||
if (extension_loaded('suhosin')) {
|
||||
$this->addPhpIniRequirement(
|
||||
'suhosin.executor.include.whitelist',
|
||||
create_function('$cfgValue', 'return false !== stripos($cfgValue, "phar");'),
|
||||
false,
|
||||
'suhosin.executor.include.whitelist must be configured correctly in php.ini',
|
||||
'Add "<strong>phar</strong>" to <strong>suhosin.executor.include.whitelist</strong> in php.ini<a href="#phpini">*</a>.'
|
||||
);
|
||||
}
|
||||
|
||||
if (extension_loaded('xdebug')) {
|
||||
$this->addPhpIniRequirement(
|
||||
'xdebug.show_exception_trace', false, true
|
||||
);
|
||||
|
||||
$this->addPhpIniRequirement(
|
||||
'xdebug.scream', false, true
|
||||
);
|
||||
|
||||
$this->addPhpIniRecommendation(
|
||||
'xdebug.max_nesting_level',
|
||||
create_function('$cfgValue', 'return $cfgValue > 100;'),
|
||||
true,
|
||||
'xdebug.max_nesting_level should be above 100 in php.ini',
|
||||
'Set "<strong>xdebug.max_nesting_level</strong>" to e.g. "<strong>250</strong>" in php.ini<a href="#phpini">*</a> to stop Xdebug\'s infinite recursion protection erroneously throwing a fatal error in your project.'
|
||||
);
|
||||
}
|
||||
|
||||
$pcreVersion = defined('PCRE_VERSION') ? (float) PCRE_VERSION : null;
|
||||
|
||||
$this->addRequirement(
|
||||
null !== $pcreVersion,
|
||||
'PCRE extension must be available',
|
||||
'Install the <strong>PCRE</strong> extension (version 8.0+).'
|
||||
);
|
||||
|
||||
if (extension_loaded('mbstring')) {
|
||||
$this->addPhpIniRequirement(
|
||||
'mbstring.func_overload',
|
||||
create_function('$cfgValue', 'return (int) $cfgValue === 0;'),
|
||||
true,
|
||||
'string functions should not be overloaded',
|
||||
'Set "<strong>mbstring.func_overload</strong>" to <strong>0</strong> in php.ini<a href="#phpini">*</a> to disable function overloading by the mbstring extension.'
|
||||
);
|
||||
}
|
||||
|
||||
/* optional recommendations follow */
|
||||
|
||||
if (file_exists(__DIR__.'/../vendor/composer')) {
|
||||
require_once __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
try {
|
||||
$r = new \ReflectionClass('Sensio\Bundle\DistributionBundle\SensioDistributionBundle');
|
||||
|
||||
$contents = file_get_contents(dirname($r->getFileName()).'/Resources/skeleton/app/SymfonyRequirements.php');
|
||||
} catch (\ReflectionException $e) {
|
||||
$contents = '';
|
||||
}
|
||||
$this->addRecommendation(
|
||||
file_get_contents(__FILE__) === $contents,
|
||||
'Requirements file should be up-to-date',
|
||||
'Your requirements file is outdated. Run composer install and re-check your configuration.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->addRecommendation(
|
||||
version_compare($installedPhpVersion, '5.3.4', '>='),
|
||||
'You should use at least PHP 5.3.4 due to PHP bug #52083 in earlier versions',
|
||||
'Your project might malfunction randomly due to PHP bug #52083 ("Notice: Trying to get property of non-object"). Install PHP 5.3.4 or newer.'
|
||||
);
|
||||
|
||||
$this->addRecommendation(
|
||||
version_compare($installedPhpVersion, '5.3.8', '>='),
|
||||
'When using annotations you should have at least PHP 5.3.8 due to PHP bug #55156',
|
||||
'Install PHP 5.3.8 or newer if your project uses annotations.'
|
||||
);
|
||||
|
||||
$this->addRecommendation(
|
||||
version_compare($installedPhpVersion, '5.4.0', '!='),
|
||||
'You should not use PHP 5.4.0 due to the PHP bug #61453',
|
||||
'Your project might not work properly due to the PHP bug #61453 ("Cannot dump definitions which have method calls"). Install PHP 5.4.1 or newer.'
|
||||
);
|
||||
|
||||
$this->addRecommendation(
|
||||
version_compare($installedPhpVersion, '5.4.11', '>='),
|
||||
'When using the logout handler from the Symfony Security Component, you should have at least PHP 5.4.11 due to PHP bug #63379 (as a workaround, you can also set invalidate_session to false in the security logout handler configuration)',
|
||||
'Install PHP 5.4.11 or newer if your project uses the logout handler from the Symfony Security Component.'
|
||||
);
|
||||
|
||||
$this->addRecommendation(
|
||||
(version_compare($installedPhpVersion, '5.3.18', '>=') && version_compare($installedPhpVersion, '5.4.0', '<'))
|
||||
||
|
||||
version_compare($installedPhpVersion, '5.4.8', '>='),
|
||||
'You should use PHP 5.3.18+ or PHP 5.4.8+ to always get nice error messages for fatal errors in the development environment due to PHP bug #61767/#60909',
|
||||
'Install PHP 5.3.18+ or PHP 5.4.8+ if you want nice error messages for all fatal errors in the development environment.'
|
||||
);
|
||||
|
||||
if (null !== $pcreVersion) {
|
||||
$this->addRecommendation(
|
||||
$pcreVersion >= 8.0,
|
||||
sprintf('PCRE extension should be at least version 8.0 (%s installed)', $pcreVersion),
|
||||
'<strong>PCRE 8.0+</strong> is preconfigured in PHP since 5.3.2 but you are using an outdated version of it. Symfony probably works anyway but it is recommended to upgrade your PCRE extension.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->addRecommendation(
|
||||
class_exists('DomDocument'),
|
||||
'PHP-DOM and PHP-XML modules should be installed',
|
||||
'Install and enable the <strong>PHP-DOM</strong> and the <strong>PHP-XML</strong> modules.'
|
||||
);
|
||||
|
||||
$this->addRecommendation(
|
||||
function_exists('mb_strlen'),
|
||||
'mb_strlen() should be available',
|
||||
'Install and enable the <strong>mbstring</strong> extension.'
|
||||
);
|
||||
|
||||
$this->addRecommendation(
|
||||
function_exists('iconv'),
|
||||
'iconv() should be available',
|
||||
'Install and enable the <strong>iconv</strong> extension.'
|
||||
);
|
||||
|
||||
$this->addRecommendation(
|
||||
function_exists('utf8_decode'),
|
||||
'utf8_decode() should be available',
|
||||
'Install and enable the <strong>XML</strong> extension.'
|
||||
);
|
||||
|
||||
$this->addRecommendation(
|
||||
function_exists('filter_var'),
|
||||
'filter_var() should be available',
|
||||
'Install and enable the <strong>filter</strong> extension.'
|
||||
);
|
||||
|
||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||
$this->addRecommendation(
|
||||
function_exists('posix_isatty'),
|
||||
'posix_isatty() should be available',
|
||||
'Install and enable the <strong>php_posix</strong> extension (used to colorize the CLI output).'
|
||||
);
|
||||
}
|
||||
|
||||
$this->addRecommendation(
|
||||
extension_loaded('intl'),
|
||||
'intl extension should be available',
|
||||
'Install and enable the <strong>intl</strong> extension (used for validators).'
|
||||
);
|
||||
|
||||
if (extension_loaded('intl')) {
|
||||
// in some WAMP server installations, new Collator() returns null
|
||||
$this->addRecommendation(
|
||||
null !== new Collator('fr_FR'),
|
||||
'intl extension should be correctly configured',
|
||||
'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.'
|
||||
);
|
||||
|
||||
// check for compatible ICU versions (only done when you have the intl extension)
|
||||
if (defined('INTL_ICU_VERSION')) {
|
||||
$version = INTL_ICU_VERSION;
|
||||
} else {
|
||||
$reflector = new ReflectionExtension('intl');
|
||||
|
||||
ob_start();
|
||||
$reflector->info();
|
||||
$output = strip_tags(ob_get_clean());
|
||||
|
||||
preg_match('/^ICU version +(?:=> )?(.*)$/m', $output, $matches);
|
||||
$version = $matches[1];
|
||||
}
|
||||
|
||||
$this->addRecommendation(
|
||||
version_compare($version, '4.0', '>='),
|
||||
'intl ICU version should be at least 4+',
|
||||
'Upgrade your <strong>intl</strong> extension with a newer ICU version (4+).'
|
||||
);
|
||||
|
||||
$this->addPhpIniRecommendation(
|
||||
'intl.error_level',
|
||||
create_function('$cfgValue', 'return (int) $cfgValue === 0;'),
|
||||
true,
|
||||
'intl.error_level should be 0 in php.ini',
|
||||
'Set "<strong>intl.error_level</strong>" to "<strong>0</strong>" in php.ini<a href="#phpini">*</a> to inhibit the messages when an error occurs in ICU functions.'
|
||||
);
|
||||
}
|
||||
|
||||
$accelerator =
|
||||
(extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'))
|
||||
||
|
||||
(extension_loaded('apc') && ini_get('apc.enabled'))
|
||||
||
|
||||
(extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable'))
|
||||
||
|
||||
(extension_loaded('Zend OPcache') && ini_get('opcache.enable'))
|
||||
||
|
||||
(extension_loaded('xcache') && ini_get('xcache.cacher'))
|
||||
||
|
||||
(extension_loaded('wincache') && ini_get('wincache.ocenabled'))
|
||||
;
|
||||
|
||||
$this->addRecommendation(
|
||||
$accelerator,
|
||||
'a PHP accelerator should be installed',
|
||||
'Install and/or enable a <strong>PHP accelerator</strong> (highly recommended).'
|
||||
);
|
||||
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
||||
$this->addRecommendation(
|
||||
$this->getRealpathCacheSize() > 1000,
|
||||
'realpath_cache_size should be above 1024 in php.ini',
|
||||
'Set "<strong>realpath_cache_size</strong>" to e.g. "<strong>1024</strong>" in php.ini<a href="#phpini">*</a> to improve performance on windows.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->addPhpIniRecommendation('short_open_tag', false);
|
||||
|
||||
$this->addPhpIniRecommendation('magic_quotes_gpc', false, true);
|
||||
|
||||
$this->addPhpIniRecommendation('register_globals', false, true);
|
||||
|
||||
$this->addPhpIniRecommendation('session.auto_start', false);
|
||||
|
||||
$this->addRecommendation(
|
||||
class_exists('PDO'),
|
||||
'PDO should be installed',
|
||||
'Install <strong>PDO</strong> (mandatory for Doctrine).'
|
||||
);
|
||||
|
||||
if (class_exists('PDO')) {
|
||||
$drivers = PDO::getAvailableDrivers();
|
||||
$this->addRecommendation(
|
||||
count($drivers) > 0,
|
||||
sprintf('PDO should have some drivers installed (currently available: %s)', count($drivers) ? implode(', ', $drivers) : 'none'),
|
||||
'Install <strong>PDO drivers</strong> (mandatory for Doctrine).'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads realpath_cache_size from php.ini and converts it to int.
|
||||
*
|
||||
* (e.g. 16k is converted to 16384 int)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getRealpathCacheSize()
|
||||
{
|
||||
$size = ini_get('realpath_cache_size');
|
||||
$size = trim($size);
|
||||
$unit = strtolower(substr($size, -1, 1));
|
||||
switch ($unit) {
|
||||
case 'g':
|
||||
return $size * 1024 * 1024 * 1024;
|
||||
case 'm':
|
||||
return $size * 1024 * 1024;
|
||||
case 'k':
|
||||
return $size * 1024;
|
||||
default:
|
||||
return (int) $size;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
app/autoload.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Composer\Autoload\ClassLoader;
|
||||
|
||||
/**
|
||||
* @var ClassLoader $loader
|
||||
*/
|
||||
$loader = require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
|
||||
|
||||
return $loader;
|
||||
142
app/check.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
require_once dirname(__FILE__).'/SymfonyRequirements.php';
|
||||
|
||||
$lineSize = 70;
|
||||
$symfonyRequirements = new SymfonyRequirements();
|
||||
$iniPath = $symfonyRequirements->getPhpIniConfigPath();
|
||||
|
||||
echo_title('Symfony2 Requirements Checker');
|
||||
|
||||
echo '> PHP is using the following php.ini file:'.PHP_EOL;
|
||||
if ($iniPath) {
|
||||
echo_style('green', ' '.$iniPath);
|
||||
} else {
|
||||
echo_style('warning', ' WARNING: No configuration file (php.ini) used by PHP!');
|
||||
}
|
||||
|
||||
echo PHP_EOL.PHP_EOL;
|
||||
|
||||
echo '> Checking Symfony requirements:'.PHP_EOL.' ';
|
||||
|
||||
$messages = array();
|
||||
foreach ($symfonyRequirements->getRequirements() as $req) {
|
||||
/** @var $req Requirement */
|
||||
if ($helpText = get_error_message($req, $lineSize)) {
|
||||
echo_style('red', 'E');
|
||||
$messages['error'][] = $helpText;
|
||||
} else {
|
||||
echo_style('green', '.');
|
||||
}
|
||||
}
|
||||
|
||||
$checkPassed = empty($messages['error']);
|
||||
|
||||
foreach ($symfonyRequirements->getRecommendations() as $req) {
|
||||
if ($helpText = get_error_message($req, $lineSize)) {
|
||||
echo_style('yellow', 'W');
|
||||
$messages['warning'][] = $helpText;
|
||||
} else {
|
||||
echo_style('green', '.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($checkPassed) {
|
||||
echo_block('success', 'OK', 'Your system is ready to run Symfony2 projects');
|
||||
} else {
|
||||
echo_block('error', 'ERROR', 'Your system is not ready to run Symfony2 projects');
|
||||
|
||||
echo_title('Fix the following mandatory requirements', 'red');
|
||||
|
||||
foreach ($messages['error'] as $helpText) {
|
||||
echo ' * '.$helpText.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($messages['warning'])) {
|
||||
echo_title('Optional recommendations to improve your setup', 'yellow');
|
||||
|
||||
foreach ($messages['warning'] as $helpText) {
|
||||
echo ' * '.$helpText.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
echo_style('title', 'Note');
|
||||
echo ' The command console could use a different php.ini file'.PHP_EOL;
|
||||
echo_style('title', '~~~~');
|
||||
echo ' than the one used with your web server. To be on the'.PHP_EOL;
|
||||
echo ' safe side, please check the requirements from your web'.PHP_EOL;
|
||||
echo ' server using the ';
|
||||
echo_style('yellow', 'web/config.php');
|
||||
echo ' script.'.PHP_EOL;
|
||||
echo PHP_EOL;
|
||||
|
||||
exit($checkPassed ? 0 : 1);
|
||||
|
||||
function get_error_message(Requirement $requirement, $lineSize)
|
||||
{
|
||||
if ($requirement->isFulfilled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$errorMessage = wordwrap($requirement->getTestMessage(), $lineSize - 3, PHP_EOL.' ').PHP_EOL;
|
||||
$errorMessage .= ' > '.wordwrap($requirement->getHelpText(), $lineSize - 5, PHP_EOL.' > ').PHP_EOL;
|
||||
|
||||
return $errorMessage;
|
||||
}
|
||||
|
||||
function echo_title($title, $style = null)
|
||||
{
|
||||
$style = $style ?: 'title';
|
||||
|
||||
echo PHP_EOL;
|
||||
echo_style($style, $title.PHP_EOL);
|
||||
echo_style($style, str_repeat('~', strlen($title)).PHP_EOL);
|
||||
echo PHP_EOL;
|
||||
}
|
||||
|
||||
function echo_style($style, $message)
|
||||
{
|
||||
// ANSI color codes
|
||||
$styles = array(
|
||||
'reset' => "\033[0m",
|
||||
'red' => "\033[31m",
|
||||
'green' => "\033[32m",
|
||||
'yellow' => "\033[33m",
|
||||
'error' => "\033[37;41m",
|
||||
'success' => "\033[37;42m",
|
||||
'title' => "\033[34m",
|
||||
);
|
||||
$supports = has_color_support();
|
||||
|
||||
echo($supports ? $styles[$style] : '').$message.($supports ? $styles['reset'] : '');
|
||||
}
|
||||
|
||||
function echo_block($style, $title, $message)
|
||||
{
|
||||
$message = ' '.trim($message).' ';
|
||||
$width = strlen($message);
|
||||
|
||||
echo PHP_EOL.PHP_EOL;
|
||||
|
||||
echo_style($style, str_repeat(' ', $width).PHP_EOL);
|
||||
echo_style($style, str_pad(' ['.$title.']', $width, ' ', STR_PAD_RIGHT).PHP_EOL);
|
||||
echo_style($style, str_pad($message, $width, ' ', STR_PAD_RIGHT).PHP_EOL);
|
||||
echo_style($style, str_repeat(' ', $width).PHP_EOL);
|
||||
}
|
||||
|
||||
function has_color_support()
|
||||
{
|
||||
static $support;
|
||||
|
||||
if (null === $support) {
|
||||
if (DIRECTORY_SEPARATOR == '\\') {
|
||||
$support = false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI');
|
||||
} else {
|
||||
$support = function_exists('posix_isatty') && @posix_isatty(STDOUT);
|
||||
}
|
||||
}
|
||||
|
||||
return $support;
|
||||
}
|
||||
159
app/config/config.yml
Normal file
@ -0,0 +1,159 @@
|
||||
imports:
|
||||
- { resource: parameters.yml }
|
||||
- { resource: security.yml }
|
||||
- { resource: services.yml }
|
||||
|
||||
framework:
|
||||
#esi: ~
|
||||
translator: { fallback: "%locale%" }
|
||||
secret: "%secret%"
|
||||
router:
|
||||
resource: "%kernel.root_dir%/config/routing.yml"
|
||||
strict_requirements: ~
|
||||
form: ~
|
||||
csrf_protection: ~
|
||||
validation: { enable_annotations: true }
|
||||
templating:
|
||||
engines: ['twig']
|
||||
#assets_version: SomeVersionScheme
|
||||
default_locale: "%locale%"
|
||||
trusted_hosts: ~
|
||||
trusted_proxies: ~
|
||||
session:
|
||||
# handler_id set to null will use default session handler from php.ini
|
||||
handler_id: ~
|
||||
fragments: ~
|
||||
http_method_override: true
|
||||
|
||||
# Twig Configuration
|
||||
twig:
|
||||
debug: "%kernel.debug%"
|
||||
strict_variables: "%kernel.debug%"
|
||||
globals:
|
||||
share_twitter: %share_twitter%
|
||||
share_mail: %share_mail%
|
||||
share_shaarli: %share_shaarli%
|
||||
shaarli_url: %shaarli_url%
|
||||
share_diaspora: %share_diaspora%
|
||||
diaspora_url: %diaspora_url%
|
||||
flattr: %flattr%
|
||||
flattrable: 1
|
||||
flattred: 2
|
||||
carrot: %carrot%
|
||||
show_printlink: %show_printlink%
|
||||
export_epub: %export_epub%
|
||||
export_mobi: %export_mobi%
|
||||
export_pdf: %export_pdf%
|
||||
version: %app.version%
|
||||
warning_message: %warning_message%
|
||||
paypal_url: "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9UBA65LG3FX9Y&lc=gb"
|
||||
flattr_url: "https://flattr.com/thing/1265480"
|
||||
form:
|
||||
resources:
|
||||
- LexikFormFilterBundle:Form:form_div_layout.html.twig
|
||||
|
||||
# Assetic Configuration
|
||||
assetic:
|
||||
debug: "%kernel.debug%"
|
||||
use_controller: false
|
||||
bundles: [ ]
|
||||
#java: /usr/bin/java
|
||||
filters:
|
||||
cssrewrite: ~
|
||||
#closure:
|
||||
# jar: "%kernel.root_dir%/Resources/java/compiler.jar"
|
||||
#yui_css:
|
||||
# jar: "%kernel.root_dir%/Resources/java/yuicompressor-2.4.7.jar"
|
||||
|
||||
# Doctrine Configuration
|
||||
doctrine:
|
||||
dbal:
|
||||
driver: "%database_driver%"
|
||||
host: "%database_host%"
|
||||
port: "%database_port%"
|
||||
dbname: "%database_name%"
|
||||
user: "%database_user%"
|
||||
password: "%database_password%"
|
||||
charset: UTF8
|
||||
path: "%database_path%"
|
||||
|
||||
orm:
|
||||
auto_generate_proxy_classes: "%kernel.debug%"
|
||||
entity_managers:
|
||||
default:
|
||||
naming_strategy: wallabag_core.doctrine.prefixed_naming_strategy
|
||||
auto_mapping: true
|
||||
|
||||
# Swiftmailer Configuration
|
||||
swiftmailer:
|
||||
transport: "%mailer_transport%"
|
||||
host: "%mailer_host%"
|
||||
username: "%mailer_user%"
|
||||
password: "%mailer_password%"
|
||||
spool: { type: memory }
|
||||
|
||||
fos_rest:
|
||||
param_fetcher_listener: true
|
||||
body_listener: true
|
||||
format_listener: true
|
||||
view:
|
||||
view_response_listener: 'force'
|
||||
formats:
|
||||
xml: true
|
||||
json : true
|
||||
templating_formats:
|
||||
html: true
|
||||
force_redirects:
|
||||
html: true
|
||||
failed_validation: HTTP_BAD_REQUEST
|
||||
default_engine: twig
|
||||
routing_loader:
|
||||
default_format: json
|
||||
|
||||
nelmio_api_doc: ~
|
||||
|
||||
nelmio_cors:
|
||||
defaults:
|
||||
allow_credentials: false
|
||||
allow_origin: []
|
||||
allow_headers: []
|
||||
allow_methods: []
|
||||
expose_headers: []
|
||||
max_age: 0
|
||||
hosts: []
|
||||
#origin_regex: false
|
||||
paths:
|
||||
'^/api/':
|
||||
allow_origin: ['*']
|
||||
allow_headers: ['X-Custom-Auth']
|
||||
allow_methods: ['POST', 'PUT', 'GET', 'DELETE']
|
||||
max_age: 3600
|
||||
'^/':
|
||||
#origin_regex: true
|
||||
allow_origin: ['^http://localhost:[0-9]+']
|
||||
allow_headers: ['X-Custom-Auth']
|
||||
allow_methods: ['POST', 'PUT', 'GET', 'DELETE']
|
||||
max_age: 3600
|
||||
hosts: ['^api\.']
|
||||
|
||||
liip_theme:
|
||||
load_controllers: false
|
||||
themes:
|
||||
- baggy
|
||||
- material
|
||||
autodetect_theme: wallabag_core.helper.detect_active_theme
|
||||
|
||||
path_patterns:
|
||||
# app_resource:
|
||||
# - %%app_path%%/views/themes/%%current_theme%%/%%template%%
|
||||
# - %%app_path%%/views/%%template%%
|
||||
bundle_resource:
|
||||
- %%bundle_path%%/Resources/views/themes/%%current_theme%%/%%template%%
|
||||
# bundle_resource_dir:
|
||||
# - %%dir%%/views/themes/%%current_theme%%/%%bundle_name%%/%%template%%
|
||||
# - %%dir%%/views/%%bundle_name%%/%%override_path%%
|
||||
|
||||
fos_user:
|
||||
db_driver: orm
|
||||
firewall_name: main
|
||||
user_class: Wallabag\CoreBundle\Entity\User
|
||||
54
app/config/config_dev.yml
Normal file
@ -0,0 +1,54 @@
|
||||
imports:
|
||||
- { resource: config.yml }
|
||||
|
||||
framework:
|
||||
router:
|
||||
resource: "%kernel.root_dir%/config/routing_dev.yml"
|
||||
strict_requirements: true
|
||||
profiler: { only_exceptions: false }
|
||||
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
intercept_redirects: false
|
||||
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
console:
|
||||
type: console
|
||||
bubble: false
|
||||
verbosity_levels:
|
||||
VERBOSITY_VERBOSE: INFO
|
||||
VERBOSITY_VERY_VERBOSE: DEBUG
|
||||
channels: ["!doctrine"]
|
||||
console_very_verbose:
|
||||
type: console
|
||||
bubble: false
|
||||
verbosity_levels:
|
||||
VERBOSITY_VERBOSE: NOTICE
|
||||
VERBOSITY_VERY_VERBOSE: NOTICE
|
||||
VERBOSITY_DEBUG: DEBUG
|
||||
channels: ["doctrine"]
|
||||
# uncomment to get logging in your browser
|
||||
# you may have to allow bigger header sizes in your Web server configuration
|
||||
#firephp:
|
||||
# type: firephp
|
||||
# level: info
|
||||
#chromephp:
|
||||
# type: chromephp
|
||||
# level: info
|
||||
|
||||
assetic:
|
||||
use_controller: true
|
||||
|
||||
swiftmailer:
|
||||
# see http://mailcatcher.me/
|
||||
transport: smtp
|
||||
host: 'localhost'
|
||||
port: 1025
|
||||
username: null
|
||||
password: null
|
||||
|
||||
30
app/config/config_prod.yml
Normal file
@ -0,0 +1,30 @@
|
||||
imports:
|
||||
- { resource: config.yml }
|
||||
|
||||
#framework:
|
||||
# validation:
|
||||
# cache: apc
|
||||
|
||||
#doctrine:
|
||||
# orm:
|
||||
# metadata_cache_driver: apc
|
||||
# result_cache_driver: apc
|
||||
# query_cache_driver: apc
|
||||
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
wsse:
|
||||
type: stream
|
||||
path: %kernel.logs_dir%/%kernel.environment%.wsse.log
|
||||
level: error
|
||||
channels: [wsse]
|
||||
nested:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
console:
|
||||
type: console
|
||||
31
app/config/config_test.yml
Normal file
@ -0,0 +1,31 @@
|
||||
imports:
|
||||
- { resource: config_dev.yml }
|
||||
|
||||
framework:
|
||||
test: ~
|
||||
session:
|
||||
storage_id: session.storage.mock_file
|
||||
profiler:
|
||||
collect: false
|
||||
|
||||
web_profiler:
|
||||
toolbar: false
|
||||
intercept_redirects: false
|
||||
|
||||
swiftmailer:
|
||||
# to be able to read emails sent
|
||||
spool:
|
||||
type: file
|
||||
|
||||
doctrine:
|
||||
dbal:
|
||||
driver: pdo_sqlite
|
||||
path: %kernel.root_dir%/../data/db/wallabag_test.sqlite
|
||||
host: localhost
|
||||
orm:
|
||||
metadata_cache_driver:
|
||||
type: service
|
||||
id: filesystem_cache
|
||||
query_cache_driver:
|
||||
type: service
|
||||
id: filesystem_cache
|
||||
50
app/config/parameters.yml.dist
Normal file
@ -0,0 +1,50 @@
|
||||
# This file is a "template" of what your parameters.yml file should look like
|
||||
parameters:
|
||||
database_driver: pdo_sqlite
|
||||
database_host: 127.0.0.1
|
||||
database_port: ~
|
||||
database_name: symfony
|
||||
database_user: root
|
||||
database_password: ~
|
||||
database_path: "%kernel.root_dir%/../data/db/wallabag.sqlite"
|
||||
database_table_prefix: wallabag_
|
||||
|
||||
mailer_transport: smtp
|
||||
mailer_host: 127.0.0.1
|
||||
mailer_user: ~
|
||||
mailer_password: ~
|
||||
|
||||
locale: en
|
||||
|
||||
# A secret key that's used to generate certain security-related tokens
|
||||
secret: ThisTokenIsNotSoSecretChangeIt
|
||||
|
||||
# wallabag misc
|
||||
app.version: 2.0.0-alpha
|
||||
|
||||
# message to display at the bottom of the page
|
||||
warning_message: >
|
||||
You're trying wallabag v2, which is in alpha version. If you find a bug, please have a look to <a href="https://github.com/wallabag/wallabag/issues">our issues list</a> and <a href="https://github.com/wallabag/wallabag/issues/new">open a new if necessary</a>
|
||||
|
||||
download_pictures: false # if true, pictures will be stored into data/assets for each article
|
||||
|
||||
# Entry view
|
||||
share_twitter: true
|
||||
share_mail: true
|
||||
share_shaarli: true
|
||||
shaarli_url: http://myshaarli.com
|
||||
share_diaspora: true
|
||||
diaspora_url: http://diasporapod.com
|
||||
flattr: true
|
||||
carrot: true
|
||||
show_printlink: true
|
||||
export_epub: true
|
||||
export_mobi: true
|
||||
export_pdf: true
|
||||
|
||||
# default user config
|
||||
items_on_page: 12
|
||||
theme: material
|
||||
language: en_US
|
||||
from_email: no-reply@wallabag.org
|
||||
rss_limit: 50
|
||||
32
app/config/routing.yml
Normal file
@ -0,0 +1,32 @@
|
||||
wallabag_api:
|
||||
resource: "@WallabagApiBundle/Resources/config/routing.yml"
|
||||
prefix: /
|
||||
|
||||
app:
|
||||
resource: @WallabagCoreBundle/Controller/
|
||||
type: annotation
|
||||
|
||||
doc-api:
|
||||
resource: "@NelmioApiDocBundle/Resources/config/routing.yml"
|
||||
prefix: /api/doc
|
||||
|
||||
login:
|
||||
pattern: /login
|
||||
defaults: { _controller: WallabagCoreBundle:Security:login }
|
||||
|
||||
login_check:
|
||||
pattern: /login_check
|
||||
|
||||
logout:
|
||||
path: /logout
|
||||
|
||||
rest :
|
||||
type : rest
|
||||
resource : "routing_rest.yml"
|
||||
prefix : /api
|
||||
|
||||
homepage:
|
||||
pattern: "/{page}"
|
||||
defaults: { _controller: WallabagCoreBundle:Entry:showUnread, page : 1 }
|
||||
requirements:
|
||||
page: \d+
|
||||
18
app/config/routing_dev.yml
Normal file
@ -0,0 +1,18 @@
|
||||
_wdt:
|
||||
resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml"
|
||||
prefix: /_wdt
|
||||
|
||||
_profiler:
|
||||
resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml"
|
||||
prefix: /_profiler
|
||||
|
||||
_configurator:
|
||||
resource: "@SensioDistributionBundle/Resources/config/routing/webconfigurator.xml"
|
||||
prefix: /_configurator
|
||||
|
||||
_errors:
|
||||
resource: "@TwigBundle/Resources/config/routing/errors.xml"
|
||||
prefix: /_error
|
||||
|
||||
_main:
|
||||
resource: routing.yml
|
||||
3
app/config/routing_rest.yml
Normal file
@ -0,0 +1,3 @@
|
||||
Rest_Wallabag:
|
||||
type : rest
|
||||
resource: "@WallabagApiBundle/Resources/config/routing_rest.yml"
|
||||
53
app/config/security.yml
Normal file
@ -0,0 +1,53 @@
|
||||
security:
|
||||
encoders:
|
||||
Wallabag\CoreBundle\Entity\User:
|
||||
algorithm: sha1
|
||||
encode_as_base64: false
|
||||
iterations: 1
|
||||
|
||||
role_hierarchy:
|
||||
ROLE_ADMIN: ROLE_USER
|
||||
ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
|
||||
|
||||
providers:
|
||||
administrators:
|
||||
entity: { class: WallabagCoreBundle:User, property: username }
|
||||
fos_userbundle:
|
||||
id: fos_user.user_provider.username
|
||||
|
||||
# the main part of the security, where you can set up firewalls
|
||||
# for specific sections of your app
|
||||
firewalls:
|
||||
wsse_secured:
|
||||
pattern: /api/.*
|
||||
wsse: true
|
||||
stateless: true
|
||||
anonymous: true
|
||||
login_firewall:
|
||||
pattern: ^/login$
|
||||
anonymous: ~
|
||||
|
||||
secured_area:
|
||||
pattern: ^/
|
||||
form_login:
|
||||
provider: fos_userbundle
|
||||
csrf_provider: security.csrf.token_manager
|
||||
|
||||
anonymous: true
|
||||
remember_me:
|
||||
key: "%secret%"
|
||||
lifetime: 31536000
|
||||
path: /
|
||||
domain: ~
|
||||
|
||||
logout:
|
||||
path: /logout
|
||||
target: /
|
||||
|
||||
access_control:
|
||||
- { path: ^/api/salt, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/forgot-password, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/, roles: ROLE_USER }
|
||||
19
app/config/services.yml
Normal file
@ -0,0 +1,19 @@
|
||||
# Learn more about services, parameters and containers at
|
||||
# http://symfony.com/doc/current/book/service_container.html
|
||||
parameters:
|
||||
security.authentication.provider.dao.class: Wallabag\CoreBundle\Security\Authentication\Provider\WallabagAuthenticationProvider
|
||||
security.encoder.digest.class: Wallabag\CoreBundle\Security\Authentication\Encoder\WallabagPasswordEncoder
|
||||
security.validator.user_password.class: Wallabag\CoreBundle\Security\Validator\WallabagUserPasswordValidator
|
||||
lexik_form_filter.get_filter.doctrine_orm.class: Wallabag\CoreBundle\Event\Subscriber\CustomDoctrineORMSubscriber
|
||||
|
||||
services:
|
||||
# used for tests
|
||||
filesystem_cache:
|
||||
class: Doctrine\Common\Cache\FilesystemCache
|
||||
arguments:
|
||||
- %kernel.cache_dir%/doctrine/metadata
|
||||
|
||||
twig.extension.text:
|
||||
class: Twig_Extensions_Extension_Text
|
||||
tags:
|
||||
- { name: twig.extension }
|
||||
27
app/console
Executable file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
// if you don't want to setup permissions the proper way, just uncomment the following PHP line
|
||||
// read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information
|
||||
//umask(0000);
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
require_once __DIR__.'/bootstrap.php.cache';
|
||||
require_once __DIR__.'/AppKernel.php';
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Debug\Debug;
|
||||
|
||||
$input = new ArgvInput();
|
||||
$env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev');
|
||||
$debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod';
|
||||
|
||||
if ($debug) {
|
||||
Debug::enable();
|
||||
}
|
||||
|
||||
$kernel = new AppKernel($env, $debug);
|
||||
$application = new Application($kernel);
|
||||
$application->run($input);
|
||||
0
app/logs/.gitkeep
Normal file
1
bin/phpunit
Symbolic link
@ -0,0 +1 @@
|
||||
../vendor/phpunit/phpunit/phpunit
|
||||
BIN
bin/poche.sqlite
Normal file
50
build.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="wallabag" default="build">
|
||||
<target name="build" depends="prepare"/>
|
||||
|
||||
<target name="clean" description="Cleanup build artifacts">
|
||||
<delete dir="${basedir}/app/cache"/>
|
||||
</target>
|
||||
|
||||
<target name="prepare" depends="clean" description="Prepare for build">
|
||||
<exec executable="composer">
|
||||
<arg value="install"/>
|
||||
<arg value="--no-interaction"/>
|
||||
<arg value="--no-progress"/>
|
||||
</exec>
|
||||
<exec executable="php">
|
||||
<arg value="${basedir}/app/console"/>
|
||||
<arg value="doctrine:database:drop"/>
|
||||
<arg value="--force"/>
|
||||
<arg value="--env=test"/>
|
||||
</exec>
|
||||
<exec executable="php">
|
||||
<arg value="${basedir}/app/console"/>
|
||||
<arg value="doctrine:database:create"/>
|
||||
<arg value="--env=test"/>
|
||||
</exec>
|
||||
<exec executable="php">
|
||||
<arg value="${basedir}/app/console"/>
|
||||
<arg value="doctrine:schema:create"/>
|
||||
<arg value="--env=test"/>
|
||||
</exec>
|
||||
<exec executable="php">
|
||||
<arg value="${basedir}/app/console"/>
|
||||
<arg value="cache:clear"/>
|
||||
<arg value="--env=test"/>
|
||||
</exec>
|
||||
<exec executable="php">
|
||||
<arg value="${basedir}/app/console"/>
|
||||
<arg value="doctrine:fixtures:load"/>
|
||||
<arg value="--no-interaction"/>
|
||||
<arg value="--env=test"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="phpunit" description="Run unit tests with PHPUnit + HTML Coverage">
|
||||
<exec executable="phpunit" failonerror="true">
|
||||
<arg value="--coverage-html"/>
|
||||
<arg value="build/coverage"/>
|
||||
</exec>
|
||||
</target>
|
||||
</project>
|
||||
1
cache/.gitignore
vendored
@ -1 +0,0 @@
|
||||
*
|
||||
95
composer.json
Normal file
@ -0,0 +1,95 @@
|
||||
{
|
||||
"name": "wallabag/wallabag",
|
||||
"type": "project",
|
||||
"description": "open source self hostable read-it-later web application",
|
||||
"keywords": ["read-it-later","read it later"],
|
||||
"homepage": "https://github.com/wallabag/wallabag",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Lœuillet",
|
||||
"email": "nicolas@loeuillet.org",
|
||||
"homepage": "http://www.cdetc.fr",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Thomas Citharel",
|
||||
"homepage": "http://tcit.fr",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Jérémy Benoist",
|
||||
"homepage": "http://www.j0k3r.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"email": "hello@wallabag.org",
|
||||
"issues": "https://github.com/wallabag/wallabag/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"symfony/symfony": "~2.7.0",
|
||||
"doctrine/orm": "~2.3",
|
||||
"doctrine/doctrine-bundle": "~1.2",
|
||||
"twig/extensions": "~1.0",
|
||||
"symfony/assetic-bundle": "~2.3",
|
||||
"symfony/swiftmailer-bundle": "~2.3",
|
||||
"symfony/monolog-bundle": "~2.4",
|
||||
"sensio/distribution-bundle": "~3.0.12",
|
||||
"sensio/framework-extra-bundle": "~3.0",
|
||||
"incenteev/composer-parameter-handler": "~2.0",
|
||||
"nelmio/cors-bundle": "~1.4.0",
|
||||
"friendsofsymfony/rest-bundle": "~1.4",
|
||||
"jms/serializer-bundle": "~0.13",
|
||||
"nelmio/api-doc-bundle": "~2.7",
|
||||
"ezyang/htmlpurifier": "~4.6",
|
||||
"mgargano/simplehtmldom": "~1.5",
|
||||
"tecnick.com/tcpdf": "~6.2",
|
||||
"simplepie/simplepie": "~1.3.1",
|
||||
"willdurand/hateoas-bundle": "~0.5.0",
|
||||
"htmlawed/htmlawed": "~1.1.19",
|
||||
"liip/theme-bundle": "~1.1.3",
|
||||
"pagerfanta/pagerfanta": "~1.0.3",
|
||||
"lexik/form-filter-bundle": "~4.0",
|
||||
"j0k3r/graby": "~1.0",
|
||||
"friendsofsymfony/user-bundle": "dev-master"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/doctrine-fixtures-bundle": "~2.2.0",
|
||||
"sensio/generator-bundle": "~2.5",
|
||||
"phpunit/phpunit": "~4.4"
|
||||
},
|
||||
"scripts": {
|
||||
"post-install-cmd": [
|
||||
"Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
|
||||
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
|
||||
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
|
||||
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
|
||||
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
|
||||
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
|
||||
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
|
||||
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
|
||||
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"symfony-app-dir": "app",
|
||||
"symfony-web-dir": "web",
|
||||
"symfony-assets-install": "relative",
|
||||
"incenteev-parameters": {
|
||||
"file": "app/config/parameters.yml"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "": "src/" }
|
||||
},
|
||||
"config": {
|
||||
"bin-dir": "bin"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
4455
composer.lock
generated
Normal file
66
config/deploy.rb
Normal file
@ -0,0 +1,66 @@
|
||||
# config valid only for current version of Capistrano
|
||||
lock '3.4.0'
|
||||
|
||||
set :application, 'wallabag'
|
||||
set :repo_url, 'git@github.com:wallabag/wallabag.git'
|
||||
|
||||
set :ssh_user, 'ssh_user'
|
||||
server 'server_ip', user: fetch(:ssh_user), roles: %w{web app db}
|
||||
|
||||
set :scm, :git
|
||||
|
||||
set :format, :pretty
|
||||
set :log_level, :info
|
||||
# set :log_level, :debug
|
||||
|
||||
set :composer_install_flags, '--no-dev --prefer-dist --no-interaction --optimize-autoloader'
|
||||
|
||||
set :linked_files, %w{app/config/parameters.yml}
|
||||
set :linked_dirs, %w{app/logs web/uploads}
|
||||
|
||||
set :keep_releases, 3
|
||||
|
||||
after 'deploy:finishing', 'deploy:cleanup'
|
||||
|
||||
# Default branch is :master
|
||||
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
|
||||
|
||||
# Default deploy_to directory is /var/www/my_app_name
|
||||
# set :deploy_to, '/var/www/my_app_name'
|
||||
|
||||
# Default value for :scm is :git
|
||||
# set :scm, :git
|
||||
|
||||
# Default value for :format is :pretty
|
||||
# set :format, :pretty
|
||||
|
||||
# Default value for :log_level is :debug
|
||||
# set :log_level, :debug
|
||||
|
||||
# Default value for :pty is false
|
||||
# set :pty, true
|
||||
|
||||
# Default value for :linked_files is []
|
||||
# set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')
|
||||
|
||||
# Default value for linked_dirs is []
|
||||
# set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system')
|
||||
|
||||
# Default value for default_env is {}
|
||||
# set :default_env, { path: "/opt/ruby/bin:$PATH" }
|
||||
|
||||
# Default value for keep_releases is 5
|
||||
# set :keep_releases, 5
|
||||
|
||||
namespace :deploy do
|
||||
|
||||
after :restart, :clear_cache do
|
||||
on roles(:web), in: :groups, limit: 3, wait: 10 do
|
||||
# Here we can do anything such as:
|
||||
# within release_path do
|
||||
# execute :rake, 'cache:clear'
|
||||
# end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
61
config/deploy/production.rb
Normal file
@ -0,0 +1,61 @@
|
||||
# server-based syntax
|
||||
# ======================
|
||||
# Defines a single server with a list of roles and multiple properties.
|
||||
# You can define all roles on a single server, or split them:
|
||||
|
||||
# server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value
|
||||
# server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value
|
||||
# server 'db.example.com', user: 'deploy', roles: %w{db}
|
||||
|
||||
|
||||
|
||||
# role-based syntax
|
||||
# ==================
|
||||
|
||||
# Defines a role with one or multiple servers. The primary server in each
|
||||
# group is considered to be the first unless any hosts have the primary
|
||||
# property set. Specify the username and a domain or IP for the server.
|
||||
# Don't use `:all`, it's a meta role.
|
||||
|
||||
# role :app, %w{deploy@example.com}, my_property: :my_value
|
||||
# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
|
||||
# role :db, %w{deploy@example.com}
|
||||
|
||||
|
||||
|
||||
# Configuration
|
||||
# =============
|
||||
# You can set any configuration variable like in config/deploy.rb
|
||||
# These variables are then only loaded and set in this stage.
|
||||
# For available Capistrano configuration variables see the documentation page.
|
||||
# http://capistranorb.com/documentation/getting-started/configuration/
|
||||
# Feel free to add new variables to customise your setup.
|
||||
|
||||
|
||||
|
||||
# Custom SSH Options
|
||||
# ==================
|
||||
# You may pass any option but keep in mind that net/ssh understands a
|
||||
# limited set of options, consult the Net::SSH documentation.
|
||||
# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
|
||||
#
|
||||
# Global options
|
||||
# --------------
|
||||
# set :ssh_options, {
|
||||
# keys: %w(/home/rlisowski/.ssh/id_rsa),
|
||||
# forward_agent: false,
|
||||
# auth_methods: %w(password)
|
||||
# }
|
||||
#
|
||||
# The server-based syntax can be used to override options:
|
||||
# ------------------------------------
|
||||
# server 'example.com',
|
||||
# user: 'user_name',
|
||||
# roles: %w{web app},
|
||||
# ssh_options: {
|
||||
# user: 'user_name', # overrides user setting above
|
||||
# keys: %w(/home/user_name/.ssh/id_rsa),
|
||||
# forward_agent: false,
|
||||
# auth_methods: %w(publickey password)
|
||||
# # password: 'please use keys'
|
||||
# }
|
||||
64
config/deploy/staging.rb
Normal file
@ -0,0 +1,64 @@
|
||||
# server-based syntax
|
||||
# ======================
|
||||
# Defines a single server with a list of roles and multiple properties.
|
||||
# You can define all roles on a single server, or split them:
|
||||
|
||||
set :branch, 'v2'
|
||||
set :deploy_to, '/var/www/'
|
||||
|
||||
# server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value
|
||||
# server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value
|
||||
# server 'db.example.com', user: 'deploy', roles: %w{db}
|
||||
|
||||
|
||||
|
||||
# role-based syntax
|
||||
# ==================
|
||||
|
||||
# Defines a role with one or multiple servers. The primary server in each
|
||||
# group is considered to be the first unless any hosts have the primary
|
||||
# property set. Specify the username and a domain or IP for the server.
|
||||
# Don't use `:all`, it's a meta role.
|
||||
|
||||
# role :app, %w{deploy@example.com}, my_property: :my_value
|
||||
# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
|
||||
# role :db, %w{deploy@example.com}
|
||||
|
||||
|
||||
|
||||
# Configuration
|
||||
# =============
|
||||
# You can set any configuration variable like in config/deploy.rb
|
||||
# These variables are then only loaded and set in this stage.
|
||||
# For available Capistrano configuration variables see the documentation page.
|
||||
# http://capistranorb.com/documentation/getting-started/configuration/
|
||||
# Feel free to add new variables to customise your setup.
|
||||
|
||||
|
||||
|
||||
# Custom SSH Options
|
||||
# ==================
|
||||
# You may pass any option but keep in mind that net/ssh understands a
|
||||
# limited set of options, consult the Net::SSH documentation.
|
||||
# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
|
||||
#
|
||||
# Global options
|
||||
# --------------
|
||||
# set :ssh_options, {
|
||||
# keys: %w(/home/rlisowski/.ssh/id_rsa),
|
||||
# forward_agent: false,
|
||||
# auth_methods: %w(password)
|
||||
# }
|
||||
#
|
||||
# The server-based syntax can be used to override options:
|
||||
# ------------------------------------
|
||||
# server 'example.com',
|
||||
# user: 'user_name',
|
||||
# roles: %w{web app},
|
||||
# ssh_options: {
|
||||
# user: 'user_name', # overrides user setting above
|
||||
# keys: %w(/home/user_name/.ssh/id_rsa),
|
||||
# forward_agent: false,
|
||||
# auth_methods: %w(publickey password)
|
||||
# # password: 'please use keys'
|
||||
# }
|
||||
259
css/style.css
@ -1,259 +0,0 @@
|
||||
/*** GENERAL ***/
|
||||
body {
|
||||
color: #222222;
|
||||
font: 20px/1.3em Palatino,Georgia,serif;
|
||||
background-color: #F1F1F1;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
|
||||
a, a:hover, a:visited {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#main ul#links {
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#main ul#links li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#main ul#links li a.current {
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
background-color: #040707;
|
||||
color: #F1F1F1;
|
||||
}
|
||||
|
||||
#main ul#sort {
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#main ul#sort li {
|
||||
display: inline;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#main ul#sort img:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#main, #article {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#links a, .backhome a{
|
||||
text-decoration: none;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
#links a:hover, .backhome a:hover{
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
background-color: #040707;
|
||||
color: #F1F1F1;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/*** ***/
|
||||
/*** LOGIN FORM ***/
|
||||
ul#login li {
|
||||
list-style-type: none;
|
||||
}
|
||||
/*** ***/
|
||||
/*** LINKS DISPLAY ***/
|
||||
|
||||
#main a.tool {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type=submit].delete {
|
||||
background : url('../img/remove.png') no-repeat center center;
|
||||
width : 16px;
|
||||
height :16px;
|
||||
border : none;
|
||||
color : transparent;
|
||||
cursor: pointer;
|
||||
font-size : 0;
|
||||
}
|
||||
|
||||
#main #content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#main .entrie {
|
||||
color: rgb(46, 46, 46);
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
min-height: 8em;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
#main .entrie h2 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#main .entrie h2 a:hover {
|
||||
color: #F5BE00;
|
||||
}
|
||||
|
||||
.tools {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.tools ul {
|
||||
padding: 0; margin: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.tools ul li {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.tools a.tool {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#article .tools {
|
||||
position: relative;
|
||||
display: inline;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#article.tools ul li{
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#main .entrie .tools a.tool span, #article .tools a.tool span {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
a.fav span {
|
||||
background: url('../img/star-on.png') no-repeat;
|
||||
}
|
||||
|
||||
a.fav span:hover {
|
||||
background: url('../img/star-off.png') no-repeat;
|
||||
}
|
||||
|
||||
a.fav-off span {
|
||||
background: url('../img/star-off.png') no-repeat;
|
||||
}
|
||||
|
||||
a.fav-off span:hover {
|
||||
background: url('../img/star-on.png') no-repeat;
|
||||
}
|
||||
|
||||
a.archive span {
|
||||
background: url('../img/checkmark-on.png') no-repeat;
|
||||
}
|
||||
|
||||
a.archive span:hover {
|
||||
background: url('../img/checkmark-off.png') no-repeat;
|
||||
}
|
||||
|
||||
a.archive-off span {
|
||||
background: url('../img/checkmark-off.png') no-repeat;
|
||||
}
|
||||
|
||||
a.archive-off span:hover {
|
||||
background: url('../img/checkmark-on.png') no-repeat;
|
||||
}
|
||||
|
||||
/*** ***/
|
||||
/*** ARTICLE PAGE ***/
|
||||
|
||||
body.article {
|
||||
color: #222222;
|
||||
font: 20px/1.3em Palatino,Georgia,serif;
|
||||
background-color: #F1F1F1;
|
||||
}
|
||||
|
||||
#article header {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #222222;
|
||||
}
|
||||
|
||||
#article header a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#article article {
|
||||
border-bottom: 1px solid #222222;
|
||||
}
|
||||
|
||||
.vieworiginal a {
|
||||
color: #888888;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.backhome {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/*** ***/
|
||||
|
||||
#main
|
||||
{
|
||||
max-width: 60em; /* 960 px */
|
||||
margin: 0 auto;
|
||||
}
|
||||
#content
|
||||
{
|
||||
width: 103.125%; /* 990px */
|
||||
overflow: hidden;
|
||||
margin-left: -1.562%; /* 15px */
|
||||
margin-bottom: -1.875em; /* 30px */
|
||||
}
|
||||
|
||||
.entrie
|
||||
{
|
||||
width: 30.303%; /* 300px */
|
||||
background-color: #fff;
|
||||
float: left;
|
||||
margin: 0 1.515% 1.875em; /* 15px 30px */
|
||||
}
|
||||
|
||||
@media only screen and ( max-width: 40em ) /* 640px */
|
||||
{
|
||||
.entrie
|
||||
{
|
||||
width: 46.876%; /* 305px */
|
||||
margin-bottom: 0.938em; /* 15px */
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and ( max-width: 20em ) /* 320px */
|
||||
{
|
||||
#content
|
||||
{
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.entrie
|
||||
{
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
0
data/assets/.gitignore
vendored
Normal file
0
data/db/.gitignore
vendored
Normal file
BIN
db/poche.sqlite
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 277 B |
|
Before Width: | Height: | Size: 235 B |
BIN
img/favicon.ico
|
Before Width: | Height: | Size: 346 B |
BIN
img/logo.png
|
Before Width: | Height: | Size: 911 B |
BIN
img/remove.png
|
Before Width: | Height: | Size: 252 B |
BIN
img/star-off.png
|
Before Width: | Height: | Size: 314 B |
BIN
img/star-on.png
|
Before Width: | Height: | Size: 281 B |
50
import.php
@ -1,50 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* poche, a read it later open source system
|
||||
*
|
||||
* @category poche
|
||||
* @author Nicolas Lœuillet <support@inthepoche.com>
|
||||
* @copyright 2013
|
||||
* @license http://www.wtfpl.net/ see COPYING file
|
||||
*/
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
include dirname(__FILE__).'/inc/config.php';
|
||||
include dirname(__FILE__).'/inc/simple_html_dom.php';
|
||||
|
||||
if (!isset($_GET['start'])) {
|
||||
echo 'Please execute the import script locally, it can take a very long time. <br /><a href="import.php?start">Bye bye Pocket, let\'s go !</a>';
|
||||
}
|
||||
else {
|
||||
$html = new simple_html_dom();
|
||||
$html->load_file('ril_export.html');
|
||||
|
||||
$read = 0;
|
||||
$errors = array();
|
||||
foreach($html->find('ul') as $ul)
|
||||
{
|
||||
foreach($ul->find('li') as $li)
|
||||
{
|
||||
$a = $li->find('a');
|
||||
$url = $a[0]->href;
|
||||
|
||||
|
||||
action_to_do('add', $url);
|
||||
if ($read == '1') {
|
||||
$last_id = $db->getHandle()->lastInsertId();
|
||||
$sql_update = "UPDATE entries SET is_read=~is_read WHERE id=?";
|
||||
$params_update = array($last_id);
|
||||
$query_update = $db->getHandle()->prepare($sql_update);
|
||||
$query_update->execute($params_update);
|
||||
}
|
||||
}
|
||||
# Pocket génère un fichier HTML avec deux <ul>
|
||||
# Le premier concerne les éléments non lus
|
||||
# Le second concerne les éléments archivés
|
||||
$read = 1;
|
||||
}
|
||||
|
||||
echo 'Import from Pocket completed. <a href="index.php">Welcome to #poche !</a>';
|
||||
logm('import from pocket completed');
|
||||
}
|
||||
262
inc/Encoding.php
@ -1,262 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @author "Sebastián Grignoli" <grignoli@framework2.com.ar>
|
||||
* @package Encoding
|
||||
* @version 1.1
|
||||
* @link http://www.framework2.com.ar/dzone/forceUTF8-es/
|
||||
* @example http://www.framework2.com.ar/dzone/forceUTF8-es/
|
||||
*/
|
||||
|
||||
class Encoding {
|
||||
|
||||
protected static $win1252ToUtf8 = array(
|
||||
128 => "\xe2\x82\xac",
|
||||
|
||||
130 => "\xe2\x80\x9a",
|
||||
131 => "\xc6\x92",
|
||||
132 => "\xe2\x80\x9e",
|
||||
133 => "\xe2\x80\xa6",
|
||||
134 => "\xe2\x80\xa0",
|
||||
135 => "\xe2\x80\xa1",
|
||||
136 => "\xcb\x86",
|
||||
137 => "\xe2\x80\xb0",
|
||||
138 => "\xc5\xa0",
|
||||
139 => "\xe2\x80\xb9",
|
||||
140 => "\xc5\x92",
|
||||
|
||||
142 => "\xc5\xbd",
|
||||
|
||||
|
||||
145 => "\xe2\x80\x98",
|
||||
146 => "\xe2\x80\x99",
|
||||
147 => "\xe2\x80\x9c",
|
||||
148 => "\xe2\x80\x9d",
|
||||
149 => "\xe2\x80\xa2",
|
||||
150 => "\xe2\x80\x93",
|
||||
151 => "\xe2\x80\x94",
|
||||
152 => "\xcb\x9c",
|
||||
153 => "\xe2\x84\xa2",
|
||||
154 => "\xc5\xa1",
|
||||
155 => "\xe2\x80\xba",
|
||||
156 => "\xc5\x93",
|
||||
|
||||
158 => "\xc5\xbe",
|
||||
159 => "\xc5\xb8"
|
||||
);
|
||||
|
||||
protected static $brokenUtf8ToUtf8 = array(
|
||||
"\xc2\x80" => "\xe2\x82\xac",
|
||||
|
||||
"\xc2\x82" => "\xe2\x80\x9a",
|
||||
"\xc2\x83" => "\xc6\x92",
|
||||
"\xc2\x84" => "\xe2\x80\x9e",
|
||||
"\xc2\x85" => "\xe2\x80\xa6",
|
||||
"\xc2\x86" => "\xe2\x80\xa0",
|
||||
"\xc2\x87" => "\xe2\x80\xa1",
|
||||
"\xc2\x88" => "\xcb\x86",
|
||||
"\xc2\x89" => "\xe2\x80\xb0",
|
||||
"\xc2\x8a" => "\xc5\xa0",
|
||||
"\xc2\x8b" => "\xe2\x80\xb9",
|
||||
"\xc2\x8c" => "\xc5\x92",
|
||||
|
||||
"\xc2\x8e" => "\xc5\xbd",
|
||||
|
||||
|
||||
"\xc2\x91" => "\xe2\x80\x98",
|
||||
"\xc2\x92" => "\xe2\x80\x99",
|
||||
"\xc2\x93" => "\xe2\x80\x9c",
|
||||
"\xc2\x94" => "\xe2\x80\x9d",
|
||||
"\xc2\x95" => "\xe2\x80\xa2",
|
||||
"\xc2\x96" => "\xe2\x80\x93",
|
||||
"\xc2\x97" => "\xe2\x80\x94",
|
||||
"\xc2\x98" => "\xcb\x9c",
|
||||
"\xc2\x99" => "\xe2\x84\xa2",
|
||||
"\xc2\x9a" => "\xc5\xa1",
|
||||
"\xc2\x9b" => "\xe2\x80\xba",
|
||||
"\xc2\x9c" => "\xc5\x93",
|
||||
|
||||
"\xc2\x9e" => "\xc5\xbe",
|
||||
"\xc2\x9f" => "\xc5\xb8"
|
||||
);
|
||||
|
||||
protected static $utf8ToWin1252 = array(
|
||||
"\xe2\x82\xac" => "\x80",
|
||||
|
||||
"\xe2\x80\x9a" => "\x82",
|
||||
"\xc6\x92" => "\x83",
|
||||
"\xe2\x80\x9e" => "\x84",
|
||||
"\xe2\x80\xa6" => "\x85",
|
||||
"\xe2\x80\xa0" => "\x86",
|
||||
"\xe2\x80\xa1" => "\x87",
|
||||
"\xcb\x86" => "\x88",
|
||||
"\xe2\x80\xb0" => "\x89",
|
||||
"\xc5\xa0" => "\x8a",
|
||||
"\xe2\x80\xb9" => "\x8b",
|
||||
"\xc5\x92" => "\x8c",
|
||||
|
||||
"\xc5\xbd" => "\x8e",
|
||||
|
||||
|
||||
"\xe2\x80\x98" => "\x91",
|
||||
"\xe2\x80\x99" => "\x92",
|
||||
"\xe2\x80\x9c" => "\x93",
|
||||
"\xe2\x80\x9d" => "\x94",
|
||||
"\xe2\x80\xa2" => "\x95",
|
||||
"\xe2\x80\x93" => "\x96",
|
||||
"\xe2\x80\x94" => "\x97",
|
||||
"\xcb\x9c" => "\x98",
|
||||
"\xe2\x84\xa2" => "\x99",
|
||||
"\xc5\xa1" => "\x9a",
|
||||
"\xe2\x80\xba" => "\x9b",
|
||||
"\xc5\x93" => "\x9c",
|
||||
|
||||
"\xc5\xbe" => "\x9e",
|
||||
"\xc5\xb8" => "\x9f"
|
||||
);
|
||||
|
||||
static function toUTF8($text){
|
||||
/**
|
||||
* Function Encoding::toUTF8
|
||||
*
|
||||
* This function leaves UTF8 characters alone, while converting almost all non-UTF8 to UTF8.
|
||||
*
|
||||
* It assumes that the encoding of the original string is either Windows-1252 or ISO 8859-1.
|
||||
*
|
||||
* It may fail to convert characters to UTF-8 if they fall into one of these scenarios:
|
||||
*
|
||||
* 1) when any of these characters: ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß
|
||||
* are followed by any of these: ("group B")
|
||||
* ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶•¸¹º»¼½¾¿
|
||||
* For example: %ABREPRESENT%C9%BB. «REPRESENTÉ»
|
||||
* The "«" (%AB) character will be converted, but the "É" followed by "»" (%C9%BB)
|
||||
* is also a valid unicode character, and will be left unchanged.
|
||||
*
|
||||
* 2) when any of these: àáâãäåæçèéêëìíîï are followed by TWO chars from group B,
|
||||
* 3) when any of these: ðñòó are followed by THREE chars from group B.
|
||||
*
|
||||
* @name toUTF8
|
||||
* @param string $text Any string.
|
||||
* @return string The same string, UTF8 encoded
|
||||
*
|
||||
*/
|
||||
|
||||
if(is_array($text))
|
||||
{
|
||||
foreach($text as $k => $v)
|
||||
{
|
||||
$text[$k] = self::toUTF8($v);
|
||||
}
|
||||
return $text;
|
||||
} elseif(is_string($text)) {
|
||||
|
||||
$max = strlen($text);
|
||||
$buf = "";
|
||||
for($i = 0; $i < $max; $i++){
|
||||
$c1 = $text{$i};
|
||||
if($c1>="\xc0"){ //Should be converted to UTF8, if it's not UTF8 already
|
||||
$c2 = $i+1 >= $max? "\x00" : $text{$i+1};
|
||||
$c3 = $i+2 >= $max? "\x00" : $text{$i+2};
|
||||
$c4 = $i+3 >= $max? "\x00" : $text{$i+3};
|
||||
if($c1 >= "\xc0" & $c1 <= "\xdf"){ //looks like 2 bytes UTF8
|
||||
if($c2 >= "\x80" && $c2 <= "\xbf"){ //yeah, almost sure it's UTF8 already
|
||||
$buf .= $c1 . $c2;
|
||||
$i++;
|
||||
} else { //not valid UTF8. Convert it.
|
||||
$cc1 = (chr(ord($c1) / 64) | "\xc0");
|
||||
$cc2 = ($c1 & "\x3f") | "\x80";
|
||||
$buf .= $cc1 . $cc2;
|
||||
}
|
||||
} elseif($c1 >= "\xe0" & $c1 <= "\xef"){ //looks like 3 bytes UTF8
|
||||
if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf"){ //yeah, almost sure it's UTF8 already
|
||||
$buf .= $c1 . $c2 . $c3;
|
||||
$i = $i + 2;
|
||||
} else { //not valid UTF8. Convert it.
|
||||
$cc1 = (chr(ord($c1) / 64) | "\xc0");
|
||||
$cc2 = ($c1 & "\x3f") | "\x80";
|
||||
$buf .= $cc1 . $cc2;
|
||||
}
|
||||
} elseif($c1 >= "\xf0" & $c1 <= "\xf7"){ //looks like 4 bytes UTF8
|
||||
if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf" && $c4 >= "\x80" && $c4 <= "\xbf"){ //yeah, almost sure it's UTF8 already
|
||||
$buf .= $c1 . $c2 . $c3;
|
||||
$i = $i + 2;
|
||||
} else { //not valid UTF8. Convert it.
|
||||
$cc1 = (chr(ord($c1) / 64) | "\xc0");
|
||||
$cc2 = ($c1 & "\x3f") | "\x80";
|
||||
$buf .= $cc1 . $cc2;
|
||||
}
|
||||
} else { //doesn't look like UTF8, but should be converted
|
||||
$cc1 = (chr(ord($c1) / 64) | "\xc0");
|
||||
$cc2 = (($c1 & "\x3f") | "\x80");
|
||||
$buf .= $cc1 . $cc2;
|
||||
}
|
||||
} elseif(($c1 & "\xc0") == "\x80"){ // needs conversion
|
||||
if(isset(self::$win1252ToUtf8[ord($c1)])) { //found in Windows-1252 special cases
|
||||
$buf .= self::$win1252ToUtf8[ord($c1)];
|
||||
} else {
|
||||
$cc1 = (chr(ord($c1) / 64) | "\xc0");
|
||||
$cc2 = (($c1 & "\x3f") | "\x80");
|
||||
$buf .= $cc1 . $cc2;
|
||||
}
|
||||
} else { // it doesn't need convesion
|
||||
$buf .= $c1;
|
||||
}
|
||||
}
|
||||
return $buf;
|
||||
} else {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
static function toWin1252($text) {
|
||||
if(is_array($text)) {
|
||||
foreach($text as $k => $v) {
|
||||
$text[$k] = self::toWin1252($v);
|
||||
}
|
||||
return $text;
|
||||
} elseif(is_string($text)) {
|
||||
return utf8_decode(str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), self::toUTF8($text)));
|
||||
} else {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
static function toISO8859($text) {
|
||||
return self::toWin1252($text);
|
||||
}
|
||||
|
||||
static function toLatin1($text) {
|
||||
return self::toWin1252($text);
|
||||
}
|
||||
|
||||
static function fixUTF8($text){
|
||||
if(is_array($text)) {
|
||||
foreach($text as $k => $v) {
|
||||
$text[$k] = self::fixUTF8($v);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
$last = "";
|
||||
while($last <> $text){
|
||||
$last = $text;
|
||||
$text = self::toUTF8(utf8_decode(str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), $text)));
|
||||
}
|
||||
$text = self::toUTF8(utf8_decode(str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), $text)));
|
||||
return $text;
|
||||
}
|
||||
|
||||
static function UTF8FixWin1252Chars($text){
|
||||
// If you received an UTF-8 string that was converted from Windows-1252 as it was ISO8859-1
|
||||
// (ignoring Windows-1252 chars from 80 to 9F) use this function to fix it.
|
||||
// See: http://en.wikipedia.org/wiki/Windows-1252
|
||||
|
||||
return str_replace(array_keys(self::$brokenUtf8ToUtf8), array_values(self::$brokenUtf8ToUtf8), $text);
|
||||
}
|
||||
|
||||
static function removeBOM($str=""){
|
||||
if(substr($str, 0,3) == pack("CCC",0xef,0xbb,0xbf)) {
|
||||
$str=substr($str, 3);
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
@ -1,110 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* JavaScript-like HTML DOM Element
|
||||
*
|
||||
* This class extends PHP's DOMElement to allow
|
||||
* users to get and set the innerHTML property of
|
||||
* HTML elements in the same way it's done in
|
||||
* JavaScript.
|
||||
*
|
||||
* Example usage:
|
||||
* @code
|
||||
* require_once 'JSLikeHTMLElement.php';
|
||||
* header('Content-Type: text/plain');
|
||||
* $doc = new DOMDocument();
|
||||
* $doc->registerNodeClass('DOMElement', 'JSLikeHTMLElement');
|
||||
* $doc->loadHTML('<div><p>Para 1</p><p>Para 2</p></div>');
|
||||
* $elem = $doc->getElementsByTagName('div')->item(0);
|
||||
*
|
||||
* // print innerHTML
|
||||
* echo $elem->innerHTML; // prints '<p>Para 1</p><p>Para 2</p>'
|
||||
* echo "\n\n";
|
||||
*
|
||||
* // set innerHTML
|
||||
* $elem->innerHTML = '<a href="http://fivefilters.org">FiveFilters.org</a>';
|
||||
* echo $elem->innerHTML; // prints '<a href="http://fivefilters.org">FiveFilters.org</a>'
|
||||
* echo "\n\n";
|
||||
*
|
||||
* // print document (with our changes)
|
||||
* echo $doc->saveXML();
|
||||
* @endcode
|
||||
*
|
||||
* @author Keyvan Minoukadeh - http://www.keyvan.net - keyvan@keyvan.net
|
||||
* @see http://fivefilters.org (the project this was written for)
|
||||
*/
|
||||
class JSLikeHTMLElement extends DOMElement
|
||||
{
|
||||
/**
|
||||
* Used for setting innerHTML like it's done in JavaScript:
|
||||
* @code
|
||||
* $div->innerHTML = '<h2>Chapter 2</h2><p>The story begins...</p>';
|
||||
* @endcode
|
||||
*/
|
||||
public function __set($name, $value) {
|
||||
if ($name == 'innerHTML') {
|
||||
// first, empty the element
|
||||
for ($x=$this->childNodes->length-1; $x>=0; $x--) {
|
||||
$this->removeChild($this->childNodes->item($x));
|
||||
}
|
||||
// $value holds our new inner HTML
|
||||
if ($value != '') {
|
||||
$f = $this->ownerDocument->createDocumentFragment();
|
||||
// appendXML() expects well-formed markup (XHTML)
|
||||
$result = @$f->appendXML($value); // @ to suppress PHP warnings
|
||||
if ($result) {
|
||||
if ($f->hasChildNodes()) $this->appendChild($f);
|
||||
} else {
|
||||
// $value is probably ill-formed
|
||||
$f = new DOMDocument();
|
||||
$value = mb_convert_encoding($value, 'HTML-ENTITIES', 'UTF-8');
|
||||
// Using <htmlfragment> will generate a warning, but so will bad HTML
|
||||
// (and by this point, bad HTML is what we've got).
|
||||
// We use it (and suppress the warning) because an HTML fragment will
|
||||
// be wrapped around <html><body> tags which we don't really want to keep.
|
||||
// Note: despite the warning, if loadHTML succeeds it will return true.
|
||||
$result = @$f->loadHTML('<htmlfragment>'.$value.'</htmlfragment>');
|
||||
if ($result) {
|
||||
$import = $f->getElementsByTagName('htmlfragment')->item(0);
|
||||
foreach ($import->childNodes as $child) {
|
||||
$importedNode = $this->ownerDocument->importNode($child, true);
|
||||
$this->appendChild($importedNode);
|
||||
}
|
||||
} else {
|
||||
// oh well, we tried, we really did. :(
|
||||
// this element is now empty
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$trace = debug_backtrace();
|
||||
trigger_error('Undefined property via __set(): '.$name.' in '.$trace[0]['file'].' on line '.$trace[0]['line'], E_USER_NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for getting innerHTML like it's done in JavaScript:
|
||||
* @code
|
||||
* $string = $div->innerHTML;
|
||||
* @endcode
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if ($name == 'innerHTML') {
|
||||
$inner = '';
|
||||
foreach ($this->childNodes as $child) {
|
||||
$inner .= $this->ownerDocument->saveXML($child);
|
||||
}
|
||||
return $inner;
|
||||
}
|
||||
|
||||
$trace = debug_backtrace();
|
||||
trigger_error('Undefined property via __get(): '.$name.' in '.$trace[0]['file'].' on line '.$trace[0]['line'], E_USER_NOTICE);
|
||||
return null;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return '['.$this->tagName.']';
|
||||
}
|
||||
}
|
||||
?>
|
||||
@ -1,256 +0,0 @@
|
||||
<?php
|
||||
class MyTool
|
||||
{
|
||||
public static function initPhp()
|
||||
{
|
||||
define('START_TIME', microtime(true));
|
||||
|
||||
if (phpversion() < 5) {
|
||||
die("Argh you don't have PHP 5 !");
|
||||
}
|
||||
|
||||
error_reporting(E_ALL);
|
||||
|
||||
function stripslashesDeep($value) {
|
||||
return is_array($value)
|
||||
? array_map('stripslashesDeep', $value)
|
||||
: stripslashes($value);
|
||||
}
|
||||
|
||||
if (get_magic_quotes_gpc()) {
|
||||
$_POST = array_map('stripslashesDeep', $_POST);
|
||||
$_GET = array_map('stripslashesDeep', $_GET);
|
||||
$_COOKIE = array_map('stripslashesDeep', $_COOKIE);
|
||||
}
|
||||
|
||||
ob_start();
|
||||
register_shutdown_function('ob_end_flush');
|
||||
}
|
||||
|
||||
public static function isUrl($url)
|
||||
{
|
||||
// http://neo22s.com/check-if-url-exists-and-is-online-php/
|
||||
$pattern='|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i';
|
||||
|
||||
return preg_match($pattern, $url);
|
||||
}
|
||||
|
||||
public static function isEmail($email)
|
||||
{
|
||||
$pattern = "/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2, 4}$/i";
|
||||
|
||||
return (preg_match($pattern, $email));
|
||||
}
|
||||
|
||||
public static function formatBBCode($text)
|
||||
{
|
||||
$replace = array(
|
||||
'/\[m\](.+?)\[\/m\]/is'
|
||||
=> '/* moderate */',
|
||||
'/\[b\](.+?)\[\/b\]/is'
|
||||
=> '<strong>$1</strong>',
|
||||
'/\[i\](.+?)\[\/i\]/is'
|
||||
=> '<em>$1</em>',
|
||||
'/\[s\](.+?)\[\/s\]/is'
|
||||
=> '<del>$1</del>',
|
||||
'/\[u\](.+?)\[\/u\]/is'
|
||||
=> '<span style="text-decoration: underline;">$1</span>',
|
||||
'/\[url\](.+?)\[\/url]/is'
|
||||
=> '<a href="$1">$1</a>',
|
||||
'/\[url=(\w+:\/\/[^\]]+)\](.+?)\[\/url]/is'
|
||||
=> '<a href="$1">$2</a>',
|
||||
'/\[quote\](.+?)\[\/quote\]/is'
|
||||
=> '<blockquote>$1</blockquote>',
|
||||
'/\[code\](.+?)\[\/code\]/is'
|
||||
=> '<code>$1</code>',
|
||||
'/\[([^[]+)\|([^[]+)\]/is'
|
||||
=> '<a href="$2">$1</a>'
|
||||
);
|
||||
$text = preg_replace(
|
||||
array_keys($replace),
|
||||
array_values($replace),
|
||||
$text
|
||||
);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public static function formatText($text)
|
||||
{
|
||||
$text = preg_replace_callback(
|
||||
'/<code_html>(.*?)<\/code_html>/is',
|
||||
create_function(
|
||||
'$matches',
|
||||
'return htmlspecialchars($matches[1]);'
|
||||
),
|
||||
$text
|
||||
);
|
||||
$text = preg_replace_callback(
|
||||
'/<code_php>(.*?)<\/code_php>/is',
|
||||
create_function(
|
||||
'$matches',
|
||||
'return highlight_string("<?php $matches[1] ?>", true);'
|
||||
),
|
||||
$text
|
||||
);
|
||||
$text = preg_replace('/<br \/>/is', '', $text);
|
||||
|
||||
$text = preg_replace(
|
||||
'#(^|\s)([a-z]+://([^\s\w/]?[\w/])*)(\s|$)#im',
|
||||
'\\1<a href="\\2">\\2</a>\\4',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace(
|
||||
'#(^|\s)wp:?([a-z]{2}|):([\w]+)#im',
|
||||
'\\1<a href="http://\\2.wikipedia.org/wiki/\\3">\\3</a>',
|
||||
$text
|
||||
);
|
||||
$text = str_replace(
|
||||
'http://.wikipedia.org/wiki/',
|
||||
'http://www.wikipedia.org/wiki/',
|
||||
$text
|
||||
);
|
||||
$text = str_replace('\wp:', 'wp:', $text);
|
||||
$text = str_replace('\http:', 'http:', $text);
|
||||
$text = MyTool::formatBBCode($text);
|
||||
$text = nl2br($text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public static function getUrl()
|
||||
{
|
||||
$https = (!empty($_SERVER['HTTPS'])
|
||||
&& (strtolower($_SERVER['HTTPS']) == 'on'))
|
||||
|| (isset($_SERVER["SERVER_PORT"])
|
||||
&& $_SERVER["SERVER_PORT"] == '443'); // HTTPS detection.
|
||||
$serverport = (!isset($_SERVER["SERVER_PORT"])
|
||||
|| $_SERVER["SERVER_PORT"] == '80'
|
||||
|| ($https && $_SERVER["SERVER_PORT"] == '443')
|
||||
? ''
|
||||
: ':' . $_SERVER["SERVER_PORT"]);
|
||||
|
||||
$scriptname = str_replace('/index.php', '/', $_SERVER["SCRIPT_NAME"]);
|
||||
|
||||
if (!isset($_SERVER["SERVER_NAME"])) {
|
||||
return $scriptname;
|
||||
}
|
||||
|
||||
return 'http' . ($https ? 's' : '') . '://'
|
||||
. $_SERVER["SERVER_NAME"] . $serverport . $scriptname;
|
||||
}
|
||||
|
||||
public static function rrmdir($dir)
|
||||
{
|
||||
if (is_dir($dir) && ($d = @opendir($dir))) {
|
||||
while (($file = @readdir($d)) !== false) {
|
||||
if ( $file == '.' || $file == '..' ) {
|
||||
continue;
|
||||
} else {
|
||||
unlink($dir . '/' . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function humanBytes($bytes)
|
||||
{
|
||||
$siPrefix = array( 'bytes', 'KB', 'MB', 'GB', 'TB', 'EB', 'ZB', 'YB' );
|
||||
$base = 1024;
|
||||
$class = min((int) log($bytes, $base), count($siPrefix) - 1);
|
||||
$val = sprintf('%1.2f', $bytes / pow($base, $class));
|
||||
|
||||
return $val . ' ' . $siPrefix[$class];
|
||||
}
|
||||
|
||||
public static function returnBytes($val)
|
||||
{
|
||||
$val = trim($val);
|
||||
$last = strtolower($val[strlen($val)-1]);
|
||||
switch($last)
|
||||
{
|
||||
case 'g': $val *= 1024;
|
||||
case 'm': $val *= 1024;
|
||||
case 'k': $val *= 1024;
|
||||
}
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
public static function getMaxFileSize()
|
||||
{
|
||||
$sizePostMax = MyTool::returnBytes(ini_get('post_max_size'));
|
||||
$sizeUploadMax = MyTool::returnBytes(ini_get('upload_max_filesize'));
|
||||
|
||||
// Return the smaller of two:
|
||||
return min($sizePostMax, $sizeUploadMax);
|
||||
}
|
||||
|
||||
public static function smallHash($text)
|
||||
{
|
||||
$t = rtrim(base64_encode(hash('crc32', $text, true)), '=');
|
||||
// Get rid of characters which need encoding in URLs.
|
||||
$t = str_replace('+', '-', $t);
|
||||
$t = str_replace('/', '_', $t);
|
||||
$t = str_replace('=', '@', $t);
|
||||
|
||||
return $t;
|
||||
}
|
||||
|
||||
public static function renderJson($data)
|
||||
{
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Content-type: application/json; charset=UTF-8');
|
||||
|
||||
echo json_encode($data);
|
||||
exit();
|
||||
}
|
||||
|
||||
public static function grabToLocal($url, $file, $force = false)
|
||||
{
|
||||
if ((!file_exists($file) || $force) && in_array('curl', get_loaded_extensions())){
|
||||
$ch = curl_init ($url);
|
||||
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
|
||||
$raw = curl_exec($ch);
|
||||
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200) {
|
||||
$fp = fopen($file, 'x');
|
||||
fwrite($fp, $raw);
|
||||
fclose($fp);
|
||||
}
|
||||
curl_close ($ch);
|
||||
}
|
||||
}
|
||||
|
||||
public static function redirect($rurl = '')
|
||||
{
|
||||
if ($rurl === '') {
|
||||
// if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['SERVER_NAME'])==0)
|
||||
$rurl = (empty($_SERVER['HTTP_REFERER'])?'?':$_SERVER['HTTP_REFERER']);
|
||||
if (isset($_POST['returnurl'])) {
|
||||
$rurl = $_POST['returnurl'];
|
||||
}
|
||||
}
|
||||
|
||||
// prevent loop
|
||||
if (empty($rurl) || parse_url($rurl, PHP_URL_QUERY) === $_SERVER['QUERY_STRING']) {
|
||||
$rurl = MyTool::getUrl();
|
||||
}
|
||||
|
||||
if (substr($rurl, 0, 1) !== '?') {
|
||||
$ref = MyTool::getUrl();
|
||||
if (substr($rurl, 0, strlen($ref)) !== $ref) {
|
||||
$rurl = $ref;
|
||||
}
|
||||
}
|
||||
header('Location: '.$rurl);
|
||||
exit();
|
||||
}
|
||||
|
||||
public static function silence_errors($num, $str)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
1103
inc/Readability.php
@ -1,136 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Session management class
|
||||
* http://www.developpez.net/forums/d51943/php/langage/sessions/
|
||||
* http://sebsauvage.net/wiki/doku.php?id=php:session
|
||||
* http://sebsauvage.net/wiki/doku.php?id=php:shaarli
|
||||
*
|
||||
* Features:
|
||||
* - Everything is stored on server-side (we do not trust client-side data,
|
||||
* such as cookie expiration)
|
||||
* - IP addresses + user agent are checked on each access to prevent session
|
||||
* cookie hijacking (such as Firesheep)
|
||||
* - Session expires on user inactivity (Session expiration date is
|
||||
* automatically updated everytime the user accesses a page.)
|
||||
* - A unique secret key is generated on server-side for this session
|
||||
* (and never sent over the wire) which can be used
|
||||
* to sign forms (HMAC) (See $_SESSION['uid'] )
|
||||
* - Token management to prevent XSRF attacks.
|
||||
*
|
||||
* TODO:
|
||||
* - log login fail
|
||||
* - prevent brute force (ban IP)
|
||||
*
|
||||
* HOWTOUSE:
|
||||
* - Just call Session::init(); to initialize session and
|
||||
* check if connected with Session::isLogged()
|
||||
*/
|
||||
|
||||
class Session
|
||||
{
|
||||
// If the user does not access any page within this time,
|
||||
// his/her session is considered expired (in seconds).
|
||||
public static $inactivity_timeout = 3600;
|
||||
private static $_instance;
|
||||
|
||||
// constructor
|
||||
private function __construct()
|
||||
{
|
||||
// Use cookies to store session.
|
||||
ini_set('session.use_cookies', 1);
|
||||
// Force cookies for session (phpsessionID forbidden in URL)
|
||||
ini_set('session.use_only_cookies', 1);
|
||||
if (!session_id()){
|
||||
// Prevent php to use sessionID in URL if cookies are disabled.
|
||||
ini_set('session.use_trans_sid', false);
|
||||
session_start('poche');
|
||||
}
|
||||
}
|
||||
|
||||
// initialize session
|
||||
public static function init()
|
||||
{
|
||||
if (!isset(self::$_instance)) {
|
||||
self::$_instance = new Session();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the IP address, user agent and language of the client
|
||||
// (Used to prevent session cookie hijacking.)
|
||||
private static function _allInfos()
|
||||
{
|
||||
$infos = $_SERVER["REMOTE_ADDR"];
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$infos.=$_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
}
|
||||
if (isset($_SERVER['HTTP_CLIENT_IP'])) {
|
||||
$infos.='_'.$_SERVER['HTTP_CLIENT_IP'];
|
||||
}
|
||||
$infos.='_'.$_SERVER['HTTP_USER_AGENT'];
|
||||
$infos.='_'.$_SERVER['HTTP_ACCEPT_LANGUAGE'];
|
||||
return sha1($infos);
|
||||
}
|
||||
|
||||
// Check that user/password is correct and init some SESSION variables.
|
||||
public static function login($login,$password,$login_test,$password_test,
|
||||
$pValues = array())
|
||||
{
|
||||
foreach ($pValues as $key => $value) {
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
if ($login==$login_test && $password==$password_test){
|
||||
// generate unique random number to sign forms (HMAC)
|
||||
$_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand());
|
||||
$_SESSION['info']=Session::_allInfos();
|
||||
$_SESSION['username']=$login;
|
||||
// Set session expiration.
|
||||
$_SESSION['expires_on']=time()+Session::$inactivity_timeout;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Force logout
|
||||
public static function logout()
|
||||
{
|
||||
unset($_SESSION['uid'],$_SESSION['info'],$_SESSION['expires_on'],$_SESSION['tokens']);
|
||||
}
|
||||
|
||||
// Make sure user is logged in.
|
||||
public static function isLogged()
|
||||
{
|
||||
if (!isset ($_SESSION['uid'])
|
||||
|| $_SESSION['info']!=Session::_allInfos()
|
||||
|| time()>=$_SESSION['expires_on']){
|
||||
Session::logout();
|
||||
return false;
|
||||
}
|
||||
// User accessed a page : Update his/her session expiration date.
|
||||
$_SESSION['expires_on']=time()+Session::$inactivity_timeout;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns a token.
|
||||
public static function getToken()
|
||||
{
|
||||
if (!isset($_SESSION['tokens'])){
|
||||
$_SESSION['tokens']=array();
|
||||
}
|
||||
// We generate a random string and store it on the server side.
|
||||
$rnd = sha1(uniqid('',true).'_'.mt_rand());
|
||||
$_SESSION['tokens'][$rnd]=1;
|
||||
return $rnd;
|
||||
}
|
||||
|
||||
// Tells if a token is ok. Using this function will destroy the token.
|
||||
// return true if token is ok.
|
||||
public static function isToken($token)
|
||||
{
|
||||
if (isset($_SESSION['tokens'][$token]))
|
||||
{
|
||||
unset($_SESSION['tokens'][$token]); // Token is used: destroy it.
|
||||
return true; // Token is ok.
|
||||
}
|
||||
return false; // Wrong token, or already used.
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* poche, a read it later open source system
|
||||
*
|
||||
* @category poche
|
||||
* @author Nicolas Lœuillet <nicolas@loeuillet.org>
|
||||
* @copyright 2013
|
||||
* @license http://www.wtfpl.net/ see COPYING file
|
||||
*/
|
||||
|
||||
if (!is_dir('db/')) {
|
||||
@mkdir('db/',0705);
|
||||
}
|
||||
|
||||
define ('DB_PATH', 'sqlite:./db/poche.sqlite');
|
||||
define ('ABS_PATH', 'assets/');
|
||||
define ('CONVERT_LINKS_FOOTNOTES', TRUE);
|
||||
define ('DOWNLOAD_PICTURES', TRUE);
|
||||
|
||||
include 'db.php';
|
||||
include 'functions.php';
|
||||
require_once 'Readability.php';
|
||||
require_once 'Encoding.php';
|
||||
require_once 'rain.tpl.class.php';
|
||||
require_once 'MyTool.class.php';
|
||||
require_once 'Session.class.php';
|
||||
|
||||
$db = new db(DB_PATH);
|
||||
|
||||
# initialisation de RainTPL
|
||||
raintpl::$tpl_dir = './tpl/';
|
||||
raintpl::$cache_dir = './cache/';
|
||||
raintpl::$base_url = get_poche_url();
|
||||
raintpl::configure('path_replace', false);
|
||||
raintpl::configure('debug', false);
|
||||
$tpl = new raintpl();
|
||||
22
inc/db.php
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* poche, a read it later open source system
|
||||
*
|
||||
* @category poche
|
||||
* @author Nicolas Lœuillet <nicolas@loeuillet.org>
|
||||
* @copyright 2013
|
||||
* @license http://www.wtfpl.net/ see COPYING file
|
||||
*/
|
||||
|
||||
class db {
|
||||
var $handle;
|
||||
function __construct($path) {
|
||||
$this->handle = new PDO($path);
|
||||
$this->handle->exec('CREATE TABLE IF NOT EXISTS "entries" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE , "title" VARCHAR, "url" VARCHAR UNIQUE , "is_read" INTEGER DEFAULT 0, "is_fav" INTEGER DEFAULT 0, "content" BLOB)');
|
||||
$this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
|
||||
public function getHandle() {
|
||||
return $this->handle;
|
||||
}
|
||||
}
|
||||
@ -1,428 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Permet de générer l'URL de poche pour le bookmarklet
|
||||
*/
|
||||
function get_poche_url()
|
||||
{
|
||||
$protocol = "http";
|
||||
if(isset($_SERVER['HTTPS'])) {
|
||||
if($_SERVER['HTTPS'] != "off" && $_SERVER['HTTPS'] != "") {
|
||||
$protocol = "https";
|
||||
}
|
||||
}
|
||||
|
||||
return $protocol . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
|
||||
// function define to retrieve url content
|
||||
function get_external_file($url)
|
||||
{
|
||||
$timeout = 15;
|
||||
// spoofing FireFox 18.0
|
||||
$useragent="Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0";
|
||||
|
||||
if (in_array ('curl', get_loaded_extensions())) {
|
||||
// Fetch feed from URL
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_HEADER, false);
|
||||
|
||||
// FeedBurner requires a proper USER-AGENT...
|
||||
curl_setopt($curl, CURL_HTTP_VERSION_1_1, true);
|
||||
curl_setopt($curl, CURLOPT_ENCODING, "gzip, deflate");
|
||||
curl_setopt($curl, CURLOPT_USERAGENT, $useragent);
|
||||
|
||||
$data = curl_exec($curl);
|
||||
|
||||
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
$httpcodeOK = isset($httpcode) and ($httpcode == 200 or $httpcode == 301);
|
||||
|
||||
curl_close($curl);
|
||||
} else {
|
||||
|
||||
// create http context and add timeout and user-agent
|
||||
$context = stream_context_create(array('http'=>array('timeout' => $timeout,'header'=> "User-Agent: ".$useragent,/*spoot Mozilla Firefox*/'follow_location' => true)));
|
||||
|
||||
// only download page lesser than 4MB
|
||||
$data = @file_get_contents($url, false, $context, -1, 4000000); // We download at most 4 MB from source.
|
||||
|
||||
if(isset($http_response_header) and isset($http_response_header[0])) {
|
||||
$httpcodeOK = isset($http_response_header) and isset($http_response_header[0]) and ((strpos($http_response_header[0], '200 OK') !== FALSE) or (strpos($http_response_header[0], '301 Moved Permanently') !== FALSE));
|
||||
}
|
||||
}
|
||||
|
||||
// if response is not empty and response is OK
|
||||
if (isset($data) and isset($httpcodeOK) and $httpcodeOK ) {
|
||||
|
||||
// take charset of page and get it
|
||||
preg_match('#<meta .*charset=.*>#Usi', $data, $meta);
|
||||
|
||||
// if meta tag is found
|
||||
if (!empty($meta[0])) {
|
||||
// retrieve encoding in $enc
|
||||
preg_match('#charset="?(.*)"#si', $meta[0], $enc);
|
||||
|
||||
// if charset is found set it otherwise, set it to utf-8
|
||||
$html_charset = (!empty($enc[1])) ? strtolower($enc[1]) : 'utf-8';
|
||||
|
||||
} else {
|
||||
$html_charset = 'utf-8';
|
||||
$enc[1] = '';
|
||||
}
|
||||
|
||||
// replace charset of url to charset of page
|
||||
$data = str_replace('charset='.$enc[1], 'charset='.$html_charset, $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Préparation de l'URL avec récupération du contenu avant insertion en base
|
||||
*/
|
||||
function prepare_url($url)
|
||||
{
|
||||
$parametres = array();
|
||||
$url = html_entity_decode(trim($url));
|
||||
|
||||
// We remove the annoying parameters added by FeedBurner and GoogleFeedProxy (?utm_source=...)
|
||||
// from shaarli, by sebsauvage
|
||||
$i=strpos($url,'&utm_source='); if ($i!==false) $url=substr($url,0,$i);
|
||||
$i=strpos($url,'?utm_source='); if ($i!==false) $url=substr($url,0,$i);
|
||||
$i=strpos($url,'#xtor=RSS-'); if ($i!==false) $url=substr($url,0,$i);
|
||||
|
||||
$title = $url;
|
||||
if (!preg_match('!^https?://!i', $url))
|
||||
$url = 'http://' . $url;
|
||||
|
||||
$html = Encoding::toUTF8(get_external_file($url,15));
|
||||
if (isset($html) and strlen($html) > 0)
|
||||
{
|
||||
$r = new Readability($html, $url);
|
||||
$r->convertLinksToFootnotes = CONVERT_LINKS_FOOTNOTES;
|
||||
if($r->init())
|
||||
{
|
||||
$content = $r->articleContent->innerHTML;
|
||||
$parametres['title'] = $r->articleTitle->innerHTML;
|
||||
$parametres['content'] = $content;
|
||||
return $parametres;
|
||||
}
|
||||
}
|
||||
|
||||
logm('error during url preparation');
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* On modifie les URLS des images dans le corps de l'article
|
||||
*/
|
||||
function filtre_picture($content, $url, $id)
|
||||
{
|
||||
$matches = array();
|
||||
preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER);
|
||||
foreach($matches as $i => $link)
|
||||
{
|
||||
$link[1] = trim($link[1]);
|
||||
if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1]) )
|
||||
{
|
||||
$absolute_path = get_absolute_link($link[2],$url);
|
||||
$filename = basename(parse_url($absolute_path, PHP_URL_PATH));
|
||||
$directory = create_assets_directory($id);
|
||||
$fullpath = $directory . '/' . $filename;
|
||||
download_pictures($absolute_path, $fullpath);
|
||||
$content = str_replace($matches[$i][2], $fullpath, $content);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le lien absolu
|
||||
*/
|
||||
function get_absolute_link($relative_link, $url)
|
||||
{
|
||||
/* return if already absolute URL */
|
||||
if (parse_url($relative_link, PHP_URL_SCHEME) != '') return $relative_link;
|
||||
|
||||
/* queries and anchors */
|
||||
if ($relative_link[0]=='#' || $relative_link[0]=='?') return $url . $relative_link;
|
||||
|
||||
/* parse base URL and convert to local variables:
|
||||
$scheme, $host, $path */
|
||||
extract(parse_url($url));
|
||||
|
||||
/* remove non-directory element from path */
|
||||
$path = preg_replace('#/[^/]*$#', '', $path);
|
||||
|
||||
/* destroy path if relative url points to root */
|
||||
if ($relative_link[0] == '/') $path = '';
|
||||
|
||||
/* dirty absolute URL */
|
||||
$abs = $host . $path . '/' . $relative_link;
|
||||
|
||||
/* replace '//' or '/./' or '/foo/../' with '/' */
|
||||
$re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
|
||||
for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {}
|
||||
|
||||
/* absolute URL is ready! */
|
||||
return $scheme.'://'.$abs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Téléchargement des images
|
||||
*/
|
||||
|
||||
function download_pictures($absolute_path, $fullpath)
|
||||
{
|
||||
$rawdata = get_external_file($absolute_path);
|
||||
|
||||
if(file_exists($fullpath)) {
|
||||
unlink($fullpath);
|
||||
}
|
||||
$fp = fopen($fullpath, 'x');
|
||||
fwrite($fp, $rawdata);
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un répertoire de médias pour l'article
|
||||
*/
|
||||
function create_assets_directory($id)
|
||||
{
|
||||
$assets_path = ABS_PATH;
|
||||
if(!is_dir($assets_path)) {
|
||||
mkdir($assets_path, 0705);
|
||||
}
|
||||
|
||||
$article_directory = $assets_path . $id;
|
||||
if(!is_dir($article_directory)) {
|
||||
mkdir($article_directory, 0705);
|
||||
}
|
||||
|
||||
return $article_directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppression du répertoire d'images
|
||||
*/
|
||||
function remove_directory($directory)
|
||||
{
|
||||
if(is_dir($directory)) {
|
||||
$files = array_diff(scandir($directory), array('.','..'));
|
||||
foreach ($files as $file) {
|
||||
(is_dir("$directory/$file")) ? remove_directory("$directory/$file") : unlink("$directory/$file");
|
||||
}
|
||||
return rmdir($directory);
|
||||
}
|
||||
}
|
||||
|
||||
function display_view($view, $id = 0, $full_head = 'yes')
|
||||
{
|
||||
global $tpl;
|
||||
|
||||
switch ($view)
|
||||
{
|
||||
case 'view':
|
||||
$entry = get_article($id);
|
||||
|
||||
if ($entry != NULL) {
|
||||
$tpl->assign('id', $entry[0]['id']);
|
||||
$tpl->assign('url', $entry[0]['url']);
|
||||
$tpl->assign('title', $entry[0]['title']);
|
||||
$tpl->assign('content', $entry[0]['content']);
|
||||
$tpl->assign('is_fav', $entry[0]['is_fav']);
|
||||
$tpl->assign('is_read', $entry[0]['is_read']);
|
||||
$tpl->assign('load_all_js', 0);
|
||||
$tpl->draw('view');
|
||||
}
|
||||
else {
|
||||
logm('error in view call : entry is NULL');
|
||||
}
|
||||
|
||||
logm('view link #' . $id);
|
||||
break;
|
||||
default: # home view
|
||||
$entries = get_entries($view);
|
||||
|
||||
$tpl->assign('entries', $entries);
|
||||
|
||||
if ($full_head == 'yes') {
|
||||
$tpl->assign('load_all_js', 1);
|
||||
$tpl->draw('head');
|
||||
$tpl->draw('home');
|
||||
}
|
||||
|
||||
$tpl->draw('entries');
|
||||
|
||||
if ($full_head == 'yes') {
|
||||
$tpl->draw('js');
|
||||
$tpl->draw('footer');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appel d'une action (mark as fav, archive, delete)
|
||||
*/
|
||||
function action_to_do($action, $url, $id = 0)
|
||||
{
|
||||
global $db;
|
||||
|
||||
switch ($action)
|
||||
{
|
||||
case 'add':
|
||||
if ($url == '')
|
||||
continue;
|
||||
|
||||
if($parametres_url = prepare_url($url)) {
|
||||
$sql_action = 'INSERT INTO entries ( url, title, content ) VALUES (?, ?, ?)';
|
||||
$params_action = array($url, $parametres_url['title'], $parametres_url['content']);
|
||||
}
|
||||
|
||||
logm('add link ' . $url);
|
||||
break;
|
||||
case 'delete':
|
||||
remove_directory(ABS_PATH . $id);
|
||||
$sql_action = "DELETE FROM entries WHERE id=?";
|
||||
$params_action = array($id);
|
||||
logm('delete link #' . $id);
|
||||
break;
|
||||
case 'toggle_fav' :
|
||||
$sql_action = "UPDATE entries SET is_fav=~is_fav WHERE id=?";
|
||||
$params_action = array($id);
|
||||
logm('mark as favorite link #' . $id);
|
||||
break;
|
||||
case 'toggle_archive' :
|
||||
$sql_action = "UPDATE entries SET is_read=~is_read WHERE id=?";
|
||||
$params_action = array($id);
|
||||
logm('archive link #' . $id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
# action query
|
||||
if (isset($sql_action))
|
||||
{
|
||||
$query = $db->getHandle()->prepare($sql_action);
|
||||
$query->execute($params_action);
|
||||
# if we add a link, we have to download pictures
|
||||
if ($action == 'add') {
|
||||
$last_id = $db->getHandle()->lastInsertId();
|
||||
if (DOWNLOAD_PICTURES) {
|
||||
$content = filtre_picture($parametres_url['content'], $url, $last_id);
|
||||
$sql_update = "UPDATE entries SET content=? WHERE id=?";
|
||||
$params_update = array($content, $last_id);
|
||||
$query_update = $db->getHandle()->prepare($sql_update);
|
||||
$query_update->execute($params_update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
logm('action query error : '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Détermine quels liens afficher : home, fav ou archives
|
||||
*/
|
||||
function get_entries($view)
|
||||
{
|
||||
global $db;
|
||||
|
||||
switch ($_SESSION['sort'])
|
||||
{
|
||||
case 'ia':
|
||||
$order = 'ORDER BY id';
|
||||
break;
|
||||
case 'id':
|
||||
$order = 'ORDER BY id DESC';
|
||||
break;
|
||||
case 'ta':
|
||||
$order = 'ORDER BY lower(title)';
|
||||
break;
|
||||
case 'td':
|
||||
$order = 'ORDER BY lower(title) DESC';
|
||||
break;
|
||||
default:
|
||||
$order = 'ORDER BY id';
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($view)
|
||||
{
|
||||
case 'archive':
|
||||
$sql = "SELECT * FROM entries WHERE is_read=? " . $order;
|
||||
$params = array(-1);
|
||||
break;
|
||||
case 'fav' :
|
||||
$sql = "SELECT * FROM entries WHERE is_fav=? " . $order;
|
||||
$params = array(-1);
|
||||
break;
|
||||
default:
|
||||
$sql = "SELECT * FROM entries WHERE is_read=? " . $order;
|
||||
$params = array(0);
|
||||
break;
|
||||
}
|
||||
|
||||
# view query
|
||||
try
|
||||
{
|
||||
$query = $db->getHandle()->prepare($sql);
|
||||
$query->execute($params);
|
||||
$entries = $query->fetchAll();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
logm('view query error : '.$e->getMessage());
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un article en fonction d'un ID
|
||||
*/
|
||||
function get_article($id)
|
||||
{
|
||||
global $db;
|
||||
|
||||
$entry = NULL;
|
||||
$sql = "SELECT * FROM entries WHERE id=?";
|
||||
$params = array(intval($id));
|
||||
|
||||
# view article query
|
||||
try
|
||||
{
|
||||
$query = $db->getHandle()->prepare($sql);
|
||||
$query->execute($params);
|
||||
$entry = $query->fetchAll();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
logm('get article query error : '.$e->getMessage());
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
function logm($message)
|
||||
{
|
||||
$t = strval(date('Y/m/d_H:i:s')).' - '.$_SERVER["REMOTE_ADDR"].' - '.strval($message)."\n";
|
||||
file_put_contents('./log.txt',$t,FILE_APPEND);
|
||||
}
|
||||
72
index.php
@ -1,72 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* poche, a read it later open source system
|
||||
*
|
||||
* @category poche
|
||||
* @author Nicolas Lœuillet <support@inthepoche.com>
|
||||
* @copyright 2013
|
||||
* @license http://www.wtfpl.net/ see COPYING file
|
||||
*/
|
||||
|
||||
include dirname(__FILE__).'/inc/config.php';
|
||||
|
||||
# initialize session
|
||||
Session::init();
|
||||
# XSRF protection with token
|
||||
if (!empty($_POST)) {
|
||||
if (!Session::isToken($_POST['token'])) {
|
||||
die('Wrong token.');
|
||||
}
|
||||
unset($_SESSION['tokens']);
|
||||
}
|
||||
|
||||
if (isset($_GET['login'])) {
|
||||
// Login
|
||||
if (!empty($_POST['login']) && !empty($_POST['password'])) {
|
||||
if (Session::login('poche', 'poche', $_POST['login'], $_POST['password'])) {
|
||||
logm('login successful');
|
||||
if (!empty($_POST['longlastingsession'])) {
|
||||
$_SESSION['longlastingsession'] = 31536000;
|
||||
$_SESSION['expires_on'] = time() + $_SESSION['longlastingsession'];
|
||||
session_set_cookie_params($_SESSION['longlastingsession']);
|
||||
} else {
|
||||
session_set_cookie_params(0); // when browser closes
|
||||
}
|
||||
session_regenerate_id(true);
|
||||
|
||||
MyTool::redirect();
|
||||
}
|
||||
logm('login failed');
|
||||
die("Login failed !");
|
||||
} else {
|
||||
logm('login failed');
|
||||
}
|
||||
}
|
||||
elseif (isset($_GET['logout'])) {
|
||||
logm('logout');
|
||||
Session::logout();
|
||||
MyTool::redirect();
|
||||
}
|
||||
|
||||
# Traitement des paramètres et déclenchement des actions
|
||||
$view = (isset ($_REQUEST['view'])) ? htmlentities($_REQUEST['view']) : 'index';
|
||||
$full_head = (isset ($_REQUEST['full_head'])) ? htmlentities($_REQUEST['full_head']) : 'yes';
|
||||
$action = (isset ($_REQUEST['action'])) ? htmlentities($_REQUEST['action']) : '';
|
||||
$_SESSION['sort'] = (isset ($_REQUEST['sort'])) ? htmlentities($_REQUEST['sort']) : 'id';
|
||||
$id = (isset ($_REQUEST['id'])) ? htmlspecialchars($_REQUEST['id']) : '';
|
||||
$url = (isset ($_GET['url'])) ? $_GET['url'] : '';
|
||||
$ref = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'];
|
||||
|
||||
$tpl->assign('isLogged', Session::isLogged());
|
||||
$tpl->assign('referer', $ref);
|
||||
$tpl->assign('view', $view);
|
||||
$tpl->assign('poche_url', get_poche_url());
|
||||
$tpl->assign('title', 'poche, a read it later open source system');
|
||||
|
||||
if (Session::isLogged()) {
|
||||
action_to_do($action, $url, $id);
|
||||
display_view($view, $id, $full_head);
|
||||
}
|
||||
else {
|
||||
$tpl->draw('login');
|
||||
}
|
||||
5
js/jquery-1.9.1.min.js
vendored
10
js/jquery.masonry.min.js
vendored
30
js/poche.js
@ -1,30 +0,0 @@
|
||||
function toggle_favorite(element, id) {
|
||||
$(element).toggleClass('fav-off');
|
||||
$.ajax ({
|
||||
url: "index.php?action=toggle_fav",
|
||||
data:{id:id}
|
||||
});
|
||||
}
|
||||
|
||||
function toggle_archive(element, id, view_article) {
|
||||
$(element).toggleClass('archive-off');
|
||||
$.ajax ({
|
||||
url: "index.php?action=toggle_archive",
|
||||
data:{id:id}
|
||||
});
|
||||
var obj = $('#entry-'+id);
|
||||
|
||||
// on vient de la vue de l'article, donc pas de gestion de grille
|
||||
if (view_article != 1) {
|
||||
$('#content').masonry('remove',obj);
|
||||
$('#content').masonry('reloadItems');
|
||||
$('#content').masonry('reload');
|
||||
}
|
||||
}
|
||||
|
||||
function sort_links(view, sort) {
|
||||
//$('#content').load('index.php', { view: view, sort: sort, full_head: 'no' } );
|
||||
$.get('index.php', { view: view, sort: sort, full_head: 'no' }, function(data) {
|
||||
$('#content').html(data);
|
||||
});
|
||||
}
|
||||
36
phpunit.xml.dist
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="app/bootstrap.php.cache"
|
||||
>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="wallabag Test Suite">
|
||||
<directory>./src/Wallabag/*Bundle/Tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<php>
|
||||
<server name="KERNEL_DIR" value="./app/" />
|
||||
</php>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src</directory>
|
||||
<exclude>
|
||||
<directory>./vendor</directory>
|
||||
<directory>./src/Wallabag/*Bundle/Resources</directory>
|
||||
<directory>./src/Wallabag/*Bundle/Tests</directory>
|
||||
<directory>./src/Wallabag/*Bundle/DataFixtures</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
7
src/.htaccess
Normal file
@ -0,0 +1,7 @@
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</IfModule>
|
||||
374
src/Wallabag/ApiBundle/Controller/WallabagRestController.php
Normal file
@ -0,0 +1,374 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\ApiBundle\Controller;
|
||||
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Wallabag\CoreBundle\Entity\Entry;
|
||||
use Wallabag\CoreBundle\Entity\Tag;
|
||||
use Hateoas\Configuration\Route;
|
||||
use Hateoas\Representation\Factory\PagerfantaFactory;
|
||||
|
||||
class WallabagRestController extends Controller
|
||||
{
|
||||
/**
|
||||
* @param Entry $entry
|
||||
* @param string $tags
|
||||
*/
|
||||
private function assignTagsToEntry(Entry $entry, $tags)
|
||||
{
|
||||
foreach (explode(',', $tags) as $label) {
|
||||
$label = trim($label);
|
||||
$tagEntity = $this
|
||||
->getDoctrine()
|
||||
->getRepository('WallabagCoreBundle:Tag')
|
||||
->findOneByLabel($label);
|
||||
|
||||
if (is_null($tagEntity)) {
|
||||
$tagEntity = new Tag($this->getUser());
|
||||
$tagEntity->setLabel($label);
|
||||
}
|
||||
|
||||
// only add the tag on the entry if the relation doesn't exist
|
||||
if (!$entry->getTags()->contains($tagEntity)) {
|
||||
$entry->addTag($tagEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve salt for a giver user.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* parameters={
|
||||
* {"name"="username", "dataType"="string", "required"=true, "description"="username"}
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSaltAction($username)
|
||||
{
|
||||
$user = $this
|
||||
->getDoctrine()
|
||||
->getRepository('WallabagCoreBundle:User')
|
||||
->findOneByUsername($username);
|
||||
|
||||
if (is_null($user)) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
return array($user->getSalt() ?: null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all entries. It could be filtered by many options.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* parameters={
|
||||
* {"name"="archive", "dataType"="boolean", "required"=false, "format"="true or false, all entries by default", "description"="filter by archived status."},
|
||||
* {"name"="star", "dataType"="boolean", "required"=false, "format"="true or false, all entries by default", "description"="filter by starred status."},
|
||||
* {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
|
||||
* {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
|
||||
* {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
|
||||
* {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
|
||||
* {"name"="tags", "dataType"="string", "required"=false, "format"="api%2Crest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
public function getEntriesAction(Request $request)
|
||||
{
|
||||
$isArchived = $request->query->get('archive');
|
||||
$isStarred = $request->query->get('star');
|
||||
$sort = $request->query->get('sort', 'created');
|
||||
$order = $request->query->get('order', 'desc');
|
||||
$page = (int) $request->query->get('page', 1);
|
||||
$perPage = (int) $request->query->get('perPage', 30);
|
||||
$tags = $request->query->get('tags', []);
|
||||
|
||||
$pager = $this
|
||||
->getDoctrine()
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order);
|
||||
|
||||
$pager->setCurrentPage($page);
|
||||
$pager->setMaxPerPage($perPage);
|
||||
|
||||
$pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
|
||||
$paginatedCollection = $pagerfantaFactory->createRepresentation(
|
||||
$pager,
|
||||
new Route('api_get_entries', [], $absolute = true)
|
||||
);
|
||||
|
||||
$json = $this->get('serializer')->serialize($paginatedCollection, 'json');
|
||||
|
||||
return $this->renderJsonResponse($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single entry.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* requirements={
|
||||
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
public function getEntryAction(Entry $entry)
|
||||
{
|
||||
$this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
|
||||
|
||||
$json = $this->get('serializer')->serialize($entry, 'json');
|
||||
|
||||
return $this->renderJsonResponse($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an entry.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* parameters={
|
||||
* {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
|
||||
* {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
|
||||
* {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
public function postEntriesAction(Request $request)
|
||||
{
|
||||
$url = $request->request->get('url');
|
||||
|
||||
$entry = $this->get('wallabag_core.content_proxy')->updateEntry(
|
||||
new Entry($this->getUser()),
|
||||
$url
|
||||
);
|
||||
|
||||
$tags = $request->request->get('tags', '');
|
||||
if (!empty($tags)) {
|
||||
$this->assignTagsToEntry($entry, $tags);
|
||||
}
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($entry);
|
||||
$em->flush();
|
||||
|
||||
$json = $this->get('serializer')->serialize($entry, 'json');
|
||||
|
||||
return $this->renderJsonResponse($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change several properties of an entry.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* requirements={
|
||||
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
|
||||
* },
|
||||
* parameters={
|
||||
* {"name"="title", "dataType"="string", "required"=false},
|
||||
* {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
|
||||
* {"name"="archive", "dataType"="boolean", "required"=false, "format"="true or false", "description"="archived the entry."},
|
||||
* {"name"="star", "dataType"="boolean", "required"=false, "format"="true or false", "description"="starred the entry."},
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
public function patchEntriesAction(Entry $entry, Request $request)
|
||||
{
|
||||
$this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
|
||||
|
||||
$title = $request->request->get('title');
|
||||
$isArchived = $request->request->get('is_archived');
|
||||
$isStarred = $request->request->get('is_starred');
|
||||
|
||||
if (!is_null($title)) {
|
||||
$entry->setTitle($title);
|
||||
}
|
||||
|
||||
if (!is_null($isArchived)) {
|
||||
$entry->setArchived($isArchived);
|
||||
}
|
||||
|
||||
if (!is_null($isStarred)) {
|
||||
$entry->setStarred($isStarred);
|
||||
}
|
||||
|
||||
$tags = $request->request->get('tags', '');
|
||||
if (!empty($tags)) {
|
||||
$this->assignTagsToEntry($entry, $tags);
|
||||
}
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->flush();
|
||||
|
||||
$json = $this->get('serializer')->serialize($entry, 'json');
|
||||
|
||||
return $this->renderJsonResponse($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete **permanently** an entry.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* requirements={
|
||||
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
public function deleteEntriesAction(Entry $entry)
|
||||
{
|
||||
$this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->remove($entry);
|
||||
$em->flush();
|
||||
|
||||
$json = $this->get('serializer')->serialize($entry, 'json');
|
||||
|
||||
return $this->renderJsonResponse($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all tags for an entry.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* requirements={
|
||||
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function getEntriesTagsAction(Entry $entry)
|
||||
{
|
||||
$this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
|
||||
|
||||
$json = $this->get('serializer')->serialize($entry->getTags(), 'json');
|
||||
|
||||
return $this->renderJsonResponse($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more tags to an entry.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* requirements={
|
||||
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
|
||||
* },
|
||||
* parameters={
|
||||
* {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function postEntriesTagsAction(Request $request, Entry $entry)
|
||||
{
|
||||
$this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
|
||||
|
||||
$tags = $request->request->get('tags', '');
|
||||
if (!empty($tags)) {
|
||||
$this->assignTagsToEntry($entry, $tags);
|
||||
}
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($entry);
|
||||
$em->flush();
|
||||
|
||||
$json = $this->get('serializer')->serialize($entry, 'json');
|
||||
|
||||
return $this->renderJsonResponse($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently remove one tag for an entry.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* requirements={
|
||||
* {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
|
||||
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
|
||||
{
|
||||
$this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
|
||||
|
||||
$entry->removeTag($tag);
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($entry);
|
||||
$em->flush();
|
||||
|
||||
$json = $this->get('serializer')->serialize($entry, 'json');
|
||||
|
||||
return $this->renderJsonResponse($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all tags.
|
||||
*
|
||||
* @ApiDoc()
|
||||
*/
|
||||
public function getTagsAction()
|
||||
{
|
||||
$json = $this->get('serializer')->serialize($this->getUser()->getTags(), 'json');
|
||||
|
||||
return $this->renderJsonResponse($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently remove one tag from **every** entry.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* requirements={
|
||||
* {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function deleteTagAction(Tag $tag)
|
||||
{
|
||||
$this->validateUserAccess($tag->getUser()->getId(), $this->getUser()->getId());
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->remove($tag);
|
||||
$em->flush();
|
||||
|
||||
$json = $this->get('serializer')->serialize($tag, 'json');
|
||||
|
||||
return $this->renderJsonResponse($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the first id is equal to the second one.
|
||||
* If not, throw exception. It means a user try to access information from an other user.
|
||||
*
|
||||
* @param int $requestUserId User id from the requested source
|
||||
* @param int $currentUserId User id from the retrieved source
|
||||
*/
|
||||
private function validateUserAccess($requestUserId, $currentUserId)
|
||||
{
|
||||
if ($requestUserId != $currentUserId) {
|
||||
throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$currentUserId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a JSON Response.
|
||||
* We don't use the Symfony JsonRespone, because it takes an array as parameter instead of a JSON string.
|
||||
*
|
||||
* @param string $json
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
private function renderJsonResponse($json)
|
||||
{
|
||||
return new Response($json, 200, array('application/json'));
|
||||
}
|
||||
}
|
||||
29
src/Wallabag/ApiBundle/DependencyInjection/Configuration.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\ApiBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
|
||||
/**
|
||||
* This is the class that validates and merges configuration from your app/config files.
|
||||
*
|
||||
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
|
||||
*/
|
||||
class Configuration implements ConfigurationInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigTreeBuilder()
|
||||
{
|
||||
$treeBuilder = new TreeBuilder();
|
||||
$rootNode = $treeBuilder->root('wallabag_api');
|
||||
|
||||
// Here you should define the parameters that are allowed to
|
||||
// configure your bundle. See the documentation linked above for
|
||||
// more information on that topic.
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\ApiBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
|
||||
|
||||
class WsseFactory implements SecurityFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
|
||||
{
|
||||
$providerId = 'security.authentication.provider.wsse.'.$id;
|
||||
$container
|
||||
->setDefinition($providerId, new DefinitionDecorator('wsse.security.authentication.provider'))
|
||||
->replaceArgument(0, new Reference($userProvider))
|
||||
;
|
||||
|
||||
$listenerId = 'security.authentication.listener.wsse.'.$id;
|
||||
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('wsse.security.authentication.listener'));
|
||||
|
||||
return array($providerId, $listenerId, $defaultEntryPoint);
|
||||
}
|
||||
|
||||
public function getPosition()
|
||||
{
|
||||
return 'pre_auth';
|
||||
}
|
||||
|
||||
public function getKey()
|
||||
{
|
||||
return 'wsse';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\ApiBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader;
|
||||
|
||||
class WallabagApiExtension extends Extension
|
||||
{
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
$configuration = new Configuration();
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
|
||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
|
||||
$loader->load('services.yml');
|
||||
}
|
||||
|
||||
public function getAlias()
|
||||
{
|
||||
return 'wallabag_api';
|
||||
}
|
||||
}
|
||||
0
src/Wallabag/ApiBundle/Resources/config/routing.yml
Normal file
4
src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
Normal file
@ -0,0 +1,4 @@
|
||||
entries:
|
||||
type: rest
|
||||
resource: "WallabagApiBundle:WallabagRest"
|
||||
name_prefix: api_
|
||||
12
src/Wallabag/ApiBundle/Resources/config/services.yml
Normal file
@ -0,0 +1,12 @@
|
||||
services:
|
||||
wsse.security.authentication.provider:
|
||||
class: Wallabag\ApiBundle\Security\Authentication\Provider\WsseProvider
|
||||
public: false
|
||||
arguments: ['', '%kernel.cache_dir%/security/nonces']
|
||||
|
||||
wsse.security.authentication.listener:
|
||||
class: Wallabag\ApiBundle\Security\Firewall\WsseListener
|
||||
public: false
|
||||
tags:
|
||||
- { name: monolog.logger, channel: wsse }
|
||||
arguments: ['@security.context', '@security.authentication.manager', '@logger']
|
||||
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\ApiBundle\Security\Authentication\Provider;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Wallabag\ApiBundle\Security\Authentication\Token\WsseUserToken;
|
||||
|
||||
class WsseProvider implements AuthenticationProviderInterface
|
||||
{
|
||||
private $userProvider;
|
||||
private $cacheDir;
|
||||
|
||||
public function __construct(UserProviderInterface $userProvider, $cacheDir)
|
||||
{
|
||||
$this->userProvider = $userProvider;
|
||||
$this->cacheDir = $cacheDir;
|
||||
|
||||
// If cache directory does not exist we create it
|
||||
if (!is_dir($this->cacheDir)) {
|
||||
mkdir($this->cacheDir, 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function authenticate(TokenInterface $token)
|
||||
{
|
||||
$user = $this->userProvider->loadUserByUsername($token->getUsername());
|
||||
|
||||
if (!$user) {
|
||||
throw new AuthenticationException('Bad credentials. Did you forgot your username?');
|
||||
}
|
||||
|
||||
if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) {
|
||||
$authenticatedToken = new WsseUserToken($user->getRoles());
|
||||
$authenticatedToken->setUser($user);
|
||||
|
||||
return $authenticatedToken;
|
||||
}
|
||||
|
||||
throw new AuthenticationException('The WSSE authentication failed.');
|
||||
}
|
||||
|
||||
protected function validateDigest($digest, $nonce, $created, $secret)
|
||||
{
|
||||
// Check created time is not in the future
|
||||
if (strtotime($created) > time()) {
|
||||
throw new AuthenticationException('Back to the future...');
|
||||
}
|
||||
|
||||
// Expire timestamp after 5 minutes
|
||||
if (time() - strtotime($created) > 300) {
|
||||
throw new AuthenticationException('Too late for this timestamp... Watch your watch.');
|
||||
}
|
||||
|
||||
// Validate nonce is unique within 5 minutes
|
||||
if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) {
|
||||
throw new NonceExpiredException('Previously used nonce detected');
|
||||
}
|
||||
|
||||
file_put_contents($this->cacheDir.'/'.$nonce, time());
|
||||
|
||||
// Validate Secret
|
||||
$expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
|
||||
|
||||
if ($digest !== $expected) {
|
||||
throw new AuthenticationException('Bad credentials ! Digest is not as expected.');
|
||||
}
|
||||
|
||||
return $digest === $expected;
|
||||
}
|
||||
|
||||
public function supports(TokenInterface $token)
|
||||
{
|
||||
return $token instanceof WsseUserToken;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\ApiBundle\Security\Authentication\Token;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
|
||||
|
||||
class WsseUserToken extends AbstractToken
|
||||
{
|
||||
public $created;
|
||||
public $digest;
|
||||
public $nonce;
|
||||
|
||||
public function __construct(array $roles = array())
|
||||
{
|
||||
parent::__construct($roles);
|
||||
|
||||
$this->setAuthenticated(count($roles) > 0);
|
||||
}
|
||||
|
||||
public function getCredentials()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
||||
62
src/Wallabag/ApiBundle/Security/Firewall/WsseListener.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\ApiBundle\Security\Firewall;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\SecurityContextInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
|
||||
use Wallabag\ApiBundle\Security\Authentication\Token\WsseUserToken;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class WsseListener implements ListenerInterface
|
||||
{
|
||||
protected $securityContext;
|
||||
protected $authenticationManager;
|
||||
protected $logger;
|
||||
|
||||
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger)
|
||||
{
|
||||
$this->securityContext = $securityContext;
|
||||
$this->authenticationManager = $authenticationManager;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function handle(GetResponseEvent $event)
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
|
||||
$wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
|
||||
if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = new WsseUserToken();
|
||||
$token->setUser($matches[1]);
|
||||
|
||||
$token->digest = $matches[2];
|
||||
$token->nonce = $matches[3];
|
||||
$token->created = $matches[4];
|
||||
|
||||
try {
|
||||
$authToken = $this->authenticationManager->authenticate($token);
|
||||
|
||||
$this->securityContext->setToken($authToken);
|
||||
|
||||
return;
|
||||
} catch (AuthenticationException $failed) {
|
||||
$failedMessage = 'WSSE Login failed for '.$token->getUsername().'. Why ? '.$failed->getMessage();
|
||||
$this->logger->err($failedMessage);
|
||||
|
||||
// Deny authentication with a '403 Forbidden' HTTP response
|
||||
$response = new Response();
|
||||
$response->setStatusCode(403);
|
||||
$response->setContent($failedMessage);
|
||||
$event->setResponse($response);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,435 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\ApiBundle\Tests\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class WallabagRestControllerTest extends WebTestCase
|
||||
{
|
||||
protected static $salt;
|
||||
|
||||
/**
|
||||
* Grab the salt once and store it to be available for all tests.
|
||||
*/
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
$client = self::createClient();
|
||||
|
||||
$user = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:User')
|
||||
->findOneByUsername('admin');
|
||||
|
||||
self::$salt = $user->getSalt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HTTP headers for authenticate user on API.
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function generateHeaders($username, $password)
|
||||
{
|
||||
$encryptedPassword = sha1($password.$username.self::$salt);
|
||||
$nonce = substr(md5(uniqid('nonce_', true)), 0, 16);
|
||||
|
||||
$now = new \DateTime('now', new \DateTimeZone('UTC'));
|
||||
$created = (string) $now->format('Y-m-d\TH:i:s\Z');
|
||||
$digest = base64_encode(sha1(base64_decode($nonce).$created.$encryptedPassword, true));
|
||||
|
||||
return array(
|
||||
'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"',
|
||||
'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="'.$username.'", PasswordDigest="'.$digest.'", Nonce="'.$nonce.'", Created="'.$created.'"',
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetSalt()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$client->request('GET', '/api/salts/admin.json');
|
||||
|
||||
$user = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:User')
|
||||
->findOneByUsername('admin');
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertArrayHasKey(0, $content);
|
||||
$this->assertEquals($user->getSalt(), $content[0]);
|
||||
|
||||
$client->request('GET', '/api/salts/notfound.json');
|
||||
$this->assertEquals(404, $client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public function testWithBadHeaders()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
|
||||
$entry = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->findOneByIsArchived(false);
|
||||
|
||||
if (!$entry) {
|
||||
$this->markTestSkipped('No content found in db.');
|
||||
}
|
||||
|
||||
$badHeaders = array(
|
||||
'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"',
|
||||
'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="admin", PasswordDigest="Wr0ngDig3st", Nonce="n0Nc3", Created="2015-01-01T13:37:00Z"',
|
||||
);
|
||||
|
||||
$client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $badHeaders);
|
||||
$this->assertEquals(403, $client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public function testGetOneEntry()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$entry = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->findOneBy(array('user' => 1, 'isArchived' => false));
|
||||
|
||||
if (!$entry) {
|
||||
$this->markTestSkipped('No content found in db.');
|
||||
}
|
||||
|
||||
$client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertEquals($entry->getTitle(), $content['title']);
|
||||
$this->assertEquals($entry->getUrl(), $content['url']);
|
||||
$this->assertCount(count($entry->getTags()), $content['tags']);
|
||||
|
||||
$this->assertTrue(
|
||||
$client->getResponse()->headers->contains(
|
||||
'Content-Type',
|
||||
'application/json'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetOneEntryWrongUser()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$entry = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->findOneBy(array('user' => 2, 'isArchived' => false));
|
||||
|
||||
if (!$entry) {
|
||||
$this->markTestSkipped('No content found in db.');
|
||||
}
|
||||
|
||||
$client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
|
||||
|
||||
$this->assertEquals(403, $client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public function testGetEntries()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$client->request('GET', '/api/entries', array(), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertGreaterThanOrEqual(1, count($content));
|
||||
$this->assertNotEmpty($content['_embedded']['items']);
|
||||
$this->assertGreaterThanOrEqual(1, $content['total']);
|
||||
$this->assertEquals(1, $content['page']);
|
||||
$this->assertGreaterThanOrEqual(1, $content['pages']);
|
||||
|
||||
$this->assertTrue(
|
||||
$client->getResponse()->headers->contains(
|
||||
'Content-Type',
|
||||
'application/json'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetStarredEntries()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$client->request('GET', '/api/entries', array('star' => 1, 'sort' => 'updated'), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertGreaterThanOrEqual(1, count($content));
|
||||
$this->assertNotEmpty($content['_embedded']['items']);
|
||||
$this->assertGreaterThanOrEqual(1, $content['total']);
|
||||
$this->assertEquals(1, $content['page']);
|
||||
$this->assertGreaterThanOrEqual(1, $content['pages']);
|
||||
|
||||
$this->assertTrue(
|
||||
$client->getResponse()->headers->contains(
|
||||
'Content-Type',
|
||||
'application/json'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetArchiveEntries()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$client->request('GET', '/api/entries', array('archive' => 1), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertGreaterThanOrEqual(1, count($content));
|
||||
$this->assertNotEmpty($content['_embedded']['items']);
|
||||
$this->assertGreaterThanOrEqual(1, $content['total']);
|
||||
$this->assertEquals(1, $content['page']);
|
||||
$this->assertGreaterThanOrEqual(1, $content['pages']);
|
||||
|
||||
$this->assertTrue(
|
||||
$client->getResponse()->headers->contains(
|
||||
'Content-Type',
|
||||
'application/json'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testDeleteEntry()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$entry = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->findOneByUser(1);
|
||||
|
||||
if (!$entry) {
|
||||
$this->markTestSkipped('No content found in db.');
|
||||
}
|
||||
|
||||
$client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertEquals($entry->getTitle(), $content['title']);
|
||||
$this->assertEquals($entry->getUrl(), $content['url']);
|
||||
|
||||
// We'll try to delete this entry again
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
|
||||
|
||||
$this->assertEquals(404, $client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public function testPostEntry()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$client->request('POST', '/api/entries.json', array(
|
||||
'url' => 'http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html',
|
||||
'tags' => 'google',
|
||||
), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertGreaterThan(0, $content['id']);
|
||||
$this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']);
|
||||
$this->assertEquals(false, $content['is_archived']);
|
||||
$this->assertEquals(false, $content['is_starred']);
|
||||
$this->assertCount(1, $content['tags']);
|
||||
}
|
||||
|
||||
public function testPatchEntry()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$entry = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->findOneByUser(1);
|
||||
|
||||
if (!$entry) {
|
||||
$this->markTestSkipped('No content found in db.');
|
||||
}
|
||||
|
||||
// hydrate the tags relations
|
||||
$nbTags = count($entry->getTags());
|
||||
|
||||
$client->request('PATCH', '/api/entries/'.$entry->getId().'.json', array(
|
||||
'title' => 'New awesome title',
|
||||
'tags' => 'new tag '.uniqid(),
|
||||
'star' => true,
|
||||
'archive' => false,
|
||||
), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertEquals($entry->getId(), $content['id']);
|
||||
$this->assertEquals($entry->getUrl(), $content['url']);
|
||||
$this->assertEquals('New awesome title', $content['title']);
|
||||
$this->assertGreaterThan($nbTags, count($content['tags']));
|
||||
}
|
||||
|
||||
public function testGetTagsEntry()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$entry = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->findOneWithTags(1);
|
||||
|
||||
$entry = $entry[0];
|
||||
|
||||
if (!$entry) {
|
||||
$this->markTestSkipped('No content found in db.');
|
||||
}
|
||||
|
||||
$tags = array();
|
||||
foreach ($entry->getTags() as $tag) {
|
||||
$tags[] = array('id' => $tag->getId(), 'label' => $tag->getLabel());
|
||||
}
|
||||
|
||||
$client->request('GET', '/api/entries/'.$entry->getId().'/tags', array(), array(), $headers);
|
||||
|
||||
$this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $client->getResponse()->getContent());
|
||||
}
|
||||
|
||||
public function testPostTagsOnEntry()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$entry = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->findOneByUser(1);
|
||||
|
||||
if (!$entry) {
|
||||
$this->markTestSkipped('No content found in db.');
|
||||
}
|
||||
|
||||
$nbTags = count($entry->getTags());
|
||||
|
||||
$newTags = 'tag1,tag2,tag3';
|
||||
|
||||
$client->request('POST', '/api/entries/'.$entry->getId().'/tags', array('tags' => $newTags), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertArrayHasKey('tags', $content);
|
||||
$this->assertEquals($nbTags + 3, count($content['tags']));
|
||||
|
||||
$entryDB = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->find($entry->getId());
|
||||
|
||||
$tagsInDB = array();
|
||||
foreach ($entryDB->getTags()->toArray() as $tag) {
|
||||
$tagsInDB[$tag->getId()] = $tag->getLabel();
|
||||
}
|
||||
|
||||
foreach (explode(',', $newTags) as $tag) {
|
||||
$this->assertContains($tag, $tagsInDB);
|
||||
}
|
||||
}
|
||||
|
||||
public function testDeleteOneTagEntrie()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$entry = $client->getContainer()
|
||||
->get('doctrine.orm.entity_manager')
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->findOneByUser(1);
|
||||
|
||||
if (!$entry) {
|
||||
$this->markTestSkipped('No content found in db.');
|
||||
}
|
||||
|
||||
// hydrate the tags relations
|
||||
$nbTags = count($entry->getTags());
|
||||
$tag = $entry->getTags()[0];
|
||||
|
||||
$client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json', array(), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertArrayHasKey('tags', $content);
|
||||
$this->assertEquals($nbTags - 1, count($content['tags']));
|
||||
}
|
||||
|
||||
public function testGetUserTags()
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$client->request('GET', '/api/tags.json', array(), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertGreaterThan(0, $content);
|
||||
$this->assertArrayHasKey('id', $content[0]);
|
||||
$this->assertArrayHasKey('label', $content[0]);
|
||||
|
||||
return end($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUserTags
|
||||
*/
|
||||
public function testDeleteUserTag($tag)
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$headers = $this->generateHeaders('admin', 'mypassword');
|
||||
|
||||
$client->request('DELETE', '/api/tags/'.$tag['id'].'.json', array(), array(), $headers);
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertArrayHasKey('label', $content);
|
||||
$this->assertEquals($tag['label'], $content['label']);
|
||||
}
|
||||
}
|
||||
18
src/Wallabag/ApiBundle/WallabagApiBundle.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\ApiBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Wallabag\ApiBundle\DependencyInjection\Security\Factory\WsseFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
class WallabagApiBundle extends Bundle
|
||||
{
|
||||
public function build(ContainerBuilder $container)
|
||||
{
|
||||
parent::build($container);
|
||||
|
||||
$extension = $container->getExtension('security');
|
||||
$extension->addSecurityListenerFactory(new WsseFactory());
|
||||
}
|
||||
}
|
||||
315
src/Wallabag/CoreBundle/Command/InstallCommand.php
Normal file
@ -0,0 +1,315 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
use Wallabag\CoreBundle\Entity\User;
|
||||
use Wallabag\CoreBundle\Entity\Config;
|
||||
|
||||
class InstallCommand extends ContainerAwareCommand
|
||||
{
|
||||
/**
|
||||
* @var InputInterface
|
||||
*/
|
||||
protected $defaultInput;
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $defaultOutput;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('wallabag:install')
|
||||
->setDescription('Wallabag installer.')
|
||||
->addOption(
|
||||
'reset',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Reset current database'
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->defaultInput = $input;
|
||||
$this->defaultOutput = $output;
|
||||
|
||||
$output->writeln('<info>Installing Wallabag...</info>');
|
||||
$output->writeln('');
|
||||
|
||||
$this
|
||||
->checkRequirements()
|
||||
->setupDatabase()
|
||||
->setupAdmin()
|
||||
->setupAsset()
|
||||
;
|
||||
|
||||
$output->writeln('<info>Wallabag has been successfully installed.</info>');
|
||||
$output->writeln('<comment>Just execute `php app/console server:run` for using wallabag: http://localhost:8000</comment>');
|
||||
}
|
||||
|
||||
protected function checkRequirements()
|
||||
{
|
||||
$this->defaultOutput->writeln('<info><comment>Step 1 of 4.</comment> Checking system requirements.</info>');
|
||||
|
||||
$fulfilled = true;
|
||||
|
||||
// @TODO: find a better way to check requirements
|
||||
$label = '<comment>PCRE</comment>';
|
||||
if (extension_loaded('pcre')) {
|
||||
$status = '<info>OK!</info>';
|
||||
$help = '';
|
||||
} else {
|
||||
$fulfilled = false;
|
||||
$status = '<error>ERROR!</error>';
|
||||
$help = 'You should enabled PCRE extension';
|
||||
}
|
||||
$rows[] = array($label, $status, $help);
|
||||
|
||||
$label = '<comment>DOM</comment>';
|
||||
if (extension_loaded('DOM')) {
|
||||
$status = '<info>OK!</info>';
|
||||
$help = '';
|
||||
} else {
|
||||
$fulfilled = false;
|
||||
$status = '<error>ERROR!</error>';
|
||||
$help = 'You should enabled DOM extension';
|
||||
}
|
||||
$rows[] = array($label, $status, $help);
|
||||
|
||||
$this->getHelper('table')
|
||||
->setHeaders(array('Checked', 'Status', 'Recommendation'))
|
||||
->setRows($rows)
|
||||
->render($this->defaultOutput);
|
||||
|
||||
if (!$fulfilled) {
|
||||
throw new \RuntimeException('Some system requirements are not fulfilled. Please check output messages and fix them.');
|
||||
} else {
|
||||
$this->defaultOutput->writeln('<info>Success! Your system can run Wallabag properly.</info>');
|
||||
}
|
||||
|
||||
$this->defaultOutput->writeln('');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function setupDatabase()
|
||||
{
|
||||
$this->defaultOutput->writeln('<info><comment>Step 2 of 4.</comment> Setting up database.</info>');
|
||||
|
||||
// user want to reset everything? Don't care about what is already here
|
||||
if (true === $this->defaultInput->getOption('reset')) {
|
||||
$this->defaultOutput->writeln('Droping database, creating database and schema');
|
||||
|
||||
$this
|
||||
->runCommand('doctrine:database:drop', array('--force' => true))
|
||||
->runCommand('doctrine:database:create')
|
||||
->runCommand('doctrine:schema:create')
|
||||
;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!$this->isDatabasePresent()) {
|
||||
$this->defaultOutput->writeln('Creating database and schema, clearing the cache');
|
||||
|
||||
$this
|
||||
->runCommand('doctrine:database:create')
|
||||
->runCommand('doctrine:schema:create')
|
||||
->runCommand('cache:clear')
|
||||
;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$dialog = $this->getHelper('dialog');
|
||||
|
||||
if ($dialog->askConfirmation($this->defaultOutput, '<question>It appears that your database already exists. Would you like to reset it? (y/N)</question> ', false)) {
|
||||
$this->defaultOutput->writeln('Droping database, creating database and schema');
|
||||
|
||||
$this
|
||||
->runCommand('doctrine:database:drop', array('--force' => true))
|
||||
->runCommand('doctrine:database:create')
|
||||
->runCommand('doctrine:schema:create')
|
||||
;
|
||||
} elseif ($this->isSchemaPresent()) {
|
||||
if ($dialog->askConfirmation($this->defaultOutput, '<question>Seems like your database contains schema. Do you want to reset it? (y/N)</question> ', false)) {
|
||||
$this->defaultOutput->writeln('Droping schema and creating schema');
|
||||
|
||||
$this
|
||||
->runCommand('doctrine:schema:drop', array('--force' => true))
|
||||
->runCommand('doctrine:schema:create')
|
||||
;
|
||||
}
|
||||
} else {
|
||||
$this->defaultOutput->writeln('Creating schema');
|
||||
|
||||
$this
|
||||
->runCommand('doctrine:schema:create')
|
||||
;
|
||||
}
|
||||
|
||||
$this->defaultOutput->writeln('Clearing the cache');
|
||||
$this->runCommand('cache:clear');
|
||||
|
||||
/*
|
||||
if ($this->getHelperSet()->get('dialog')->askConfirmation($this->defaultOutput, '<question>Load fixtures (Y/N)?</question>', false)) {
|
||||
$doctrineConfig = $this->getContainer()->get('doctrine.orm.entity_manager')->getConnection()->getConfiguration();
|
||||
$logger = $doctrineConfig->getSQLLogger();
|
||||
// speed up fixture load
|
||||
$doctrineConfig->setSQLLogger(null);
|
||||
$this->runCommand('doctrine:fixtures:load');
|
||||
$doctrineConfig->setSQLLogger($logger);
|
||||
}
|
||||
*/
|
||||
|
||||
$this->defaultOutput->writeln('');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function setupAdmin()
|
||||
{
|
||||
$this->defaultOutput->writeln('<info><comment>Step 3 of 4.</comment> Administration setup.</info>');
|
||||
|
||||
$dialog = $this->getHelperSet()->get('dialog');
|
||||
|
||||
if (false === $dialog->askConfirmation($this->defaultOutput, '<question>Would you like to create a new user ? (y/N)</question>', true)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
|
||||
|
||||
$user = new User();
|
||||
$user->setUsername($dialog->ask($this->defaultOutput, '<question>Username</question> <comment>(default: wallabag)</comment> :', 'wallabag'));
|
||||
$user->setPassword($dialog->ask($this->defaultOutput, '<question>Password</question> <comment>(default: wallabag)</comment> :', 'wallabag'));
|
||||
$user->setEmail($dialog->ask($this->defaultOutput, '<question>Email:</question>', ''));
|
||||
$user->setEnabled(true);
|
||||
|
||||
$em->persist($user);
|
||||
|
||||
$config = new Config($user);
|
||||
$config->setTheme($this->getContainer()->getParameter('theme'));
|
||||
$config->setItemsPerPage($this->getContainer()->getParameter('items_on_page'));
|
||||
$config->setRssLimit($this->getContainer()->getParameter('rss_limit'));
|
||||
$config->setLanguage($this->getContainer()->getParameter('language'));
|
||||
|
||||
$em->persist($config);
|
||||
|
||||
$em->flush();
|
||||
|
||||
$this->defaultOutput->writeln('');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function setupAsset()
|
||||
{
|
||||
$this->defaultOutput->writeln('<info><comment>Step 4 of 4.</comment> Installing assets.</info>');
|
||||
|
||||
$this
|
||||
->runCommand('assets:install')
|
||||
->runCommand('assetic:dump')
|
||||
;
|
||||
|
||||
$this->defaultOutput->writeln('');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command.
|
||||
*
|
||||
* @param string $command
|
||||
* @param array $parameters Parameters to this command (usually 'force' => true)
|
||||
*/
|
||||
protected function runCommand($command, $parameters = array())
|
||||
{
|
||||
$parameters = array_merge(
|
||||
array('command' => $command),
|
||||
$parameters,
|
||||
array(
|
||||
'--no-debug' => true,
|
||||
'--env' => $this->defaultInput->getOption('env') ?: 'dev',
|
||||
)
|
||||
);
|
||||
|
||||
if ($this->defaultInput->getOption('no-interaction')) {
|
||||
$parameters = array_merge($parameters, array('--no-interaction' => true));
|
||||
}
|
||||
|
||||
$this->getApplication()->setAutoExit(false);
|
||||
$exitCode = $this->getApplication()->run(new ArrayInput($parameters), new NullOutput());
|
||||
|
||||
if (0 !== $exitCode) {
|
||||
$this->getApplication()->setAutoExit(true);
|
||||
|
||||
$errorMessage = sprintf('The command "%s" terminated with an error code: %u.', $command, $exitCode);
|
||||
$this->defaultOutput->writeln("<error>$errorMessage</error>");
|
||||
$exception = new \Exception($errorMessage, $exitCode);
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
// PDO does not always close the connection after Doctrine commands.
|
||||
// See https://github.com/symfony/symfony/issues/11750.
|
||||
$this->getContainer()->get('doctrine')->getManager()->getConnection()->close();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the database already exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isDatabasePresent()
|
||||
{
|
||||
$connection = $this->getContainer()->get('doctrine')->getManager()->getConnection();
|
||||
$databaseName = $connection->getDatabase();
|
||||
|
||||
try {
|
||||
$schemaManager = $connection->getSchemaManager();
|
||||
} catch (\Exception $exception) {
|
||||
if (false !== strpos($exception->getMessage(), sprintf("Unknown database '%s'", $databaseName))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
// custom verification for sqlite, since `getListDatabasesSQL` doesn't work for sqlite
|
||||
if ('sqlite' == $schemaManager->getDatabasePlatform()->getName()) {
|
||||
$params = $this->getContainer()->get('doctrine.dbal.default_connection')->getParams();
|
||||
|
||||
if (isset($params['path']) && file_exists($params['path'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($databaseName, $schemaManager->listDatabases());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the schema is already created.
|
||||
* If we found at least oen table, it means the schema exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isSchemaPresent()
|
||||
{
|
||||
$schemaManager = $this->getContainer()->get('doctrine')->getManager()->getConnection()->getSchemaManager();
|
||||
|
||||
return count($schemaManager->listTableNames()) > 0 ? true : false;
|
||||
}
|
||||
}
|
||||
181
src/Wallabag/CoreBundle/Controller/ConfigController.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Controller;
|
||||
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Wallabag\CoreBundle\Entity\Config;
|
||||
use Wallabag\CoreBundle\Entity\User;
|
||||
use Wallabag\CoreBundle\Form\Type\ChangePasswordType;
|
||||
use Wallabag\CoreBundle\Form\Type\UserInformationType;
|
||||
use Wallabag\CoreBundle\Form\Type\NewUserType;
|
||||
use Wallabag\CoreBundle\Form\Type\RssType;
|
||||
use Wallabag\CoreBundle\Tools\Utils;
|
||||
|
||||
class ConfigController extends Controller
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @Route("/config", name="config")
|
||||
*/
|
||||
public function indexAction(Request $request)
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$config = $this->getConfig();
|
||||
$user = $this->getUser();
|
||||
|
||||
// handle basic config detail (this form is defined as a service)
|
||||
$configForm = $this->createForm('config', $config);
|
||||
$configForm->handleRequest($request);
|
||||
|
||||
if ($configForm->isValid()) {
|
||||
$em->persist($config);
|
||||
$em->flush();
|
||||
|
||||
// switch active theme
|
||||
$activeTheme = $this->get('liip_theme.active_theme');
|
||||
$activeTheme->setName($config->getTheme());
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'Config saved'
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('config'));
|
||||
}
|
||||
|
||||
// handle changing password
|
||||
$pwdForm = $this->createForm(new ChangePasswordType());
|
||||
$pwdForm->handleRequest($request);
|
||||
|
||||
if ($pwdForm->isValid()) {
|
||||
$user->setPassword($pwdForm->get('new_password')->getData());
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'Password updated'
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('config'));
|
||||
}
|
||||
|
||||
// handle changing user information
|
||||
$userForm = $this->createForm(new UserInformationType(), $user, array('validation_groups' => array('Profile')));
|
||||
$userForm->handleRequest($request);
|
||||
|
||||
if ($userForm->isValid()) {
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'Information updated'
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('config'));
|
||||
}
|
||||
|
||||
// handle rss information
|
||||
$rssForm = $this->createForm(new RssType(), $config);
|
||||
$rssForm->handleRequest($request);
|
||||
|
||||
if ($rssForm->isValid()) {
|
||||
$em->persist($config);
|
||||
$em->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'RSS information updated'
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('config'));
|
||||
}
|
||||
|
||||
// handle adding new user
|
||||
$newUser = new User();
|
||||
$newUserForm = $this->createForm(new NewUserType(), $newUser, array('validation_groups' => array('Profile')));
|
||||
$newUserForm->handleRequest($request);
|
||||
|
||||
if ($newUserForm->isValid()) {
|
||||
$em->persist($newUser);
|
||||
|
||||
$config = new Config($newUser);
|
||||
$config->setTheme($this->container->getParameter('theme'));
|
||||
$config->setItemsPerPage($this->container->getParameter('items_on_page'));
|
||||
$config->setRssLimit($this->container->getParameter('rss_limit'));
|
||||
$config->setLanguage($this->container->getParameter('language'));
|
||||
|
||||
$em->persist($config);
|
||||
|
||||
$em->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
sprintf('User "%s" added', $newUser->getUsername())
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('config'));
|
||||
}
|
||||
|
||||
return $this->render('WallabagCoreBundle:Config:index.html.twig', array(
|
||||
'form' => array(
|
||||
'config' => $configForm->createView(),
|
||||
'rss' => $rssForm->createView(),
|
||||
'pwd' => $pwdForm->createView(),
|
||||
'user' => $userForm->createView(),
|
||||
'new_user' => $newUserForm->createView(),
|
||||
),
|
||||
'rss' => array(
|
||||
'username' => $user->getUsername(),
|
||||
'token' => $config->getRssToken(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @Route("/generate-token", name="generate_token")
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function generateTokenAction(Request $request)
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
$config->setRssToken(Utils::generateToken());
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($config);
|
||||
$em->flush();
|
||||
|
||||
if ($request->isXmlHttpRequest()) {
|
||||
return new JsonResponse(array('token' => $config->getRssToken()));
|
||||
}
|
||||
|
||||
return $request->headers->get('referer') ? $this->redirect($request->headers->get('referer')) : $this->redirectToRoute('config');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve config for the current user.
|
||||
* If no config were found, create a new one.
|
||||
*
|
||||
* @return Wallabag\CoreBundle\Entity\Config
|
||||
*/
|
||||
private function getConfig()
|
||||
{
|
||||
$config = $this->getDoctrine()
|
||||
->getRepository('WallabagCoreBundle:Config')
|
||||
->findOneByUser($this->getUser());
|
||||
|
||||
if (!$config) {
|
||||
$config = new Config($this->getUser());
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
326
src/Wallabag/CoreBundle/Controller/EntryController.php
Normal file
@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Controller;
|
||||
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Wallabag\CoreBundle\Entity\Entry;
|
||||
use Wallabag\CoreBundle\Form\Type\NewEntryType;
|
||||
use Wallabag\CoreBundle\Form\Type\EditEntryType;
|
||||
use Wallabag\CoreBundle\Filter\EntryFilterType;
|
||||
use Pagerfanta\Adapter\DoctrineORMAdapter;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
|
||||
class EntryController extends Controller
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @Route("/new-entry", name="new_entry")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function addEntryFormAction(Request $request)
|
||||
{
|
||||
$entry = new Entry($this->getUser());
|
||||
|
||||
$form = $this->createForm(new NewEntryType(), $entry);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($entry);
|
||||
$em->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'Entry saved'
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('homepage'));
|
||||
}
|
||||
|
||||
return $this->render('WallabagCoreBundle:Entry:new_form.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @Route("/new", name="new")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function addEntryAction(Request $request)
|
||||
{
|
||||
return $this->render('WallabagCoreBundle:Entry:new.html.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit an entry content.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @Route("/edit/{id}", requirements={"id" = "\d+"}, name="edit")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function editEntryAction(Request $request, Entry $entry)
|
||||
{
|
||||
$this->checkUserAction($entry);
|
||||
|
||||
$form = $this->createForm(new EditEntryType(), $entry);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($entry);
|
||||
$em->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'Entry updated'
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('view', array('id' => $entry->getId())));
|
||||
}
|
||||
|
||||
return $this->render('WallabagCoreBundle:Entry:edit.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows all entries for current user.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $page
|
||||
*
|
||||
* @Route("/all/list/{page}", name="all", defaults={"page" = "1"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function showAllAction(Request $request, $page)
|
||||
{
|
||||
return $this->showEntries('all', $request, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows unread entries for current user.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $page
|
||||
*
|
||||
* @Route("/unread/list/{page}", name="unread", defaults={"page" = "1"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function showUnreadAction(Request $request, $page)
|
||||
{
|
||||
return $this->showEntries('unread', $request, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows read entries for current user.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $page
|
||||
*
|
||||
* @Route("/archive/list/{page}", name="archive", defaults={"page" = "1"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function showArchiveAction(Request $request, $page)
|
||||
{
|
||||
return $this->showEntries('archive', $request, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows starred entries for current user.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $page
|
||||
*
|
||||
* @Route("/starred/list/{page}", name="starred", defaults={"page" = "1"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function showStarredAction(Request $request, $page)
|
||||
{
|
||||
return $this->showEntries('starred', $request, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Global method to retrieve entries depending on the given type
|
||||
* It returns the response to be send.
|
||||
*
|
||||
* @param string $type Entries type: unread, starred or archive
|
||||
* @param Request $request
|
||||
* @param int $page
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
private function showEntries($type, Request $request, $page)
|
||||
{
|
||||
$repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
|
||||
|
||||
switch ($type) {
|
||||
case 'starred':
|
||||
$qb = $repository->getBuilderForStarredByUser($this->getUser()->getId());
|
||||
break;
|
||||
|
||||
case 'archive':
|
||||
$qb = $repository->getBuilderForArchiveByUser($this->getUser()->getId());
|
||||
break;
|
||||
|
||||
case 'unread':
|
||||
$qb = $repository->getBuilderForUnreadByUser($this->getUser()->getId());
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
$qb = $repository->getBuilderForAllByUser($this->getUser()->getId());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('Type "%s" is not implemented.', $type));
|
||||
}
|
||||
|
||||
$form = $this->get('form.factory')->create(new EntryFilterType());
|
||||
|
||||
if ($request->query->has($form->getName())) {
|
||||
// manually bind values from the request
|
||||
$form->submit($request->query->get($form->getName()));
|
||||
|
||||
// build the query from the given form object
|
||||
$this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $qb);
|
||||
}
|
||||
|
||||
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery());
|
||||
$entries = new Pagerfanta($pagerAdapter);
|
||||
|
||||
$entries->setMaxPerPage($this->getUser()->getConfig()->getItemsPerPage());
|
||||
$entries->setCurrentPage($page);
|
||||
|
||||
return $this->render(
|
||||
'WallabagCoreBundle:Entry:entries.html.twig',
|
||||
array(
|
||||
'form' => $form->createView(),
|
||||
'entries' => $entries,
|
||||
'currentPage' => $page,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows entry content.
|
||||
*
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @Route("/view/{id}", requirements={"id" = "\d+"}, name="view")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function viewAction(Entry $entry)
|
||||
{
|
||||
$this->checkUserAction($entry);
|
||||
|
||||
return $this->render(
|
||||
'WallabagCoreBundle:Entry:entry.html.twig',
|
||||
array('entry' => $entry)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes read status for an entry.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @Route("/archive/{id}", requirements={"id" = "\d+"}, name="archive_entry")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function toggleArchiveAction(Request $request, Entry $entry)
|
||||
{
|
||||
$this->checkUserAction($entry);
|
||||
|
||||
$entry->toggleArchive();
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'Entry '.($entry->isArchived() ? 'archived' : 'unarchived')
|
||||
);
|
||||
|
||||
return $this->redirect($request->headers->get('referer'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes favorite status for an entry.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @Route("/star/{id}", requirements={"id" = "\d+"}, name="star_entry")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function toggleStarAction(Request $request, Entry $entry)
|
||||
{
|
||||
$this->checkUserAction($entry);
|
||||
|
||||
$entry->toggleStar();
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'Entry '.($entry->isStarred() ? 'starred' : 'unstarred')
|
||||
);
|
||||
|
||||
return $this->redirect($request->headers->get('referer'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes entry and redirect to the homepage.
|
||||
*
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @Route("/delete/{id}", requirements={"id" = "\d+"}, name="delete_entry")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function deleteEntryAction(Entry $entry)
|
||||
{
|
||||
$this->checkUserAction($entry);
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->remove($entry);
|
||||
$em->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'Entry deleted'
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('homepage'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the logged user can manage the given entry.
|
||||
*
|
||||
* @param Entry $entry
|
||||
*/
|
||||
private function checkUserAction(Entry $entry)
|
||||
{
|
||||
if ($this->getUser()->getId() != $entry->getUser()->getId()) {
|
||||
throw $this->createAccessDeniedException('You can not access this entry.');
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/Wallabag/CoreBundle/Controller/RssController.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Controller;
|
||||
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Wallabag\CoreBundle\Entity\User;
|
||||
use Wallabag\CoreBundle\Entity\Entry;
|
||||
use Pagerfanta\Adapter\DoctrineORMAdapter;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
|
||||
class RssController extends Controller
|
||||
{
|
||||
/**
|
||||
* Shows unread entries for current user.
|
||||
*
|
||||
* @Route("/{username}/{token}/unread.xml", name="unread_rss", defaults={"_format"="xml"})
|
||||
* @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function showUnreadAction(User $user)
|
||||
{
|
||||
return $this->showEntries('unread', $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows read entries for current user.
|
||||
*
|
||||
* @Route("/{username}/{token}/archive.xml", name="archive_rss")
|
||||
* @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function showArchiveAction(User $user)
|
||||
{
|
||||
return $this->showEntries('archive', $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows starred entries for current user.
|
||||
*
|
||||
* @Route("/{username}/{token}/starred.xml", name="starred_rss")
|
||||
* @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function showStarredAction(User $user)
|
||||
{
|
||||
return $this->showEntries('starred', $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Global method to retrieve entries depending on the given type
|
||||
* It returns the response to be send.
|
||||
*
|
||||
* @param string $type Entries type: unread, starred or archive
|
||||
* @param User $user
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
private function showEntries($type, User $user)
|
||||
{
|
||||
$repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
|
||||
|
||||
switch ($type) {
|
||||
case 'starred':
|
||||
$qb = $repository->getBuilderForStarredByUser($user->getId());
|
||||
break;
|
||||
|
||||
case 'archive':
|
||||
$qb = $repository->getBuilderForArchiveByUser($user->getId());
|
||||
break;
|
||||
|
||||
case 'unread':
|
||||
$qb = $repository->getBuilderForUnreadByUser($user->getId());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('Type "%s" is not implemented.', $type));
|
||||
}
|
||||
|
||||
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery());
|
||||
$entries = new Pagerfanta($pagerAdapter);
|
||||
|
||||
$perPage = $user->getConfig()->getRssLimit() ?: $this->container->getParameter('rss_limit');
|
||||
$entries->setMaxPerPage($perPage);
|
||||
|
||||
return $this->render('WallabagCoreBundle:Entry:entries.xml.twig', array(
|
||||
'type' => $type,
|
||||
'entries' => $entries,
|
||||
));
|
||||
}
|
||||
}
|
||||
153
src/Wallabag/CoreBundle/Controller/SecurityController.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Controller;
|
||||
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\SecurityContext;
|
||||
use Wallabag\CoreBundle\Form\Type\ResetPasswordType;
|
||||
|
||||
class SecurityController extends Controller
|
||||
{
|
||||
public function loginAction(Request $request)
|
||||
{
|
||||
$session = $request->getSession();
|
||||
// get the login error if there is one
|
||||
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
|
||||
$error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
|
||||
} else {
|
||||
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
|
||||
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
|
||||
}
|
||||
|
||||
return $this->render('WallabagCoreBundle:Security:login.html.twig', array(
|
||||
// last username entered by the user
|
||||
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
|
||||
'error' => $error,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Request forgot password: show form.
|
||||
*
|
||||
* @Route("/forgot-password", name="forgot_password")
|
||||
*
|
||||
* @Method({"GET", "POST"})
|
||||
*/
|
||||
public function forgotPasswordAction(Request $request)
|
||||
{
|
||||
$form = $this->createForm('forgot_password');
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$user = $this->getDoctrine()->getRepository('WallabagCoreBundle:User')->findOneByEmail($form->get('email')->getData());
|
||||
|
||||
// generate "hard" token
|
||||
$user->setConfirmationToken(rtrim(strtr(base64_encode(hash('sha256', uniqid(mt_rand(), true), true)), '+/', '-_'), '='));
|
||||
$user->setPasswordRequestedAt(new \DateTime());
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
$message = \Swift_Message::newInstance()
|
||||
->setSubject('Reset Password')
|
||||
->setFrom($this->container->getParameter('from_email'))
|
||||
->setTo($user->getEmail())
|
||||
->setBody($this->renderView('WallabagCoreBundle:Mail:forgotPassword.txt.twig', array(
|
||||
'username' => $user->getUsername(),
|
||||
'confirmationUrl' => $this->generateUrl('forgot_password_reset', array('token' => $user->getConfirmationToken()), true),
|
||||
)))
|
||||
;
|
||||
$this->get('mailer')->send($message);
|
||||
|
||||
return $this->redirect($this->generateUrl('forgot_password_check_email',
|
||||
array('email' => $this->getObfuscatedEmail($user->getEmail()))
|
||||
));
|
||||
}
|
||||
|
||||
return $this->render('WallabagCoreBundle:Security:forgotPassword.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the user to check his email provider.
|
||||
*
|
||||
* @Route("/forgot-password/check-email", name="forgot_password_check_email")
|
||||
*
|
||||
* @Method({"GET"})
|
||||
*/
|
||||
public function checkEmailAction(Request $request)
|
||||
{
|
||||
$email = $request->query->get('email');
|
||||
|
||||
if (empty($email)) {
|
||||
// the user does not come from the forgotPassword action
|
||||
return $this->redirect($this->generateUrl('forgot_password'));
|
||||
}
|
||||
|
||||
return $this->render('WallabagCoreBundle:Security:checkEmail.html.twig', array(
|
||||
'email' => $email,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset user password.
|
||||
*
|
||||
* @Route("/forgot-password/{token}", name="forgot_password_reset")
|
||||
*
|
||||
* @Method({"GET", "POST"})
|
||||
*/
|
||||
public function resetAction(Request $request, $token)
|
||||
{
|
||||
$user = $this->getDoctrine()->getRepository('WallabagCoreBundle:User')->findOneByConfirmationToken($token);
|
||||
|
||||
if (null === $user) {
|
||||
throw $this->createNotFoundException(sprintf('No user found with token "%s"', $token));
|
||||
}
|
||||
|
||||
$form = $this->createForm(new ResetPasswordType());
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$user->setPassword($form->get('new_password')->getData());
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'The password has been reset successfully'
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('login'));
|
||||
}
|
||||
|
||||
return $this->render('WallabagCoreBundle:Security:reset.html.twig', array(
|
||||
'token' => $token,
|
||||
'form' => $form->createView(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the truncated email displayed when requesting the resetting.
|
||||
*
|
||||
* Keeping only the part following @ in the address.
|
||||
*
|
||||
* @param string $email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getObfuscatedEmail($email)
|
||||
{
|
||||
if (false !== $pos = strpos($email, '@')) {
|
||||
$email = '...'.substr($email, $pos);
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
}
|
||||
31
src/Wallabag/CoreBundle/Controller/StaticController.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Controller;
|
||||
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
|
||||
class StaticController extends Controller
|
||||
{
|
||||
/**
|
||||
* @Route("/howto", name="howto")
|
||||
*/
|
||||
public function howtoAction()
|
||||
{
|
||||
return $this->render(
|
||||
'WallabagCoreBundle:Static:howto.html.twig',
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/about", name="about")
|
||||
*/
|
||||
public function aboutAction()
|
||||
{
|
||||
return $this->render(
|
||||
'WallabagCoreBundle:Static:about.html.twig',
|
||||
array()
|
||||
);
|
||||
}
|
||||
}
|
||||
80
src/Wallabag/CoreBundle/Controller/TagController.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Controller;
|
||||
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Wallabag\CoreBundle\Form\Type\NewTagType;
|
||||
use Wallabag\CoreBundle\Entity\Tag;
|
||||
use Wallabag\CoreBundle\Entity\Entry;
|
||||
|
||||
class TagController extends Controller
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @Route("/new-tag/{entry}", requirements={"entry" = "\d+"}, name="new_tag")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function addTagFormAction(Request $request, Entry $entry)
|
||||
{
|
||||
$tag = new Tag($this->getUser());
|
||||
$form = $this->createForm(new NewTagType(), $tag);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$existingTag = $this->getDoctrine()
|
||||
->getRepository('WallabagCoreBundle:Tag')
|
||||
->findOneByLabelAndUserId($tag->getLabel(), $this->getUser()->getId());
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
if (is_null($existingTag)) {
|
||||
$entry->addTag($tag);
|
||||
$em->persist($tag);
|
||||
} else {
|
||||
if (!$existingTag->hasEntry($entry)) {
|
||||
$entry->addTag($existingTag);
|
||||
$em->persist($existingTag);
|
||||
}
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'Tag added'
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('view', array('id' => $entry->getId())));
|
||||
}
|
||||
|
||||
return $this->render('WallabagCoreBundle:Tag:new_form.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
'entry' => $entry,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows tags for current user.
|
||||
*
|
||||
* @Route("/tag/list", name="tag")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function showTagAction()
|
||||
{
|
||||
$tags = $this->getDoctrine()
|
||||
->getRepository('WallabagCoreBundle:Tag')
|
||||
->findTags($this->getUser()->getId());
|
||||
|
||||
return $this->render(
|
||||
'WallabagCoreBundle:Tag:tags.html.twig',
|
||||
array(
|
||||
'tags' => $tags,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
45
src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\DataFixtures\ORM;
|
||||
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Wallabag\CoreBundle\Entity\Config;
|
||||
|
||||
class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$adminConfig = new Config($this->getReference('admin-user'));
|
||||
$adminConfig->setTheme('material');
|
||||
$adminConfig->setItemsPerPage(30);
|
||||
$adminConfig->setLanguage('en_US');
|
||||
|
||||
$manager->persist($adminConfig);
|
||||
|
||||
$this->addReference('admin-config', $adminConfig);
|
||||
|
||||
$bobConfig = new Config($this->getReference('bob-user'));
|
||||
$bobConfig->setTheme('default');
|
||||
$bobConfig->setItemsPerPage(10);
|
||||
$bobConfig->setLanguage('fr_FR');
|
||||
|
||||
$manager->persist($bobConfig);
|
||||
|
||||
$this->addReference('bob-config', $bobConfig);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
}
|
||||
100
src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\DataFixtures\ORM;
|
||||
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Wallabag\CoreBundle\Entity\Entry;
|
||||
use Wallabag\CoreBundle\Entity\Tag;
|
||||
|
||||
class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$entry1 = new Entry($this->getReference('admin-user'));
|
||||
$entry1->setUrl('http://0.0.0.0');
|
||||
$entry1->setTitle('test title entry1');
|
||||
$entry1->setContent('This is my content /o/');
|
||||
|
||||
$manager->persist($entry1);
|
||||
|
||||
$this->addReference('entry1', $entry1);
|
||||
|
||||
$entry2 = new Entry($this->getReference('admin-user'));
|
||||
$entry2->setUrl('http://0.0.0.0');
|
||||
$entry2->setTitle('test title entry2');
|
||||
$entry2->setContent('This is my content /o/');
|
||||
|
||||
$manager->persist($entry2);
|
||||
|
||||
$this->addReference('entry2', $entry2);
|
||||
|
||||
$entry3 = new Entry($this->getReference('bob-user'));
|
||||
$entry3->setUrl('http://0.0.0.0');
|
||||
$entry3->setTitle('test title entry3');
|
||||
$entry3->setContent('This is my content /o/');
|
||||
|
||||
$tag1 = new Tag($this->getReference('bob-user'));
|
||||
$tag1->setLabel('foo');
|
||||
$tag2 = new Tag($this->getReference('bob-user'));
|
||||
$tag2->setLabel('bar');
|
||||
|
||||
$entry3->addTag($tag1);
|
||||
$entry3->addTag($tag2);
|
||||
|
||||
$manager->persist($entry3);
|
||||
|
||||
$this->addReference('entry3', $entry3);
|
||||
|
||||
$entry4 = new Entry($this->getReference('admin-user'));
|
||||
$entry4->setUrl('http://0.0.0.0');
|
||||
$entry4->setTitle('test title entry4');
|
||||
$entry4->setContent('This is my content /o/');
|
||||
|
||||
$tag1 = new Tag($this->getReference('admin-user'));
|
||||
$tag1->setLabel('foo');
|
||||
$tag2 = new Tag($this->getReference('admin-user'));
|
||||
$tag2->setLabel('bar');
|
||||
|
||||
$entry4->addTag($tag1);
|
||||
$entry4->addTag($tag2);
|
||||
|
||||
$manager->persist($entry4);
|
||||
|
||||
$this->addReference('entry4', $entry4);
|
||||
|
||||
$entry5 = new Entry($this->getReference('admin-user'));
|
||||
$entry5->setUrl('http://0.0.0.0');
|
||||
$entry5->setTitle('test title entry5');
|
||||
$entry5->setContent('This is my content /o/');
|
||||
$entry5->setStarred(true);
|
||||
|
||||
$manager->persist($entry5);
|
||||
|
||||
$this->addReference('entry5', $entry5);
|
||||
|
||||
$entry6 = new Entry($this->getReference('admin-user'));
|
||||
$entry6->setUrl('http://0.0.0.0');
|
||||
$entry6->setTitle('test title entry6');
|
||||
$entry6->setContent('This is my content /o/');
|
||||
$entry6->setArchived(true);
|
||||
|
||||
$manager->persist($entry6);
|
||||
|
||||
$this->addReference('entry6', $entry6);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 30;
|
||||
}
|
||||
}
|
||||
49
src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\DataFixtures\ORM;
|
||||
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Wallabag\CoreBundle\Entity\User;
|
||||
|
||||
class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$userAdmin = new User();
|
||||
$userAdmin->setName('Big boss');
|
||||
$userAdmin->setEmail('bigboss@wallabag.org');
|
||||
$userAdmin->setUsername('admin');
|
||||
$userAdmin->setPassword('mypassword');
|
||||
$userAdmin->setEnabled(true);
|
||||
|
||||
$manager->persist($userAdmin);
|
||||
|
||||
$this->addReference('admin-user', $userAdmin);
|
||||
|
||||
$bobUser = new User();
|
||||
$bobUser->setName('Bobby');
|
||||
$bobUser->setEmail('bobby@wallabag.org');
|
||||
$bobUser->setUsername('bob');
|
||||
$bobUser->setPassword('mypassword');
|
||||
$bobUser->setEnabled(true);
|
||||
|
||||
$manager->persist($bobUser);
|
||||
|
||||
$this->addReference('bob-user', $bobUser);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader;
|
||||
|
||||
class WallabagCoreExtension extends Extension
|
||||
{
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
|
||||
$loader->load('services.yml');
|
||||
}
|
||||
|
||||
public function getAlias()
|
||||
{
|
||||
return 'wallabag_core';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Doctrine\Mapping;
|
||||
|
||||
use Doctrine\ORM\Mapping\NamingStrategy;
|
||||
|
||||
/**
|
||||
* Puts a prefix to each table.
|
||||
*
|
||||
* Solution from :
|
||||
* - http://stackoverflow.com/a/23860613/569101
|
||||
* - http://doctrine-orm.readthedocs.org/en/latest/reference/namingstrategy.html
|
||||
*/
|
||||
class PrefixedNamingStrategy implements NamingStrategy
|
||||
{
|
||||
protected $prefix = '';
|
||||
|
||||
public function __construct($prefix)
|
||||
{
|
||||
$this->prefix = (string) $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function classToTableName($className)
|
||||
{
|
||||
return strtolower($this->prefix.substr($className, strrpos($className, '\\') + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function propertyToColumnName($propertyName, $className = null)
|
||||
{
|
||||
return $propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function referenceColumnName()
|
||||
{
|
||||
return 'id';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function joinColumnName($propertyName)
|
||||
{
|
||||
return $propertyName.'_'.$this->referenceColumnName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null)
|
||||
{
|
||||
// for join table we don't want to have both table concatenated AND prefixed
|
||||
// we just want the whole table to prefixed once
|
||||
// ie: not "wallabag_entry_wallabag_tag" but "wallabag_entry_tag"
|
||||
$target = substr($targetEntity, strrpos($targetEntity, '\\') + 1);
|
||||
|
||||
return strtolower($this->classToTableName($sourceEntity).'_'.$target);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function joinKeyColumnName($entityName, $referencedColumnName = null)
|
||||
{
|
||||
return strtolower($this->classToTableName($entityName).'_'.($referencedColumnName ?: $this->referenceColumnName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null)
|
||||
{
|
||||
return $propertyName.'_'.$embeddedColumnName;
|
||||
}
|
||||
}
|
||||