Compare commits
871 Commits
release-20
...
master
Author | SHA1 | Date |
---|---|---|
Andrew Dolgov | 9f734c9050 | 3 years ago |
Andrew Dolgov | 3b70d1f622 | 3 years ago |
Andrew Dolgov | 2a5c2be6cd | 3 years ago |
Andrew Dolgov | 41245da8a6 | 3 years ago |
Andrew Dolgov | 6fe0751038 | 3 years ago |
Andrew Dolgov | 377e0b812c | 3 years ago |
Andrew Dolgov | dd30825b94 | 3 years ago |
fox | d1ffe6d6cf | 3 years ago |
Philip Klempin | aead30a041 | 3 years ago |
Andrew Dolgov | a936e80630 | 3 years ago |
Andrew Dolgov | 9e7e0e84d7 | 3 years ago |
Andrew Dolgov | c6f5902cbc | 3 years ago |
Andrew Dolgov | a9646b9574 | 3 years ago |
Andrew Dolgov | 145fc31625 | 3 years ago |
Andrew Dolgov | 949e2ab4d2 | 3 years ago |
Andrew Dolgov | 8ed927dbd2 | 3 years ago |
Andrew Dolgov | 78ff7770d1 | 3 years ago |
fox | 012a9fdee3 | 3 years ago |
Jon Schewe | e44f0cb937 | 3 years ago |
Andrew Dolgov | 36e174750e | 3 years ago |
fox | b8f82ca12f | 3 years ago |
jmechnich | e8f9567d79 | 3 years ago |
Andrew Dolgov | a1173ab06a | 3 years ago |
Andrew Dolgov | 2c931df77c | 3 years ago |
Andrew Dolgov | 5c60254474 | 3 years ago |
Andrew Dolgov | 0808123179 | 3 years ago |
fox | 5ed108dce4 | 3 years ago |
fox | 28eafa2bcd | 3 years ago |
wn_ | 23b4152c9e | 3 years ago |
wn_ | 992e9cd9e3 | 3 years ago |
Nils Gotzhein | b6b6771d8d | 3 years ago |
fox | a73e3bec45 | 3 years ago |
wn_ | cf0ec06b8c | 3 years ago |
Andrew Dolgov | 73d14338ab | 3 years ago |
Andrew Dolgov | 9669bb94de | 3 years ago |
Andrew Dolgov | 44c5d0feba | 3 years ago |
fox | cd26dbe64c | 3 years ago |
Philip Klempin | 14d57d9a14 | 3 years ago |
fox | 7bd9572aa1 | 3 years ago |
Philip Klempin | 1d4d3bc49c | 3 years ago |
Weblate | f16fc3bf41 | 3 years ago |
Andrew Dolgov | 93a5ba55d3 | 3 years ago |
Andrew Dolgov | 800ebd6373 | 3 years ago |
Andrew Dolgov | 69f261c41d | 3 years ago |
Andrew Dolgov | e9c062a189 | 3 years ago |
fox | 34807bacd4 | 3 years ago |
Andrew Dolgov | 4e9c3500fb | 3 years ago |
Philip Klempin | b3bedd0a94 | 3 years ago |
Andrew Dolgov | 8ed8a10965 | 3 years ago |
Andrew Dolgov | 92c78beb90 | 3 years ago |
Andrew Dolgov | 8e1281b41e | 3 years ago |
Andrew Dolgov | 326850845d | 3 years ago |
Andrew Dolgov | dff479af64 | 3 years ago |
Andrew Dolgov | d09a64d6f9 | 3 years ago |
Andrew Dolgov | 8574532b7f | 3 years ago |
Andrew Dolgov | 4795c4a2a9 | 3 years ago |
Eike | 0f51350e9f | 3 years ago |
Andrew Dolgov | 295fc1f88a | 3 years ago |
Andrew Dolgov | 2adf364c2c | 3 years ago |
Andrew Dolgov | 9f6237a1b8 | 3 years ago |
Andrew Dolgov | 57cd8acfc9 | 3 years ago |
fox | 77031575ab | 3 years ago |
linkai | 983655165e | 3 years ago |
kdan | 6c06a26649 | 3 years ago |
Andrew Dolgov | f423874e05 | 3 years ago |
Andrew Dolgov | b5a559a1a7 | 3 years ago |
Andrew Dolgov | e3c4724dc1 | 3 years ago |
fox | 82749ee7a7 | 3 years ago |
Jacek Tomasiak | 0c38dc8456 | 3 years ago |
kdan | 2ccf0e50a2 | 3 years ago |
linkai | acf0e0d266 | 3 years ago |
Andrew Dolgov | b2f888e386 | 3 years ago |
Andrew Dolgov | fea59de26b | 3 years ago |
Andrew Dolgov | 86300a0ca8 | 3 years ago |
Andrew Dolgov | d11718c89c | 3 years ago |
linkai | 0574675ed6 | 3 years ago |
Andrew Dolgov | e8f78181f1 | 3 years ago |
Andrew Dolgov | 88a7130d79 | 3 years ago |
Andrew Dolgov | e8e4fc641e | 3 years ago |
Andrew Dolgov | df145c8064 | 3 years ago |
Andrew Dolgov | c6befcddb7 | 3 years ago |
Andrew Dolgov | 5a71426ea5 | 3 years ago |
Andrew Dolgov | b3d45a4a5d | 3 years ago |
Andrew Dolgov | cc634ba91d | 3 years ago |
Eike | b12072fef9 | 3 years ago |
fox | a6c5dda7e0 | 3 years ago |
Oliver Haucke | cfd9e6b53b | 3 years ago |
fox | 0f61675cd0 | 3 years ago |
Rodney Stromlund | c18383d1ea | 3 years ago |
Andrew Dolgov | 3e22368962 | 3 years ago |
Andrew Dolgov | eadaaebd58 | 3 years ago |
fox | 61b4a678ea | 3 years ago |
Cyb10101 | c15c1dfb0b | 3 years ago |
Andrew Dolgov | a61348e2b7 | 3 years ago |
Andrew Dolgov | a5af15cfe9 | 3 years ago |
Andrew Dolgov | 49ef15f11d | 3 years ago |
Andrew Dolgov | c0fba62fa0 | 3 years ago |
Andrew Dolgov | 0acd33abe3 | 3 years ago |
Андрій Жук | 0294297ccc | 3 years ago |
Piotr | a4f82fddf4 | 3 years ago |
Glandos | 49b25a6430 | 3 years ago |
fox | f2f2b6d1f4 | 3 years ago |
wn_ | 5d5c034a90 | 3 years ago |
fox | 0b82afabd5 | 3 years ago |
wn_ | 2ed5a79e64 | 3 years ago |
Andrew Dolgov | 8c32ed76df | 3 years ago |
Andrew Dolgov | ceb8179ccc | 3 years ago |
Andrew Dolgov | 19c277391e | 3 years ago |
Andrew Dolgov | 58ab641fea | 3 years ago |
Andrew Dolgov | be2d1602bd | 3 years ago |
Andrew Dolgov | e3c51b0e6c | 3 years ago |
Andrew Dolgov | c34a4c85bd | 3 years ago |
Andrew Dolgov | 0f6644880a | 3 years ago |
Andrew Dolgov | 98251022d4 | 3 years ago |
Andrew Dolgov | 334a361e79 | 3 years ago |
Andrew Dolgov | d275134f26 | 3 years ago |
Andrew Dolgov | 2e6d48ead7 | 3 years ago |
Andrew Dolgov | 43744412f4 | 3 years ago |
Andrew Dolgov | ef5d6b9b78 | 3 years ago |
Andrew Dolgov | e12a6ca540 | 3 years ago |
Andrew Dolgov | 1f5adf1600 | 3 years ago |
Andrew Dolgov | 68299c914b | 3 years ago |
fox | 56f7b25e85 | 3 years ago |
wn_ | 711e8e70e0 | 3 years ago |
Andrew Dolgov | 718c9f07fa | 3 years ago |
Andrew Dolgov | 43ea36d030 | 3 years ago |
fox | ce9955c6ff | 3 years ago |
wn_ | cd52ca80ab | 3 years ago |
wn_ | baf3ecd4cf | 3 years ago |
Andrew Dolgov | 968270ed48 | 3 years ago |
wn_ | 541a07250c | 3 years ago |
wn_ | f057c124d1 | 3 years ago |
wn_ | 7ea48f7a4b | 3 years ago |
wn_ | b6ae280446 | 3 years ago |
Andrew Dolgov | db0315e596 | 3 years ago |
Andrew Dolgov | 88534a8ae4 | 3 years ago |
Andrew Dolgov | 82bed1e651 | 3 years ago |
fox | 7c2b473d12 | 3 years ago |
wn_ | 401b22666d | 3 years ago |
Andrew Dolgov | 0f5fd9ea13 | 3 years ago |
Andrew Dolgov | 32c080bec0 | 3 years ago |
Andrew Dolgov | 166517240e | 3 years ago |
Andrew Dolgov | 7a1e1630d8 | 3 years ago |
Andrew Dolgov | 92f859add2 | 3 years ago |
Andrew Dolgov | a0e41f41a4 | 3 years ago |
Andrew Dolgov | 7ec8a6cad0 | 3 years ago |
Andrew Dolgov | d9ba403927 | 3 years ago |
Andrew Dolgov | 44b274b6d4 | 3 years ago |
fox | c134aa387d | 3 years ago |
Andrew Dolgov | f81a579386 | 3 years ago |
JustAMacUser | 39bbbef030 | 3 years ago |
Andrew Dolgov | 1870fe172b | 3 years ago |
Andrew Dolgov | b23ba3e236 | 3 years ago |
Andrew Dolgov | a0ce7f556b | 3 years ago |
Andrew Dolgov | 1664b87821 | 3 years ago |
Andrew Dolgov | 13210747d8 | 3 years ago |
Andrew Dolgov | 15b39a534d | 3 years ago |
Andrew Dolgov | f7ee812db2 | 3 years ago |
fox | 1b71cd9f44 | 3 years ago |
Jordan Galby | 3d801b1ac5 | 3 years ago |
Andrew Dolgov | 2f402d598d | 3 years ago |
Andrew Dolgov | 38ab3ef11c | 3 years ago |
Andrew Dolgov | 4ddcd54e8d | 3 years ago |
fox | 06ebb81eb8 | 3 years ago |
Philip Klempin | fa22e1bc35 | 3 years ago |
Andrew Dolgov | 4e81233ac9 | 3 years ago |
Andrew Dolgov | fcce1c443e | 3 years ago |
Andrew Dolgov | bc73bf0f67 | 3 years ago |
Andrew Dolgov | efde6d36c7 | 3 years ago |
Andrew Dolgov | e85cba5958 | 3 years ago |
Marek Pavelka | f9d366f028 | 3 years ago |
Andrew Dolgov | 52d1a5c96d | 3 years ago |
Andrew Dolgov | 580eccd3da | 3 years ago |
Andrew Dolgov | b9268fcc88 | 3 years ago |
Andrew Dolgov | 96d89fe912 | 3 years ago |
Andrew Dolgov | bd1630d278 | 3 years ago |
Andrew Dolgov | 76a6060ca3 | 3 years ago |
Andrew Dolgov | 4949e1a590 | 3 years ago |
Andrew Dolgov | 146b1e0feb | 3 years ago |
Andrew Dolgov | 6e0474a7c8 | 3 years ago |
Andrew Dolgov | f67d2623b7 | 3 years ago |
Andrew Dolgov | a4da2f1e62 | 3 years ago |
Andrew Dolgov | 755072de91 | 3 years ago |
Andrew Dolgov | de47082ca6 | 3 years ago |
Andrew Dolgov | f9a381ecca | 3 years ago |
Andrew Dolgov | 27ab16b6dc | 3 years ago |
Andrew Dolgov | 324aef9f6f | 3 years ago |
Andrew Dolgov | 03361dda34 | 3 years ago |
Andrew Dolgov | 24e64b8c78 | 3 years ago |
Andrew Dolgov | 21e0b28cf1 | 3 years ago |
Andrew Dolgov | f9a9fcbb56 | 3 years ago |
Andrew Dolgov | 3e1b3e8ea8 | 3 years ago |
Andrew Dolgov | 143617afb1 | 3 years ago |
Andrew Dolgov | 84fe383ed4 | 3 years ago |
Andrew Dolgov | 16726ec07f | 3 years ago |
Andrew Dolgov | a0dd5baa51 | 3 years ago |
Andrew Dolgov | 5e738ec278 | 3 years ago |
Andrew Dolgov | 71b12857e0 | 3 years ago |
Andrew Dolgov | 353ee40378 | 3 years ago |
Andrew Dolgov | 668b0ac7a6 | 3 years ago |
Andrew Dolgov | fb89c3bad0 | 3 years ago |
Andrew Dolgov | 5bc47451e1 | 3 years ago |
Andrew Dolgov | 36ad46e60d | 3 years ago |
Ptsa Daniel | 02af69328f | 3 years ago |
Dario Di Ludovico | 4aa595e3ba | 3 years ago |
Andrew Dolgov | 96031c80bf | 3 years ago |
Andrew Dolgov | e826c9e055 | 3 years ago |
Andrew Dolgov | f58879c1dc | 3 years ago |
Andrew Dolgov | bdc72e5b63 | 3 years ago |
Andrew Dolgov | df9c389cbf | 3 years ago |
Andrew Dolgov | b6033d0bbd | 3 years ago |
Andrew Dolgov | 0b93d8d013 | 3 years ago |
Andrew Dolgov | 089fa5ec26 | 3 years ago |
Andrew Dolgov | 87d13e826f | 3 years ago |
Andrew Dolgov | eba8c97f36 | 3 years ago |
Andrew Dolgov | a3ab4020bf | 3 years ago |
Andrew Dolgov | ddfa39015e | 3 years ago |
Andrew Dolgov | 6ec66d0ce5 | 3 years ago |
Andrew Dolgov | f804caec90 | 3 years ago |
Andrew Dolgov | ae7b87bca9 | 3 years ago |
Andrew Dolgov | 2160a86092 | 3 years ago |
Andrew Dolgov | 4e1c78374f | 3 years ago |
Andrew Dolgov | 74391ec30a | 3 years ago |
Andrew Dolgov | dd9d017f7d | 3 years ago |
Andrew Dolgov | 9b321be270 | 3 years ago |
Andrew Dolgov | 4fe2e6bbf1 | 3 years ago |
Andrew Dolgov | b1961163b8 | 3 years ago |
Andrew Dolgov | bc7cb76379 | 3 years ago |
Weblate | 63ca6333a5 | 3 years ago |
Andrew Dolgov | ea25c49eb9 | 3 years ago |
Andrew Dolgov | fe4c284858 | 3 years ago |
fox | 9b2267510b | 3 years ago |
wn_ | fed5158ec5 | 3 years ago |
Andrew Dolgov | cfb4882591 | 3 years ago |
Andrew Dolgov | 28dd255c30 | 3 years ago |
Andrew Dolgov | bfeaf4d6a4 | 3 years ago |
Andrew Dolgov | ef03f8188c | 3 years ago |
Andrew Dolgov | c26f58d8a5 | 3 years ago |
Andrew Dolgov | a125e8540d | 3 years ago |
Andrew Dolgov | 1fb7125f90 | 3 years ago |
Andrew Dolgov | 46b77fc6b7 | 3 years ago |
Andrew Dolgov | 5db6939dc9 | 3 years ago |
Andrew Dolgov | 603cc89638 | 3 years ago |
Andrew Dolgov | f4d0e7bb6d | 3 years ago |
Andrew Dolgov | 72c04123d4 | 3 years ago |
Andrew Dolgov | 518e677a6b | 3 years ago |
Andrew Dolgov | 266c8a6eae | 3 years ago |
Andrew Dolgov | ac6a59914b | 3 years ago |
Andrew Dolgov | ffb93d72ac | 3 years ago |
Andrew Dolgov | 773bad1490 | 3 years ago |
Andrew Dolgov | 1dcc36deca | 3 years ago |
Andrew Dolgov | c036c27ec7 | 3 years ago |
Andrew Dolgov | 17650775d2 | 3 years ago |
Andrew Dolgov | 5bb8714839 | 3 years ago |
Andrew Dolgov | 77b5201b7d | 3 years ago |
Andrew Dolgov | d6fd0d5462 | 3 years ago |
Andrew Dolgov | 39c570a9ff | 3 years ago |
Andrew Dolgov | b27218a1e3 | 3 years ago |
fox | cb81b784e8 | 3 years ago |
Andrew Dolgov | 1d9fa2a42e | 3 years ago |
ltGuillaume | 825e362f0e | 3 years ago |
Andrew Dolgov | 7b0b5b55c7 | 3 years ago |
Andrew Dolgov | 68ecf52594 | 3 years ago |
Andrew Dolgov | 473ea6255c | 3 years ago |
Andrew Dolgov | 217922899d | 3 years ago |
Andrew Dolgov | 270f0c3132 | 3 years ago |
Andrew Dolgov | 63651bd91d | 3 years ago |
Andrew Dolgov | e5469479c1 | 3 years ago |
Andrew Dolgov | 42e057c808 | 3 years ago |
Andrew Dolgov | 53dcd4b229 | 3 years ago |
fox | 42cb2e5112 | 3 years ago |
wn_ | 2e8b064236 | 3 years ago |
Andrew Dolgov | 2cd159e2ce | 3 years ago |
Andrew Dolgov | 2aed79d729 | 3 years ago |
Andrew Dolgov | ecb94ec23d | 3 years ago |
Andrew Dolgov | 5c1f9f31bd | 3 years ago |
Andrew Dolgov | fe06416f17 | 3 years ago |
Andrew Dolgov | 98c75a9e43 | 3 years ago |
Andrew Dolgov | b649d2240f | 3 years ago |
Andrew Dolgov | c8883d3440 | 3 years ago |
Andrew Dolgov | bc2953b5e7 | 3 years ago |
Andrew Dolgov | 198c9b4069 | 3 years ago |
Andrew Dolgov | e8e6329040 | 3 years ago |
Andrew Dolgov | c744cfe2dc | 3 years ago |
Andrew Dolgov | d016f7a499 | 3 years ago |
Andrew Dolgov | 476965b161 | 3 years ago |
fox | c9b0196de0 | 3 years ago |
Threk | 9442ceb7bd | 3 years ago |
Andrew Dolgov | f398fea414 | 3 years ago |
Andrew Dolgov | cb4b730e42 | 3 years ago |
Andrew Dolgov | 386dc415d9 | 3 years ago |
Andrew Dolgov | 9b8b07376f | 3 years ago |
Andrew Dolgov | f90531ae40 | 3 years ago |
Andrew Dolgov | 6cf771f2bc | 3 years ago |
Andrew Dolgov | c50a4296a5 | 3 years ago |
Andrew Dolgov | 04128c7870 | 3 years ago |
Andrew Dolgov | 2f6ea8b387 | 3 years ago |
Andrew Dolgov | b74e313844 | 3 years ago |
Andrew Dolgov | 4fda5ccd0e | 3 years ago |
Andrew Dolgov | 30765805fd | 3 years ago |
Andrew Dolgov | 31b29e0a56 | 3 years ago |
Andrew Dolgov | 8f8ca49e4b | 3 years ago |
Andrew Dolgov | 4ede76280b | 3 years ago |
Andrew Dolgov | bd4ade6329 | 3 years ago |
Andrew Dolgov | 5eb0f3d640 | 3 years ago |
Andrew Dolgov | e19570f422 | 3 years ago |
Andrew Dolgov | c0fb0a5ec0 | 3 years ago |
Andrew Dolgov | 921569e5da | 3 years ago |
Andrew Dolgov | 8256ab5dd9 | 3 years ago |
Andrew Dolgov | 0cb719a404 | 3 years ago |
Marek Pavelka | 5c6c123676 | 3 years ago |
Andrew Dolgov | dfdb746a76 | 3 years ago |
Andrew Dolgov | cb7f322f09 | 3 years ago |
Andrew Dolgov | 06cb181f73 | 3 years ago |
Andrew Dolgov | 75e659ba65 | 3 years ago |
Andrew Dolgov | 0730128a97 | 3 years ago |
Andrew Dolgov | dbda996a7a | 3 years ago |
Andrew Dolgov | 1aedd22306 | 3 years ago |
Andrew Dolgov | 50087df162 | 3 years ago |
Andrew Dolgov | adf7189e94 | 3 years ago |
Andrew Dolgov | 3b67abb0ea | 3 years ago |
Andrew Dolgov | 6f93c45c28 | 3 years ago |
Andrew Dolgov | 9ec0732942 | 3 years ago |
Andrew Dolgov | ba86c64d38 | 3 years ago |
fox | c4b78ed0a6 | 3 years ago |
sam302psu | 57fdf032e9 | 3 years ago |
sam302psu | 8f8142df29 | 3 years ago |
Andrew Dolgov | 386316aba1 | 3 years ago |
Andrew Dolgov | 1ab6ca57af | 3 years ago |
Andrew Dolgov | d6629ed188 | 3 years ago |
Andrew Dolgov | 86b12fc06c | 3 years ago |
Andrew Dolgov | 08ff629af5 | 3 years ago |
Andrew Dolgov | d4ad483add | 3 years ago |
Andrew Dolgov | 982bd838bf | 3 years ago |
Andrew Dolgov | 30b94fb194 | 3 years ago |
Andrew Dolgov | 1a7f724bfa | 3 years ago |
Andrew Dolgov | 20d0cbff77 | 3 years ago |
Andrew Dolgov | f9888fc67f | 3 years ago |
Andrew Dolgov | c4eaab8a31 | 3 years ago |
Andrew Dolgov | 7cf12233d7 | 3 years ago |
Andrew Dolgov | dae0476159 | 3 years ago |
Andrew Dolgov | 2005a7bf4f | 3 years ago |
Andrew Dolgov | f097ae608d | 3 years ago |
Andrew Dolgov | 3bab5ca6b1 | 3 years ago |
Andrew Dolgov | f195e86be3 | 3 years ago |
Andrew Dolgov | 84d8b08d1f | 3 years ago |
Andrew Dolgov | 70adfd4a74 | 3 years ago |
Andrew Dolgov | 6f835ded78 | 3 years ago |
Andrew Dolgov | f56a4eab17 | 3 years ago |
Andrew Dolgov | 372e8e062c | 3 years ago |
Andrew Dolgov | 51ed72efab | 3 years ago |
fox | cd504b0e60 | 3 years ago |
wn_ | 03400bd8d4 | 3 years ago |
Andrew Dolgov | 031ee47a3e | 3 years ago |
Andrew Dolgov | b150e46a52 | 3 years ago |
Andrew Dolgov | cd962dfa00 | 3 years ago |
Andrew Dolgov | 56f658711f | 3 years ago |
Andrew Dolgov | 8b1a2406e6 | 3 years ago |
Andrew Dolgov | 127a868e40 | 3 years ago |
Andrew Dolgov | f38be747d1 | 3 years ago |
Andrew Dolgov | f96abd2b52 | 3 years ago |
Andrew Dolgov | 2d1391a02b | 3 years ago |
Andrew Dolgov | dbad39d7a2 | 3 years ago |
Andrew Dolgov | 6359259dbb | 3 years ago |
Andrew Dolgov | 320503dd39 | 3 years ago |
Andrew Dolgov | 20a844085f | 3 years ago |
Andrew Dolgov | 1e6973307c | 3 years ago |
Andrew Dolgov | 7ef72fe0dc | 3 years ago |
Andrew Dolgov | b05d4e3d9f | 3 years ago |
Andrew Dolgov | bf02afed45 | 3 years ago |
Andrew Dolgov | 1bb0d9b603 | 3 years ago |
Andrew Dolgov | a22ddb2fe0 | 3 years ago |
Andrew Dolgov | bada1601fc | 3 years ago |
Andrew Dolgov | f4fdc9c2a3 | 3 years ago |
Andrew Dolgov | afc7142250 | 3 years ago |
Andrew Dolgov | e2cbb54b2c | 3 years ago |
Andrew Dolgov | 7f2fe465b0 | 3 years ago |
Andrew Dolgov | d821e4b090 | 3 years ago |
Andrew Dolgov | 85f411d688 | 3 years ago |
Andrew Dolgov | 15f9cb708e | 3 years ago |
Andrew Dolgov | de63e3799a | 3 years ago |
Ptsa Daniel | 5832b0b040 | 3 years ago |
Andrew Dolgov | cf5c7c4f29 | 3 years ago |
Andrew Dolgov | 78a7b3642f | 3 years ago |
Andrew Dolgov | dfff2cef7b | 3 years ago |
Andrew Dolgov | 5edcbf2e9b | 3 years ago |
Andrew Dolgov | c1cd3324e3 | 3 years ago |
Andrew Dolgov | 6d06450649 | 3 years ago |
Andrew Dolgov | 126b1fd2de | 3 years ago |
Andrew Dolgov | c521e26a19 | 3 years ago |
Andrew Dolgov | d6bb77f452 | 3 years ago |
Andrew Dolgov | ebf16a36a1 | 3 years ago |
Andrew Dolgov | ef8c3abd7e | 3 years ago |
Andrew Dolgov | 3fd7856543 | 3 years ago |
fox | c6fb62f384 | 3 years ago |
Andrew Dolgov | bc4475b669 | 3 years ago |
Andrew Dolgov | cf1ede0ba8 | 3 years ago |
fox | 1baf8c5217 | 3 years ago |
Andrew Dolgov | d577eb898c | 3 years ago |
Andrew Dolgov | c01b6e43fd | 3 years ago |
wn_ | 86513d70dd | 3 years ago |
Andrew Dolgov | bf9033beb6 | 3 years ago |
Andrew Dolgov | 167c9fc34e | 3 years ago |
Andrew Dolgov | e6a875b7e4 | 3 years ago |
Andrew Dolgov | 4896874bda | 3 years ago |
Andrew Dolgov | fa7c6a6129 | 3 years ago |
Dario Di Ludovico | b63119df33 | 3 years ago |
Andrew Dolgov | b5d9b285f1 | 3 years ago |
Weblate | 05364e11ed | 3 years ago |
Andrew Dolgov | cb512d653c | 3 years ago |
Andrew Dolgov | 2a0b3a161c | 3 years ago |
Weblate | ab0bf8692d | 3 years ago |
Andrew Dolgov | c21fbb2d13 | 3 years ago |
Andrew Dolgov | 15cad4a9c0 | 3 years ago |
Andrew Dolgov | 634f1210a6 | 3 years ago |
Andrew Dolgov | 9a2f893672 | 3 years ago |
Andrew Dolgov | 8d49b6396e | 3 years ago |
Ptsa Daniel | 5794a801f0 | 3 years ago |
Dario Di Ludovico | 1dfa699aea | 3 years ago |
Glandos | a6853d2f49 | 3 years ago |
Andrew Dolgov | 26a6177bc9 | 3 years ago |
Andrew Dolgov | 9689f884ab | 3 years ago |
Andrew Dolgov | 05f690c86b | 3 years ago |
Andrew Dolgov | 3ab664f846 | 3 years ago |
Andrew Dolgov | f3d4bae32e | 3 years ago |
Andrew Dolgov | 51142e1bf8 | 3 years ago |
Andrew Dolgov | 7815a881e8 | 3 years ago |
Andrew Dolgov | 56b10fea18 | 3 years ago |
Andrew Dolgov | fd9cd52929 | 3 years ago |
Andrew Dolgov | a1ca62af50 | 3 years ago |
Andrew Dolgov | 22ae284db4 | 3 years ago |
Andrew Dolgov | 281f2efeb8 | 3 years ago |
Andrew Dolgov | 89ad25405e | 3 years ago |
Andrew Dolgov | 8915bd1b21 | 3 years ago |
Andrew Dolgov | 34c74400a4 | 3 years ago |
Andrew Dolgov | dcf0135285 | 3 years ago |
Andrew Dolgov | 59c14e9c00 | 3 years ago |
Andrew Dolgov | efd196839a | 3 years ago |
Andrew Dolgov | 1464abbbfc | 3 years ago |
Andrew Dolgov | f137e64a13 | 3 years ago |
Andrew Dolgov | c96172fa04 | 3 years ago |
Andrew Dolgov | 5aa05c90e1 | 3 years ago |
Andrew Dolgov | 011e318947 | 3 years ago |
Andrew Dolgov | 6f02b1afd0 | 3 years ago |
Frenck Lutke | 27b676b7b2 | 3 years ago |
Andrew Dolgov | 7f18e8c33b | 3 years ago |
Andrew Dolgov | 7869378436 | 3 years ago |
Frenck Lutke | 2f2642bbd4 | 3 years ago |
Andrew Dolgov | 00d0cb8c81 | 3 years ago |
Andrew Dolgov | 2621fe7955 | 3 years ago |
Andrew Dolgov | bd2314170d | 3 years ago |
Andrew Dolgov | e858e979e9 | 3 years ago |
Andrew Dolgov | 49a9afadce | 3 years ago |
Andrew Dolgov | 1112922029 | 3 years ago |
Andrew Dolgov | 8026f3c3bd | 3 years ago |
Andrew Dolgov | 988eb3ac91 | 3 years ago |
Andrew Dolgov | 922a699215 | 3 years ago |
Weblate | c70fc68012 | 3 years ago |
Andrew Dolgov | 93940d2a9f | 3 years ago |
Andrew Dolgov | 1adacd0572 | 3 years ago |
Andrew Dolgov | db583287b2 | 3 years ago |
Andrew Dolgov | 2f14fa1bc3 | 3 years ago |
Andrew Dolgov | 7f41228a71 | 3 years ago |
Andrew Dolgov | 553548b689 | 3 years ago |
Andrew Dolgov | 9313ebf2e7 | 3 years ago |
Andrew Dolgov | 8b09e653e0 | 3 years ago |
Andrew Dolgov | 155e4f6125 | 3 years ago |
Andrew Dolgov | 96182597c4 | 3 years ago |
Andrew Dolgov | 9ad5f04e51 | 3 years ago |
Andrew Dolgov | e468e5a589 | 3 years ago |
Andrew Dolgov | 6ea1430a04 | 3 years ago |
Andrew Dolgov | e6505b7d83 | 3 years ago |
Andrew Dolgov | d6203bf350 | 3 years ago |
Andrew Dolgov | a42e8aad97 | 3 years ago |
Andrew Dolgov | 8d2e3c2528 | 3 years ago |
Andrew Dolgov | 37d46411c7 | 3 years ago |
Andrew Dolgov | 85095f8a53 | 3 years ago |
Andrew Dolgov | ab4dafa4be | 3 years ago |
Andrew Dolgov | 9e2e12dff8 | 3 years ago |
Andrew Dolgov | 46e650622c | 3 years ago |
Andrew Dolgov | 2ae0b7059f | 3 years ago |
Andrew Dolgov | 5229cc58b2 | 3 years ago |
Andrew Dolgov | 4ed91619dd | 3 years ago |
Andrew Dolgov | cae54dad56 | 3 years ago |
Andrew Dolgov | 6e4fbbfa4d | 3 years ago |
Andrew Dolgov | 29ada58b4a | 3 years ago |
Andrew Dolgov | 77e6d589ff | 3 years ago |
Andrew Dolgov | fd5dd27f16 | 3 years ago |
fox | ac6cea859a | 3 years ago |
Andrew Dolgov | caf3040313 | 3 years ago |
Andrew Dolgov | 445ac1213c | 3 years ago |
Andrew Dolgov | 6b7af973b2 | 3 years ago |
Andrew Dolgov | 12bcf826e4 | 3 years ago |
Andrew Dolgov | 211f699aa0 | 3 years ago |
Andrew Dolgov | 383f4ca04a | 3 years ago |
Andrew Dolgov | e4107ac952 | 3 years ago |
wn_ | 7c966b69d5 | 3 years ago |
Andrew Dolgov | 42173386b3 | 3 years ago |
Andrew Dolgov | add6242e51 | 3 years ago |
fox | 3f00502305 | 3 years ago |
wn_ | 6fbf7ef368 | 3 years ago |
Andrew Dolgov | be4e7b1340 | 3 years ago |
Andrew Dolgov | 043ef3dad6 | 3 years ago |
Andrew Dolgov | 167ed87684 | 3 years ago |
Andrew Dolgov | 33fff26869 | 3 years ago |
wn_ | 02a9485966 | 3 years ago |
Andrew Dolgov | 6f29ecbbb9 | 3 years ago |
Andrew Dolgov | f6bfb89b29 | 3 years ago |
wn_ | cb401af6f6 | 3 years ago |
Andrew Dolgov | 861a632ac7 | 3 years ago |
Andrew Dolgov | c6b7a7f8d0 | 3 years ago |
Andrew Dolgov | 2ab215daca | 3 years ago |
fox | d0efa35d22 | 3 years ago |
Andrew Dolgov | 521d0b65c7 | 3 years ago |
wn_ | 1bd5152c80 | 3 years ago |
Andrew Dolgov | d1328321be | 3 years ago |
Andrew Dolgov | 2843b99171 | 3 years ago |
Andrew Dolgov | 810afdaf5a | 3 years ago |
Andrew Dolgov | fb471652c0 | 3 years ago |
Andrew Dolgov | 9e56896bd4 | 3 years ago |
Andrew Dolgov | 3b8d69206c | 3 years ago |
Andrew Dolgov | 94560132dd | 3 years ago |
Andrew Dolgov | b4e96374bc | 3 years ago |
Andrew Dolgov | da97b29dbe | 3 years ago |
Andrew Dolgov | 590b1fc39e | 3 years ago |
Andrew Dolgov | be91355c20 | 3 years ago |
Andrew Dolgov | d6de021ae6 | 3 years ago |
Andrew Dolgov | 39be169f0b | 3 years ago |
Andrew Dolgov | 5c7416458f | 3 years ago |
Andrew Dolgov | 22fe9b54d2 | 3 years ago |
Andrew Dolgov | 9586c72a17 | 3 years ago |
Andrew Dolgov | 545bcc3e4b | 3 years ago |
fox | b8786215dc | 3 years ago |
wn_ | ce3e1756b3 | 3 years ago |
Andrew Dolgov | 053b262aa7 | 3 years ago |
Andrew Dolgov | fc0ebf0891 | 3 years ago |
Andrew Dolgov | c9ccb0791d | 3 years ago |
Andrew Dolgov | cf249d7e8c | 3 years ago |
Andrew Dolgov | d5f4979831 | 3 years ago |
Andrew Dolgov | 5cec4eb015 | 3 years ago |
Andrew Dolgov | 760a26e484 | 3 years ago |
Andrew Dolgov | 737cffc241 | 3 years ago |
Andrew Dolgov | d445530fa0 | 3 years ago |
Andrew Dolgov | 4fa8450d38 | 3 years ago |
Andrew Dolgov | 921b5ca2ce | 3 years ago |
Andrew Dolgov | e73779fec1 | 3 years ago |
Andrew Dolgov | d9fe14a012 | 3 years ago |
Andrew Dolgov | 131f34648d | 3 years ago |
Andrew Dolgov | 660a1bbe01 | 3 years ago |
Andrew Dolgov | bb4e4282f4 | 3 years ago |
Andrew Dolgov | 6b43b788d9 | 3 years ago |
Andrew Dolgov | dba6dce3b3 | 3 years ago |
Andrew Dolgov | f645120641 | 3 years ago |
Andrew Dolgov | d26269865f | 3 years ago |
Andrew Dolgov | bec35200e9 | 3 years ago |
Andrew Dolgov | 0832dd9d40 | 3 years ago |
Andrew Dolgov | 00310d2d23 | 3 years ago |
Andrew Dolgov | dcfea9baac | 3 years ago |
Andrew Dolgov | d57e7eaa98 | 3 years ago |
Andrew Dolgov | 5475eed452 | 3 years ago |
Andrew Dolgov | b6c3dde1cc | 3 years ago |
Andrew Dolgov | c088e9d9d8 | 3 years ago |
Andrew Dolgov | 89fd9ec8c3 | 3 years ago |
Andrew Dolgov | e61e7c8356 | 3 years ago |
Andrew Dolgov | f77c17c6f0 | 3 years ago |
Andrew Dolgov | 70fa423026 | 3 years ago |
Andrew Dolgov | 0b6a71f8ea | 3 years ago |
Andrew Dolgov | 049c423454 | 3 years ago |
Andrew Dolgov | 839cb2cd21 | 3 years ago |
Andrew Dolgov | 61fdce4f44 | 3 years ago |
Andrew Dolgov | 2c5927d8cd | 3 years ago |
Andrew Dolgov | 2e4b403787 | 3 years ago |
Andrew Dolgov | bed36cbf9f | 3 years ago |
Andrew Dolgov | a2c75257f1 | 3 years ago |
Andrew Dolgov | 75435aa960 | 3 years ago |
Andrew Dolgov | d8a99ce06a | 3 years ago |
Andrew Dolgov | 39c0fe3697 | 3 years ago |
Andrew Dolgov | ee0b66b6bd | 3 years ago |
Andrew Dolgov | e03d6379a6 | 3 years ago |
Andrew Dolgov | 466cba39d8 | 3 years ago |
Andrew Dolgov | 1adb9bb6b6 | 3 years ago |
Andrew Dolgov | b888bc2091 | 3 years ago |
Andrew Dolgov | e4609c18ef | 3 years ago |
Andrew Dolgov | b16abc157e | 3 years ago |
Andrew Dolgov | 92cb91e2e2 | 3 years ago |
Andrew Dolgov | 35b6d63289 | 3 years ago |
Andrew Dolgov | 6ecee2abbd | 3 years ago |
Andrew Dolgov | ea37d05a83 | 3 years ago |
Andrew Dolgov | 2ac6508fe6 | 3 years ago |
Andrew Dolgov | 7be1e3ed38 | 3 years ago |
Andrew Dolgov | 2b2833bb4f | 3 years ago |
Andrew Dolgov | 4632d6cf55 | 3 years ago |
Andrew Dolgov | e9c3118ddd | 3 years ago |
Andrew Dolgov | 538f87e415 | 3 years ago |
Andrew Dolgov | d439685895 | 3 years ago |
Andrew Dolgov | 00b31c3f53 | 3 years ago |
Andrew Dolgov | 3c14eed1c2 | 3 years ago |
Andrew Dolgov | 35b6a88146 | 3 years ago |
Andrew Dolgov | 7587f2cdc6 | 3 years ago |
Andrew Dolgov | 91049335eb | 3 years ago |
Andrew Dolgov | 9ac6741d24 | 3 years ago |
Andrew Dolgov | 4325c30a3f | 3 years ago |
Andrew Dolgov | 273ada7353 | 3 years ago |
Andrew Dolgov | 7adcada324 | 3 years ago |
Andrew Dolgov | 0fc783e2b3 | 3 years ago |
Andrew Dolgov | 89e8176c69 | 3 years ago |
Andrew Dolgov | 91e7969383 | 3 years ago |
Andrew Dolgov | 24c79d91c2 | 3 years ago |
Andrew Dolgov | f58c49beaa | 3 years ago |
Andrew Dolgov | bf88c64d1e | 3 years ago |
Andrew Dolgov | 9d7ba773ec | 3 years ago |
Andrew Dolgov | 7fad6ce651 | 3 years ago |
Andrew Dolgov | bdbbdbb0ed | 3 years ago |
Andrew Dolgov | 627af2c236 | 3 years ago |
Andrew Dolgov | 4f4e57bb26 | 3 years ago |
Andrew Dolgov | 1f5d81b77c | 3 years ago |
Andrew Dolgov | af4b3e7df0 | 3 years ago |
Andrew Dolgov | 22fc6871e8 | 3 years ago |
Andrew Dolgov | d7127cead3 | 3 years ago |
Andrew Dolgov | 1f43d7916c | 3 years ago |
Andrew Dolgov | 26d6b84a57 | 3 years ago |
Andrew Dolgov | cb6b3584ce | 3 years ago |
Andrew Dolgov | 3887665bcb | 3 years ago |
Andrew Dolgov | cca84aedfd | 3 years ago |
Andrew Dolgov | 88f7c4f1a5 | 3 years ago |
Andrew Dolgov | 6e06fe2885 | 3 years ago |
Andrew Dolgov | 5c4223992f | 3 years ago |
Andrew Dolgov | 70e293bccb | 3 years ago |
Andrew Dolgov | d4157b9e4e | 3 years ago |
Andrew Dolgov | 39604bedef | 3 years ago |
Andrew Dolgov | 5d42ce553f | 3 years ago |
Andrew Dolgov | 9f55454f63 | 3 years ago |
Andrew Dolgov | bd3c38de84 | 3 years ago |
Andrew Dolgov | cfad740c99 | 3 years ago |
Andrew Dolgov | 91285e3868 | 3 years ago |
Andrew Dolgov | d1c83fad14 | 3 years ago |
Andrew Dolgov | 71f2f4288f | 3 years ago |
Andrew Dolgov | 6426ae559a | 3 years ago |
Andrew Dolgov | 166f2d4666 | 3 years ago |
Andrew Dolgov | 8e79f1717d | 3 years ago |
Andrew Dolgov | 5704deb460 | 3 years ago |
Andrew Dolgov | 257efb43c6 | 3 years ago |
Andrew Dolgov | 020f062a76 | 3 years ago |
Andrew Dolgov | 6b006a18e7 | 3 years ago |
Andrew Dolgov | ecb36b6354 | 3 years ago |
Dario Di Ludovico | 8b022c2bfb | 3 years ago |
Andrew Dolgov | 82adb01307 | 3 years ago |
fox | 916c21fe60 | 3 years ago |
Andrew Dolgov | 868b9b476e | 3 years ago |
Andrew Dolgov | 52a86c5e38 | 3 years ago |
Andrew Dolgov | a4604e892c | 3 years ago |
Andrew Dolgov | 3c584376ca | 3 years ago |
Andrew Dolgov | 9f31381bb6 | 3 years ago |
Andrew Dolgov | a2e688fcb2 | 3 years ago |
Joseph | 68e2ccb354 | 3 years ago |
Andrew Dolgov | 37a81ba594 | 3 years ago |
Andrew Dolgov | ff6031d3c9 | 3 years ago |
Andrew Dolgov | 4996d8ccfe | 3 years ago |
Andrew Dolgov | 0b7377238a | 3 years ago |
Andrew Dolgov | 33ea46c2bc | 3 years ago |
Andrew Dolgov | 0fbf109912 | 3 years ago |
Andrew Dolgov | a8cc43a0ff | 3 years ago |
Andrew Dolgov | 2547ece0ca | 3 years ago |
Andrew Dolgov | 1c7e4782aa | 3 years ago |
Andrew Dolgov | 6b5c9c781b | 3 years ago |
Andrew Dolgov | e5cedc7d5f | 3 years ago |
Andrew Dolgov | 8e75551f95 | 3 years ago |
Andrew Dolgov | 15fd23c374 | 3 years ago |
Ptsa Daniel | ce1831e2be | 3 years ago |
Andrew Dolgov | d4c925819b | 3 years ago |
Andrew Dolgov | 43d8a1f2ff | 3 years ago |
Andrew Dolgov | 103d30ad3f | 3 years ago |
Andrew Dolgov | c36b2adf84 | 3 years ago |
Andrew Dolgov | 8464c619e4 | 3 years ago |
Andrew Dolgov | 17413078a7 | 3 years ago |
Andrew Dolgov | 9684ce5c4b | 3 years ago |
Andrew Dolgov | b112198991 | 3 years ago |
Andrew Dolgov | 5127c29297 | 3 years ago |
Andrew Dolgov | aa63014073 | 3 years ago |
Andrew Dolgov | 46f6d7c11a | 3 years ago |
Andrew Dolgov | e7924c6dac | 3 years ago |
Andrew Dolgov | 0b71729bd3 | 3 years ago |
Andrew Dolgov | eec5871f5f | 3 years ago |
Glandos | e2c7166719 | 3 years ago |
Andrew Dolgov | d3940b6259 | 3 years ago |
Andrew Dolgov | 481bd76100 | 3 years ago |
Andrew Dolgov | 6af83e3881 | 3 years ago |
Andrew Dolgov | e6624cf631 | 3 years ago |
Andrew Dolgov | 119a4226d8 | 3 years ago |
Andrew Dolgov | f2d3cba231 | 3 years ago |
Weblate | d02872983d | 3 years ago |
Andrew Dolgov | 6365bf39d9 | 3 years ago |
Andrew Dolgov | 6d7fea537e | 3 years ago |
Andrew Dolgov | 157675d9fd | 3 years ago |
Andrew Dolgov | 7f0800537e | 3 years ago |
Andrew Dolgov | ad7842c98a | 3 years ago |
Andrew Dolgov | 9330bde991 | 3 years ago |
Andrew Dolgov | 03b85248e6 | 3 years ago |
Andrew Dolgov | 71dfc83466 | 3 years ago |
Andrew Dolgov | 1f2ba932b8 | 3 years ago |
Andrew Dolgov | d23a261b92 | 3 years ago |
Andrew Dolgov | 3268364693 | 3 years ago |
Andrew Dolgov | 3d11c61f32 | 3 years ago |
Andrew Dolgov | 219cc9a0ab | 3 years ago |
Andrew Dolgov | 8f8675a26a | 3 years ago |
Andrew Dolgov | 699186f430 | 3 years ago |
fox | a718b692a0 | 3 years ago |
Philip Klempin | ace19c0790 | 3 years ago |
Andrew Dolgov | 0f7af07c6e | 3 years ago |
Andrew Dolgov | 9804a17b79 | 3 years ago |
Andrew Dolgov | a72171f8ef | 3 years ago |
Andrew Dolgov | 20fb056323 | 3 years ago |
Andrew Dolgov | bf6d0f2817 | 3 years ago |
Andrew Dolgov | 72e38bfe1f | 3 years ago |
Andrew Dolgov | d466284fab | 3 years ago |
Andrew Dolgov | cb7c075cd2 | 3 years ago |
Andrew Dolgov | 83b0738b04 | 3 years ago |
Andrew Dolgov | 3134d71b8f | 3 years ago |
Andrew Dolgov | eac7ad5d34 | 3 years ago |
Andrew Dolgov | 4182018cb7 | 3 years ago |
Andrew Dolgov | 1a680d4eae | 3 years ago |
Andrew Dolgov | 848bc57f29 | 3 years ago |
Andrew Dolgov | 74986d1ac6 | 3 years ago |
Andrew Dolgov | cc646790fd | 3 years ago |
Andrew Dolgov | 09e9f34bb4 | 3 years ago |
Andrew Dolgov | 7af8744c85 | 3 years ago |
Andrew Dolgov | e7e73193fe | 3 years ago |
Andrew Dolgov | 2505ae43a9 | 3 years ago |
Andrew Dolgov | 9e1459d5db | 3 years ago |
Andrew Dolgov | 72edab5f1c | 3 years ago |
Andrew Dolgov | 7833760fa0 | 3 years ago |
Andrew Dolgov | d630a92c40 | 3 years ago |
Andrew Dolgov | 2f8efab275 | 3 years ago |
Andrew Dolgov | a5819569f2 | 3 years ago |
Andrew Dolgov | 6a25bc53ef | 3 years ago |
Andrew Dolgov | 3655e7aaf1 | 3 years ago |
Andrew Dolgov | aba028a375 | 3 years ago |
Andrew Dolgov | f6f0f21664 | 3 years ago |
Andrew Dolgov | 0871a51cb4 | 3 years ago |
Andrew Dolgov | 63a90d26f3 | 3 years ago |
Andrew Dolgov | 7ae0e8d9c5 | 3 years ago |
Andrew Dolgov | 345dbb3521 | 3 years ago |
Andrew Dolgov | 6c8ccd2acc | 3 years ago |
Andrew Dolgov | 9f3de2d24c | 3 years ago |
Andrew Dolgov | 07408ac222 | 3 years ago |
Andrew Dolgov | d91eae9c7e | 3 years ago |
Andrew Dolgov | 7eb860af61 | 3 years ago |
Andrew Dolgov | 6e57fd77af | 3 years ago |
Andrew Dolgov | a14873d5b4 | 3 years ago |
Andrew Dolgov | 54bbd08f38 | 3 years ago |
Andrew Dolgov | ca4c93c6b9 | 3 years ago |
Andrew Dolgov | 7874f6ac58 | 3 years ago |
Andrew Dolgov | a341a838b1 | 3 years ago |
Andrew Dolgov | 51d2deeea9 | 3 years ago |
Andrew Dolgov | fc2e0bf67b | 3 years ago |
Andrew Dolgov | fa2ebcd0a2 | 3 years ago |
Andrew Dolgov | 363b3629a4 | 3 years ago |
Andrew Dolgov | 3b52cea811 | 3 years ago |
Andrew Dolgov | 1d5c8ee500 | 3 years ago |
Andrew Dolgov | 1eb1629d9e | 3 years ago |
Andrew Dolgov | 20b56b5b23 | 3 years ago |
Andrew Dolgov | 4165834f80 | 3 years ago |
Andrew Dolgov | 9de26d44da | 3 years ago |
Andrew Dolgov | d293cbd5a9 | 3 years ago |
Andrew Dolgov | 43abc183ab | 3 years ago |
Andrew Dolgov | 0a788da2d2 | 3 years ago |
Andrew Dolgov | 3aada04c7f | 3 years ago |
Andrew Dolgov | 942afb43a1 | 3 years ago |
Andrew Dolgov | 5d0f65358f | 3 years ago |
Andrew Dolgov | 3ad820e083 | 3 years ago |
Andrew Dolgov | 479da5aa86 | 3 years ago |
Andrew Dolgov | 3f972f8fed | 3 years ago |
Andrew Dolgov | 983a874ddd | 3 years ago |
Andrew Dolgov | c1ad7acfb9 | 3 years ago |
Andrew Dolgov | 41fc03287e | 3 years ago |
Andrew Dolgov | c94f1b6ff8 | 3 years ago |
Andrew Dolgov | b6e1a5c91a | 3 years ago |
Andrew Dolgov | ce2335deaf | 3 years ago |
Andrew Dolgov | d8de10d78a | 3 years ago |
Andrew Dolgov | 73e697a0df | 3 years ago |
Andrew Dolgov | 73070544ca | 3 years ago |
Andrew Dolgov | 5cfc5914f2 | 3 years ago |
Andrew Dolgov | 5849a39820 | 3 years ago |
Andrew Dolgov | ce489a724b | 3 years ago |
Andrew Dolgov | 10392ecc28 | 3 years ago |
Andrew Dolgov | 9fdeb58fd3 | 3 years ago |
Andrew Dolgov | 8b39e6bca7 | 3 years ago |
Andrew Dolgov | a544123b59 | 3 years ago |
Andrew Dolgov | 6e774a58fe | 3 years ago |
Andrew Dolgov | 403dca154c | 3 years ago |
Andrew Dolgov | b4cbc792cc | 3 years ago |
Andrew Dolgov | 6d8f2221b8 | 3 years ago |
Eike | 505cbdd82e | 3 years ago |
Andrew Dolgov | eb896f824d | 3 years ago |
Andrew Dolgov | 927df33d49 | 3 years ago |
Andrew Dolgov | 64f7ac0e74 | 3 years ago |
fox | 607ecab31e | 3 years ago |
fox | 1507b051fd | 3 years ago |
Andrew Dolgov | 6c546f37ba | 3 years ago |
Andrew Dolgov | 43e9dd5ea9 | 3 years ago |
Andrew Dolgov | b30b354b53 | 3 years ago |
Andrew Dolgov | 0d1336bd29 | 3 years ago |
Andrew Dolgov | 1ded706f8f | 3 years ago |
Andrew Dolgov | 2933483393 | 3 years ago |
Andrew Dolgov | 41bde84a92 | 3 years ago |
Andrew Dolgov | 4e95591087 | 3 years ago |
Andrew Dolgov | da0ad82c24 | 3 years ago |
Andrew Dolgov | 6c13449088 | 3 years ago |
Andrew Dolgov | 25520e9784 | 3 years ago |
Andrew Dolgov | 7a2ad08a7d | 3 years ago |
Andrew Dolgov | c82457e534 | 3 years ago |
Andrew Dolgov | bc0d50e892 | 3 years ago |
Andrew Dolgov | b2993bcd30 | 3 years ago |
Andrew Dolgov | 78ed64932f | 3 years ago |
Andrew Dolgov | ee4b7bebe8 | 3 years ago |
Andrew Dolgov | 3d32a5f755 | 3 years ago |
Andrew Dolgov | 40f38fc87f | 3 years ago |
Andrew Dolgov | 6311fb607d | 3 years ago |
Andrew Dolgov | f67f0f864b | 3 years ago |
Dario Di Ludovico | ff194fadea | 3 years ago |
Andrew Dolgov | d1e8042cf3 | 3 years ago |
Andrew Dolgov | 6d4005f984 | 3 years ago |
fox | 8cf8db8456 | 3 years ago |
JustAMacUser | fadf4dec96 | 3 years ago |
Ptsa Daniel | 17bc1de49e | 3 years ago |
Weblate | 219b493550 | 3 years ago |
Andrew Dolgov | 8e1e9ec2e3 | 3 years ago |
Andrew Dolgov | 6ed6b9e120 | 3 years ago |
Andrew Dolgov | dcad60284c | 3 years ago |
Andrew Dolgov | 33a5ecd2ce | 3 years ago |
Andrew Dolgov | 0868ff9d64 | 3 years ago |
Andrew Dolgov | dc40f69511 | 3 years ago |
Andrew Dolgov | 8a34084df1 | 3 years ago |
Andrew Dolgov | 4e3ef7a4dd | 3 years ago |
Andrew Dolgov | a8302fb253 | 3 years ago |
Andrew Dolgov | 8764662138 | 3 years ago |
Andrew Dolgov | 2abc434e26 | 3 years ago |
Andrew Dolgov | 8cc07bc8bd | 3 years ago |
Andrew Dolgov | e86b2e60d3 | 3 years ago |
Andrew Dolgov | 8de2100cf7 | 3 years ago |
Andrew Dolgov | 57f36f3f97 | 3 years ago |
Ptsa Daniel | 7282f0bf38 | 3 years ago |
fox | d4666d30d2 | 3 years ago |
Tony | 564a24fd78 | 3 years ago |
Andrew Dolgov | 6da576dbe4 | 3 years ago |
Andrew Dolgov | f59c567831 | 3 years ago |
Andrew Dolgov | 5f733604f0 | 3 years ago |
Andrew Dolgov | 9e62513095 | 3 years ago |
Andrew Dolgov | b65e07a12b | 3 years ago |
Andrew Dolgov | f25ea5355c | 3 years ago |
fox | 82d3c653a7 | 3 years ago |
wn_ | 12435b223e | 3 years ago |
Andrew Dolgov | 50d089ae59 | 3 years ago |
fox | e48beee7fc | 3 years ago |
wn_ | d2db58de4f | 3 years ago |
fox | ef7e679363 | 3 years ago |
Andrew Dolgov | b4b2ba99ef | 3 years ago |
Andrew Dolgov | f05f9b4252 | 3 years ago |
Andrew Dolgov | 9b7338e807 | 3 years ago |
Andrew Dolgov | 8aa1b0fed6 | 3 years ago |
wn | 62da307ef1 | 3 years ago |
wn | a1f8d6941b | 3 years ago |
wn | 8c4ca7c8ef | 3 years ago |
wn | 95d0cb4953 | 3 years ago |
wn | c68f2aabc9 | 3 years ago |
wn | 936b91a7e6 | 3 years ago |
wn | 6bdf4a1a25 | 3 years ago |
wn | 08a6f6bde2 | 3 years ago |
wn | 75536b4790 | 3 years ago |
wn | 6f31372b37 | 3 years ago |
wn | 358bcdd881 | 3 years ago |
@ -1,24 +1,14 @@
|
||||
Thumbs.db
|
||||
/deploy.exclude
|
||||
/deploy.sh
|
||||
/.app_is_ready
|
||||
/messages.mo
|
||||
/node_modules
|
||||
/locale/**/*.po~
|
||||
/package-lock.json
|
||||
*~
|
||||
*.DS_Store
|
||||
#*
|
||||
.idea/*
|
||||
plugins.local/*
|
||||
themes.local/*
|
||||
config.php
|
||||
feed-icons/*
|
||||
cache/*/*
|
||||
lock/*
|
||||
tags
|
||||
cache/htmlpurifier/*/*ser
|
||||
lib/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/*/*ser
|
||||
web.config
|
||||
/.save.cson
|
||||
/.tags*
|
||||
/.gutentags
|
||||
/plugins.local/*
|
||||
/themes.local/*
|
||||
/config.php
|
||||
/feed-icons/*
|
||||
/cache/*/*
|
||||
/lock/*
|
||||
/.vscode/settings.json
|
||||
/vendor/**/.git
|
||||
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Listen for XDebug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"pathMappings": {
|
||||
"/var/www/html/tt-rss": "${workspaceRoot}",
|
||||
},
|
||||
"port": 9000
|
||||
},
|
||||
{
|
||||
"name": "Launch Chrome",
|
||||
"request": "launch",
|
||||
"type": "chrome",
|
||||
"pathMapping": {
|
||||
"/tt-rss/": "${workspaceFolder}"
|
||||
},
|
||||
"urlFilter": "*/tt-rss/*",
|
||||
"runtimeExecutable": "chrome.exe",
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,91 +0,0 @@
|
||||
<?php
|
||||
class Backend extends Handler_Protected {
|
||||
/* function digestTest() {
|
||||
if (isset($_SESSION['uid'])) {
|
||||
header("Content-type: text/html");
|
||||
|
||||
$rv = Digest::prepare_headlines_digest($_SESSION['uid'], 1, 1000);
|
||||
|
||||
print "<h1>HTML</h1>";
|
||||
print $rv[0];
|
||||
print "<h1>Plain text</h1>";
|
||||
print "<pre>".$rv[3]."</pre>";
|
||||
} else {
|
||||
print error_json(6);
|
||||
}
|
||||
} */
|
||||
|
||||
function help() {
|
||||
$topic = basename(clean($_REQUEST["topic"])); // only one for now
|
||||
|
||||
if ($topic == "main") {
|
||||
$info = RPC::get_hotkeys_info();
|
||||
$imap = RPC::get_hotkeys_map();
|
||||
$omap = array();
|
||||
|
||||
foreach ($imap[1] as $sequence => $action) {
|
||||
if (!isset($omap[$action])) $omap[$action] = array();
|
||||
|
||||
array_push($omap[$action], $sequence);
|
||||
}
|
||||
|
||||
print "<ul class='panel panel-scrollable hotkeys-help' style='height : 300px'>";
|
||||
|
||||
$cur_section = "";
|
||||
foreach ($info as $section => $hotkeys) {
|
||||
|
||||
if ($cur_section) print "<li> </li>";
|
||||
print "<li><h3>" . $section . "</h3></li>";
|
||||
$cur_section = $section;
|
||||
|
||||
foreach ($hotkeys as $action => $description) {
|
||||
|
||||
if (is_array($omap[$action])) {
|
||||
foreach ($omap[$action] as $sequence) {
|
||||
if (strpos($sequence, "|") !== false) {
|
||||
$sequence = substr($sequence,
|
||||
strpos($sequence, "|")+1,
|
||||
strlen($sequence));
|
||||
} else {
|
||||
$keys = explode(" ", $sequence);
|
||||
|
||||
for ($i = 0; $i < count($keys); $i++) {
|
||||
if (strlen($keys[$i]) > 1) {
|
||||
$tmp = '';
|
||||
foreach (str_split($keys[$i]) as $c) {
|
||||
switch ($c) {
|
||||
case '*':
|
||||
$tmp .= __('Shift') . '+';
|
||||
break;
|
||||
case '^':
|
||||
$tmp .= __('Ctrl') . '+';
|
||||
break;
|
||||
default:
|
||||
$tmp .= $c;
|
||||
}
|
||||
}
|
||||
$keys[$i] = $tmp;
|
||||
}
|
||||
}
|
||||
$sequence = join(" ", $keys);
|
||||
}
|
||||
|
||||
print "<li>";
|
||||
print "<div class='hk'><code>$sequence</code></div>";
|
||||
print "<div class='desc'>$description</div>";
|
||||
print "</li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print "</ul>";
|
||||
}
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
print "<button dojoType='dijit.form.Button'
|
||||
onclick=\"return dijit.byId('helpDlg').hide()\">".__('Close this window')."</button>";
|
||||
print "</footer>";
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,644 @@
|
||||
<?php
|
||||
class Config {
|
||||
private const _ENVVAR_PREFIX = "TTRSS_";
|
||||
|
||||
const T_BOOL = 1;
|
||||
const T_STRING = 2;
|
||||
const T_INT = 3;
|
||||
|
||||
const SCHEMA_VERSION = 145;
|
||||
|
||||
/* override defaults, defined below in _DEFAULTS[], prefixing with _ENVVAR_PREFIX:
|
||||
|
||||
DB_TYPE becomes:
|
||||
|
||||
.env:
|
||||
|
||||
TTRSS_DB_TYPE=pgsql
|
||||
|
||||
or config.php:
|
||||
|
||||
putenv('TTRSS_DB_TYPE=pgsql');
|
||||
|
||||
etc, etc.
|
||||
*/
|
||||
|
||||
const DB_TYPE = "DB_TYPE";
|
||||
const DB_HOST = "DB_HOST";
|
||||
const DB_USER = "DB_USER";
|
||||
const DB_NAME = "DB_NAME";
|
||||
const DB_PASS = "DB_PASS";
|
||||
const DB_PORT = "DB_PORT";
|
||||
// database credentials
|
||||
|
||||
const MYSQL_CHARSET = "MYSQL_CHARSET";
|
||||
// connection charset for MySQL. if you have a legacy database and/or experience
|
||||
// garbage unicode characters with this option, try setting it to a blank string.
|
||||
|
||||
const SELF_URL_PATH = "SELF_URL_PATH";
|
||||
// this should be set to a fully qualified URL used to access
|
||||
// your tt-rss instance over the net, such as: https://example.com/tt-rss/
|
||||
// if your tt-rss instance is behind a reverse proxy, use external URL.
|
||||
// tt-rss will likely help you pick correct value for this on startup
|
||||
|
||||
const SINGLE_USER_MODE = "SINGLE_USER_MODE";
|
||||
// operate in single user mode, disables all functionality related to
|
||||
// multiple users and authentication. enabling this assumes you have
|
||||
// your tt-rss directory protected by other means (e.g. http auth).
|
||||
|
||||
const SIMPLE_UPDATE_MODE = "SIMPLE_UPDATE_MODE";
|
||||
// enables fallback update mode where tt-rss tries to update feeds in
|
||||
// background while tt-rss is open in your browser.
|
||||
// if you don't have a lot of feeds and don't want to or can't run
|
||||
// background processes while not running tt-rss, this method is generally
|
||||
// viable to keep your feeds up to date.
|
||||
|
||||
const PHP_EXECUTABLE = "PHP_EXECUTABLE";
|
||||
// use this PHP CLI executable to start various tasks
|
||||
|
||||
const LOCK_DIRECTORY = "LOCK_DIRECTORY";
|
||||
// base directory for lockfiles (must be writable)
|
||||
|
||||
const CACHE_DIR = "CACHE_DIR";
|
||||
// base directory for local cache (must be writable)
|
||||
|
||||
const ICONS_DIR = "ICONS_DIR";
|
||||
const ICONS_URL = "ICONS_URL";
|
||||
// directory and URL for feed favicons (directory must be writable)
|
||||
|
||||
const AUTH_AUTO_CREATE = "AUTH_AUTO_CREATE";
|
||||
// auto create users authenticated via external modules
|
||||
|
||||
const AUTH_AUTO_LOGIN = "AUTH_AUTO_LOGIN";
|
||||
// auto log in users authenticated via external modules i.e. auth_remote
|
||||
|
||||
const FORCE_ARTICLE_PURGE = "FORCE_ARTICLE_PURGE";
|
||||
// unconditinally purge all articles older than this amount, in days
|
||||
// overrides user-controlled purge interval
|
||||
|
||||
const SESSION_COOKIE_LIFETIME = "SESSION_COOKIE_LIFETIME";
|
||||
// default lifetime of a session (e.g. login) cookie. In seconds,
|
||||
// 0 means cookie will be deleted when browser closes.
|
||||
|
||||
const SMTP_FROM_NAME = "SMTP_FROM_NAME";
|
||||
const SMTP_FROM_ADDRESS = "SMTP_FROM_ADDRESS";
|
||||
// send email using this name and address
|
||||
|
||||
const DIGEST_SUBJECT = "DIGEST_SUBJECT";
|
||||
// default subject for email digest
|
||||
|
||||
const CHECK_FOR_UPDATES = "CHECK_FOR_UPDATES";
|
||||
// enable built-in update checker, both for core code and plugins (using git)
|
||||
|
||||
const PLUGINS = "PLUGINS";
|
||||
// system plugins enabled for all users, comma separated list, no quotes
|
||||
// keep at least one auth module in there (i.e. auth_internal)
|
||||
|
||||
const LOG_DESTINATION = "LOG_DESTINATION";
|
||||
// available options: sql (default, event log), syslog, stdout (for debugging)
|
||||
|
||||
const LOCAL_OVERRIDE_STYLESHEET = "LOCAL_OVERRIDE_STYLESHEET";
|
||||
// link this stylesheet on all pages (if it exists), should be placed in themes.local
|
||||
|
||||
const LOCAL_OVERRIDE_JS = "LOCAL_OVERRIDE_JS";
|
||||
// same but this javascript file (you can use that for polyfills), should be placed in themes.local
|
||||
|
||||
const DAEMON_MAX_CHILD_RUNTIME = "DAEMON_MAX_CHILD_RUNTIME";
|
||||
// in seconds, terminate update tasks that ran longer than this interval
|
||||
|
||||
const DAEMON_MAX_JOBS = "DAEMON_MAX_JOBS";
|
||||
// max concurrent update jobs forking update daemon starts
|
||||
|
||||
const FEED_FETCH_TIMEOUT = "FEED_FETCH_TIMEOUT";
|
||||
// How long to wait for response when requesting feed from a site (seconds)
|
||||
|
||||
const FEED_FETCH_NO_CACHE_TIMEOUT = "FEED_FETCH_NO_CACHE_TIMEOUT";
|
||||
// Same but not cached
|
||||
|
||||
const FILE_FETCH_TIMEOUT = "FILE_FETCH_TIMEOUT";
|
||||
// Default timeout when fetching files from remote sites
|
||||
|
||||
const FILE_FETCH_CONNECT_TIMEOUT = "FILE_FETCH_CONNECT_TIMEOUT";
|
||||
// How long to wait for initial response from website when fetching files from remote sites
|
||||
|
||||
const DAEMON_UPDATE_LOGIN_LIMIT = "DAEMON_UPDATE_LOGIN_LIMIT";
|
||||
// stop updating feeds if user haven't logged in for X days
|
||||
|
||||
const DAEMON_FEED_LIMIT = "DAEMON_FEED_LIMIT";
|
||||
// how many feeds to update in one batch
|
||||
|
||||
const DAEMON_SLEEP_INTERVAL = "DAEMON_SLEEP_INTERVAL";
|
||||
// default sleep interval between feed updates (sec)
|
||||
|
||||
const MAX_CACHE_FILE_SIZE = "MAX_CACHE_FILE_SIZE";
|
||||
// do not cache files larger than that (bytes)
|
||||
|
||||
const MAX_DOWNLOAD_FILE_SIZE = "MAX_DOWNLOAD_FILE_SIZE";
|
||||
// do not download files larger than that (bytes)
|
||||
|
||||
const MAX_FAVICON_FILE_SIZE = "MAX_FAVICON_FILE_SIZE";
|
||||
// max file size for downloaded favicons (bytes)
|
||||
|
||||
const CACHE_MAX_DAYS = "CACHE_MAX_DAYS";
|
||||
// max age in days for various automatically cached (temporary) files
|
||||
|
||||
const MAX_CONDITIONAL_INTERVAL = "MAX_CONDITIONAL_INTERVAL";
|
||||
// max interval between forced unconditional updates for servers not complying with http if-modified-since (seconds)
|
||||
|
||||
const DAEMON_UNSUCCESSFUL_DAYS_LIMIT = "DAEMON_UNSUCCESSFUL_DAYS_LIMIT";
|
||||
// automatically disable updates for feeds which failed to
|
||||
// update for this amount of days; 0 disables
|
||||
|
||||
const LOG_SENT_MAIL = "LOG_SENT_MAIL";
|
||||
// log all sent emails in the event log
|
||||
|
||||
const HTTP_PROXY = "HTTP_PROXY";
|
||||
// use HTTP proxy for requests
|
||||
|
||||
const FORBID_PASSWORD_CHANGES = "FORBID_PASSWORD_CHANGES";
|
||||
// prevent users from changing passwords
|
||||
|
||||
const SESSION_NAME = "SESSION_NAME";
|
||||
// default session cookie name
|
||||
|
||||
const CHECK_FOR_PLUGIN_UPDATES = "CHECK_FOR_PLUGIN_UPDATES";
|
||||
// enable plugin update checker (using git)
|
||||
|
||||
const ENABLE_PLUGIN_INSTALLER = "ENABLE_PLUGIN_INSTALLER";
|
||||
// allow installing first party plugins using plugin installer in prefs
|
||||
|
||||
const AUTH_MIN_INTERVAL = "AUTH_MIN_INTERVAL";
|
||||
// minimum amount of seconds required between authentication attempts
|
||||
|
||||
const HTTP_USER_AGENT = "HTTP_USER_AGENT";
|
||||
// http user agent (changing this is not recommended)
|
||||
|
||||
// default values for all of the above:
|
||||
private const _DEFAULTS = [
|
||||
Config::DB_TYPE => [ "pgsql", Config::T_STRING ],
|
||||
Config::DB_HOST => [ "db", Config::T_STRING ],
|
||||
Config::DB_USER => [ "", Config::T_STRING ],
|
||||
Config::DB_NAME => [ "", Config::T_STRING ],
|
||||
Config::DB_PASS => [ "", Config::T_STRING ],
|
||||
Config::DB_PORT => [ "5432", Config::T_STRING ],
|
||||
Config::MYSQL_CHARSET => [ "UTF8", Config::T_STRING ],
|
||||
Config::SELF_URL_PATH => [ "", Config::T_STRING ],
|
||||
Config::SINGLE_USER_MODE => [ "", Config::T_BOOL ],
|
||||
Config::SIMPLE_UPDATE_MODE => [ "", Config::T_BOOL ],
|
||||
Config::PHP_EXECUTABLE => [ "/usr/bin/php", Config::T_STRING ],
|
||||
Config::LOCK_DIRECTORY => [ "lock", Config::T_STRING ],
|
||||
Config::CACHE_DIR => [ "cache", Config::T_STRING ],
|
||||
Config::ICONS_DIR => [ "feed-icons", Config::T_STRING ],
|
||||
Config::ICONS_URL => [ "feed-icons", Config::T_STRING ],
|
||||
Config::AUTH_AUTO_CREATE => [ "true", Config::T_BOOL ],
|
||||
Config::AUTH_AUTO_LOGIN => [ "true", Config::T_BOOL ],
|
||||
Config::FORCE_ARTICLE_PURGE => [ 0, Config::T_INT ],
|
||||
Config::SESSION_COOKIE_LIFETIME => [ 86400, Config::T_INT ],
|
||||
Config::SMTP_FROM_NAME => [ "Tiny Tiny RSS", Config::T_STRING ],
|
||||
Config::SMTP_FROM_ADDRESS => [ "noreply@localhost", Config::T_STRING ],
|
||||
Config::DIGEST_SUBJECT => [ "[tt-rss] New headlines for last 24 hours",
|
||||
Config::T_STRING ],
|
||||
Config::CHECK_FOR_UPDATES => [ "true", Config::T_BOOL ],
|
||||
Config::PLUGINS => [ "auth_internal", Config::T_STRING ],
|
||||
Config::LOG_DESTINATION => [ Logger::LOG_DEST_SQL, Config::T_STRING ],
|
||||
Config::LOCAL_OVERRIDE_STYLESHEET => [ "local-overrides.css",
|
||||
Config::T_STRING ],
|
||||
Config::LOCAL_OVERRIDE_JS => [ "local-overrides.js",
|
||||
Config::T_STRING ],
|
||||
Config::DAEMON_MAX_CHILD_RUNTIME => [ 1800, Config::T_INT ],
|
||||
Config::DAEMON_MAX_JOBS => [ 2, Config::T_INT ],
|
||||
Config::FEED_FETCH_TIMEOUT => [ 45, Config::T_INT ],
|
||||
Config::FEED_FETCH_NO_CACHE_TIMEOUT => [ 15, Config::T_INT ],
|
||||
Config::FILE_FETCH_TIMEOUT => [ 45, Config::T_INT ],
|
||||
Config::FILE_FETCH_CONNECT_TIMEOUT => [ 15, Config::T_INT ],
|
||||
Config::DAEMON_UPDATE_LOGIN_LIMIT => [ 30, Config::T_INT ],
|
||||
Config::DAEMON_FEED_LIMIT => [ 500, Config::T_INT ],
|
||||
Config::DAEMON_SLEEP_INTERVAL => [ 120, Config::T_INT ],
|
||||
Config::MAX_CACHE_FILE_SIZE => [ 64*1024*1024, Config::T_INT ],
|
||||
Config::MAX_DOWNLOAD_FILE_SIZE => [ 16*1024*1024, Config::T_INT ],
|
||||
Config::MAX_FAVICON_FILE_SIZE => [ 1*1024*1024, Config::T_INT ],
|
||||
Config::CACHE_MAX_DAYS => [ 7, Config::T_INT ],
|
||||
Config::MAX_CONDITIONAL_INTERVAL => [ 3600*12, Config::T_INT ],
|
||||
Config::DAEMON_UNSUCCESSFUL_DAYS_LIMIT => [ 30, Config::T_INT ],
|
||||
Config::LOG_SENT_MAIL => [ "", Config::T_BOOL ],
|
||||
Config::HTTP_PROXY => [ "", Config::T_STRING ],
|
||||
Config::FORBID_PASSWORD_CHANGES => [ "", Config::T_BOOL ],
|
||||
Config::SESSION_NAME => [ "ttrss_sid", Config::T_STRING ],
|
||||
Config::CHECK_FOR_PLUGIN_UPDATES => [ "true", Config::T_BOOL ],
|
||||
Config::ENABLE_PLUGIN_INSTALLER => [ "true", Config::T_BOOL ],
|
||||
Config::AUTH_MIN_INTERVAL => [ 5, Config::T_INT ],
|
||||
Config::HTTP_USER_AGENT => [ 'Tiny Tiny RSS/%s (https://tt-rss.org/)',
|
||||
Config::T_STRING ],
|
||||
];
|
||||
|
||||
private static $instance;
|
||||
|
||||
private $params = [];
|
||||
private $schema_version = null;
|
||||
private $version = [];
|
||||
|
||||
/** @var Db_Migrations $migrations */
|
||||
private $migrations;
|
||||
|
||||
public static function get_instance() : Config {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
function __construct() {
|
||||
$ref = new ReflectionClass(get_class($this));
|
||||
|
||||
foreach ($ref->getConstants() as $const => $cvalue) {
|
||||
if (isset($this::_DEFAULTS[$const])) {
|
||||
$override = getenv($this::_ENVVAR_PREFIX . $const);
|
||||
|
||||
list ($defval, $deftype) = $this::_DEFAULTS[$const];
|
||||
|
||||
$this->params[$cvalue] = [ self::cast_to($override !== false ? $override : $defval, $deftype), $deftype ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package maintainers who don't use git: if version_static.txt exists in tt-rss root
|
||||
directory, its contents are displayed instead of git commit-based version, this could be generated
|
||||
based on source git tree commit used when creating the package */
|
||||
|
||||
static function get_version(bool $as_string = true) {
|
||||
return self::get_instance()->_get_version($as_string);
|
||||
}
|
||||
|
||||
private function _get_version(bool $as_string = true) {
|
||||
$root_dir = dirname(__DIR__);
|
||||
|
||||
if (empty($this->version)) {
|
||||
$this->version["status"] = -1;
|
||||
|
||||
if (PHP_OS === "Darwin") {
|
||||
$ttrss_version["version"] = "UNKNOWN (Unsupported, Darwin)";
|
||||
} else if (file_exists("$root_dir/version_static.txt")) {
|
||||
$this->version["version"] = trim(file_get_contents("$root_dir/version_static.txt")) . " (Unsupported)";
|
||||
} else if (is_dir("$root_dir/.git")) {
|
||||
$this->version = self::get_version_from_git($root_dir);
|
||||
|
||||
if ($this->version["status"] != 0) {
|
||||
user_error("Unable to determine version: " . $this->version["version"], E_USER_WARNING);
|
||||
|
||||
$this->version["version"] = "UNKNOWN (Unsupported, Git error)";
|
||||
}
|
||||
} else {
|
||||
$this->version["version"] = "UNKNOWN (Unsupported)";
|
||||
}
|
||||
}
|
||||
|
||||
return $as_string ? $this->version["version"] : $this->version;
|
||||
}
|
||||
|
||||
static function get_version_from_git(string $dir) {
|
||||
$descriptorspec = [
|
||||
1 => ["pipe", "w"], // STDOUT
|
||||
2 => ["pipe", "w"], // STDERR
|
||||
];
|
||||
|
||||
$rv = [
|
||||
"status" => -1,
|
||||
"version" => "",
|
||||
"commit" => "",
|
||||
"timestamp" => 0,
|
||||
];
|
||||
|
||||
$proc = proc_open("git --no-pager log --pretty=\"version-%ct-%h\" -n1 HEAD",
|
||||
$descriptorspec, $pipes, $dir);
|
||||
|
||||
if (is_resource($proc)) {
|
||||
$stdout = trim(stream_get_contents($pipes[1]));
|
||||
$stderr = trim(stream_get_contents($pipes[2]));
|
||||
$status = proc_close($proc);
|
||||
|
||||
$rv["status"] = $status;
|
||||
|
||||
list($check, $timestamp, $commit) = explode("-", $stdout);
|
||||
|
||||
if ($check == "version") {
|
||||
|
||||
$rv["version"] = strftime("%y.%m", (int)$timestamp) . "-$commit";
|
||||
$rv["commit"] = $commit;
|
||||
$rv["timestamp"] = $timestamp;
|
||||
|
||||
// proc_close() may return -1 even if command completed successfully
|
||||
// so if it looks like we got valid data, we ignore it
|
||||
|
||||
if ($rv["status"] == -1)
|
||||
$rv["status"] = 0;
|
||||
|
||||
} else {
|
||||
$rv["version"] = T_sprintf("Git error [RC=%d]: %s", $status, $stderr);
|
||||
}
|
||||
}
|
||||
|
||||
return $rv;
|
||||
}
|
||||
|
||||
static function get_migrations() : Db_Migrations {
|
||||
return self::get_instance()->_get_migrations();
|
||||
}
|
||||
|
||||
private function _get_migrations() : Db_Migrations {
|
||||
if (empty($this->migrations)) {
|
||||
$this->migrations = new Db_Migrations();
|
||||
$this->migrations->initialize(dirname(__DIR__) . "/sql", "ttrss_version", true, self::SCHEMA_VERSION);
|
||||
}
|
||||
|
||||
return $this->migrations;
|
||||
}
|
||||
|
||||
static function is_migration_needed() : bool {
|
||||
return self::get_migrations()->is_migration_needed();
|
||||
}
|
||||
|
||||
static function get_schema_version() : int {
|
||||
return self::get_migrations()->get_version();
|
||||
}
|
||||
|
||||
static function cast_to(string $value, int $type_hint) {
|
||||
switch ($type_hint) {
|
||||
case self::T_BOOL:
|
||||
return sql_bool_to_bool($value);
|
||||
case self::T_INT:
|
||||
return (int) $value;
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
private function _get(string $param) {
|
||||
list ($value, $type_hint) = $this->params[$param];
|
||||
|
||||
return $this->cast_to($value, $type_hint);
|
||||
}
|
||||
|
||||
private function _add(string $param, string $default, int $type_hint) {
|
||||
$override = getenv($this::_ENVVAR_PREFIX . $param);
|
||||
|
||||
$this->params[$param] = [ self::cast_to($override !== false ? $override : $default, $type_hint), $type_hint ];
|
||||
}
|
||||
|
||||
static function add(string $param, string $default, int $type_hint = Config::T_STRING) {
|
||||
$instance = self::get_instance();
|
||||
|
||||
return $instance->_add($param, $default, $type_hint);
|
||||
}
|
||||
|
||||
static function get(string $param) {
|
||||
$instance = self::get_instance();
|
||||
|
||||
return $instance->_get($param);
|
||||
}
|
||||
|
||||
/** this returns Config::SELF_URL_PATH sans trailing slash */
|
||||
static function get_self_url() : string {
|
||||
$self_url_path = self::get(Config::SELF_URL_PATH);
|
||||
|
||||
if (substr($self_url_path, -1) === "/") {
|
||||
return substr($self_url_path, 0, -1);
|
||||
} else {
|
||||
return $self_url_path;
|
||||
}
|
||||
}
|
||||
|
||||
static function is_server_https() : bool {
|
||||
return (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] != 'off')) ||
|
||||
(!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https');
|
||||
}
|
||||
|
||||
/** generates reference self_url_path (no trailing slash) */
|
||||
static function make_self_url() : string {
|
||||
$proto = self::is_server_https() ? 'https' : 'http';
|
||||
$self_url_path = $proto . '://' . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
|
||||
|
||||
$self_url_path = preg_replace("/\w+\.php(\?.*$)?$/", "", $self_url_path);
|
||||
|
||||
if (substr($self_url_path, -1) === "/") {
|
||||
return substr($self_url_path, 0, -1);
|
||||
} else {
|
||||
return $self_url_path;
|
||||
}
|
||||
}
|
||||
|
||||
/* sanity check stuff */
|
||||
|
||||
private static function check_mysql_tables() {
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT engine, table_name FROM information_schema.tables WHERE
|
||||
table_schema = ? AND table_name LIKE 'ttrss_%' AND engine != 'InnoDB'");
|
||||
$sth->execute([self::get(Config::DB_NAME)]);
|
||||
|
||||
$bad_tables = [];
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
array_push($bad_tables, $line);
|
||||
}
|
||||
|
||||
return $bad_tables;
|
||||
}
|
||||
|
||||
static function sanity_check() {
|
||||
|
||||
/*
|
||||
we don't actually need the DB object right now but some checks below might use ORM which won't be initialized
|
||||
because it is set up in the Db constructor, which is why it's a good idea to invoke it as early as possible
|
||||
|
||||
it is a bit of a hack, maybe ORM should be initialized somewhere else (functions.php?)
|
||||
*/
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$errors = [];
|
||||
|
||||
if (strpos(self::get(Config::PLUGINS), "auth_") === false) {
|
||||
array_push($errors, "Please enable at least one authentication module via PLUGINS");
|
||||
}
|
||||
|
||||
if (function_exists('posix_getuid') && posix_getuid() == 0) {
|
||||
array_push($errors, "Please don't run this script as root.");
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.1.0', '<')) {
|
||||
array_push($errors, "PHP version 7.1.0 or newer required. You're using " . PHP_VERSION . ".");
|
||||
}
|
||||
|
||||
if (!class_exists("UConverter")) {
|
||||
array_push($errors, "PHP UConverter class is missing, it's provided by the Internationalization (intl) module.");
|
||||
}
|
||||
|
||||
if (!is_writable(self::get(Config::CACHE_DIR) . "/images")) {
|
||||
array_push($errors, "Image cache is not writable (chmod -R 777 ".self::get(Config::CACHE_DIR)."/images)");
|
||||
}
|
||||
|
||||
if (!is_writable(self::get(Config::CACHE_DIR) . "/upload")) {
|
||||
array_push($errors, "Upload cache is not writable (chmod -R 777 ".self::get(Config::CACHE_DIR)."/upload)");
|
||||
}
|
||||
|
||||
if (!is_writable(self::get(Config::CACHE_DIR) . "/export")) {
|
||||
array_push($errors, "Data export cache is not writable (chmod -R 777 ".self::get(Config::CACHE_DIR)."/export)");
|
||||
}
|
||||
|
||||
// ttrss_users won't be there on initial startup (before migrations are done)
|
||||
if (!Config::is_migration_needed() && self::get(Config::SINGLE_USER_MODE)) {
|
||||
if (UserHelper::get_login_by_id(1) != "admin") {
|
||||
array_push($errors, "SINGLE_USER_MODE is enabled but default admin account (ID: 1) is not found.");
|
||||
}
|
||||
}
|
||||
|
||||
if (php_sapi_name() != "cli") {
|
||||
|
||||
if (self::get_schema_version() < 0) {
|
||||
array_push($errors, "Base database schema is missing. Either load it manually or perform a migration (<code>update.php --update-schema</code>)");
|
||||
}
|
||||
|
||||
$ref_self_url_path = self::make_self_url();
|
||||
|
||||
if ($ref_self_url_path) {
|
||||
$ref_self_url_path = preg_replace("/\w+\.php$/", "", $ref_self_url_path);
|
||||
}
|
||||
|
||||
if (self::get_self_url() == "http://example.org/tt-rss") {
|
||||
$hint = $ref_self_url_path ? "(possible value: <b>$ref_self_url_path</b>)" : "";
|
||||
array_push($errors,
|
||||
"Please set SELF_URL_PATH to the correct value for your server: $hint");
|
||||
}
|
||||
|
||||
if (self::get_self_url() != $ref_self_url_path) {
|
||||
array_push($errors,
|
||||
"Please set SELF_URL_PATH to the correct value detected for your server: <b>$ref_self_url_path</b> (you're using: <b>" . self::get_self_url() . "</b>)");
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_writable(self::get(Config::ICONS_DIR))) {
|
||||
array_push($errors, "ICONS_DIR defined in config.php is not writable (chmod -R 777 ".self::get(Config::ICONS_DIR).").\n");
|
||||
}
|
||||
|
||||
if (!is_writable(self::get(Config::LOCK_DIRECTORY))) {
|
||||
array_push($errors, "LOCK_DIRECTORY is not writable (chmod -R 777 ".self::get(Config::LOCK_DIRECTORY).").\n");
|
||||
}
|
||||
|
||||
if (!function_exists("curl_init") && !ini_get("allow_url_fopen")) {
|
||||
array_push($errors, "PHP configuration option allow_url_fopen is disabled, and CURL functions are not present. Either enable allow_url_fopen or install PHP extension for CURL.");
|
||||
}
|
||||
|
||||
if (!function_exists("json_encode")) {
|
||||
array_push($errors, "PHP support for JSON is required, but was not found.");
|
||||
}
|
||||
|
||||
if (!class_exists("PDO")) {
|
||||
array_push($errors, "PHP support for PDO is required but was not found.");
|
||||
}
|
||||
|
||||
if (!function_exists("mb_strlen")) {
|
||||
array_push($errors, "PHP support for mbstring functions is required but was not found.");
|
||||
}
|
||||
|
||||
if (!function_exists("hash")) {
|
||||
array_push($errors, "PHP support for hash() function is required but was not found.");
|
||||
}
|
||||
|
||||
if (ini_get("safe_mode")) {
|
||||
array_push($errors, "PHP safe mode setting is obsolete and not supported by tt-rss.");
|
||||
}
|
||||
|
||||
if (!function_exists("mime_content_type")) {
|
||||
array_push($errors, "PHP function mime_content_type() is missing, try enabling fileinfo module.");
|
||||
}
|
||||
|
||||
if (!class_exists("DOMDocument")) {
|
||||
array_push($errors, "PHP support for DOMDocument is required, but was not found.");
|
||||
}
|
||||
|
||||
if (self::get(Config::DB_TYPE) == "mysql") {
|
||||
$bad_tables = self::check_mysql_tables();
|
||||
|
||||
if (count($bad_tables) > 0) {
|
||||
$bad_tables_fmt = [];
|
||||
|
||||
foreach ($bad_tables as $bt) {
|
||||
array_push($bad_tables_fmt, sprintf("%s (%s)", $bt['table_name'], $bt['engine']));
|
||||
}
|
||||
|
||||
$msg = "<p>The following tables use an unsupported MySQL engine: <b>" .
|
||||
implode(", ", $bad_tables_fmt) . "</b>.</p>";
|
||||
|
||||
$msg .= "<p>The only supported engine on MySQL is InnoDB. MyISAM lacks functionality to run
|
||||
tt-rss.
|
||||
Please backup your data (via OPML) and re-import the schema before continuing.</p>
|
||||
<p><b>WARNING: importing the schema would mean LOSS OF ALL YOUR DATA.</b></p>";
|
||||
|
||||
|
||||
array_push($errors, $msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($errors) > 0 && php_sapi_name() != "cli") { ?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Startup failed</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="themes/light.css">
|
||||
</head>
|
||||
<body class="sanity_failed flat ttrss_utility">
|
||||
<div class="content">
|
||||
<h1>Startup failed</h1>
|
||||
|
||||
<p>Please fix errors indicated by the following messages:</p>
|
||||
|
||||
<?php foreach ($errors as $error) { echo self::format_error($error); } ?>
|
||||
|
||||
<p>You might want to check tt-rss <a target="_blank" href="https://tt-rss.org/wiki.php">wiki</a> or the
|
||||
<a target="_blank" href="https://community.tt-rss.org/">forums</a> for more information. Please search the forums before creating new topic
|
||||
for your question.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<?php
|
||||
die;
|
||||
} else if (count($errors) > 0) {
|
||||
echo "Please fix errors indicated by the following messages:\n\n";
|
||||
|
||||
foreach ($errors as $error) {
|
||||
echo " * " . strip_tags($error)."\n";
|
||||
}
|
||||
|
||||
echo "\nYou might want to check tt-rss wiki or the forums for more information.\n";
|
||||
echo "Please search the forums before creating new topic for your question.\n";
|
||||
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static function format_error($msg) {
|
||||
return "<div class=\"alert alert-danger\">$msg</div>";
|
||||
}
|
||||
|
||||
static function get_override_links() {
|
||||
$rv = "";
|
||||
|
||||
$local_css = get_theme_path(self::get(self::LOCAL_OVERRIDE_STYLESHEET));
|
||||
if ($local_css) $rv .= stylesheet_tag($local_css);
|
||||
|
||||
$local_js = get_theme_path(self::get(self::LOCAL_OVERRIDE_JS));
|
||||
if ($local_js) $rv .= javascript_tag($local_js);
|
||||
|
||||
return $rv;
|
||||
}
|
||||
|
||||
static function get_user_agent() {
|
||||
return sprintf(self::get(self::HTTP_USER_AGENT), self::get_version());
|
||||
}
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
class Db_Migrations {
|
||||
|
||||
private $base_filename = "schema.sql";
|
||||
private $base_path;
|
||||
private $migrations_path;
|
||||
private $migrations_table;
|
||||
private $base_is_latest;
|
||||
private $pdo;
|
||||
|
||||
private $cached_version;
|
||||
private $cached_max_version;
|
||||
private $max_version_override;
|
||||
|
||||
function __construct() {
|
||||
$this->pdo = Db::pdo();
|
||||
}
|
||||
|
||||
function initialize_for_plugin(Plugin $plugin, bool $base_is_latest = true, string $schema_suffix = "sql") {
|
||||
$plugin_dir = PluginHost::getInstance()->get_plugin_dir($plugin);
|
||||
$this->initialize($plugin_dir . "/${schema_suffix}",
|
||||
strtolower("ttrss_migrations_plugin_" . get_class($plugin)),
|
||||
$base_is_latest);
|
||||
}
|
||||
|
||||
function initialize(string $root_path, string $migrations_table, bool $base_is_latest = true, int $max_version_override = 0) {
|
||||
$this->base_path = "$root_path/" . Config::get(Config::DB_TYPE);
|
||||
$this->migrations_path = $this->base_path . "/migrations";
|
||||
$this->migrations_table = $migrations_table;
|
||||
$this->base_is_latest = $base_is_latest;
|
||||
$this->max_version_override = $max_version_override;
|
||||
}
|
||||
|
||||
private function set_version(int $version) {
|
||||
Debug::log("Updating table {$this->migrations_table} with version ${version}...", Debug::LOG_EXTENDED);
|
||||
|
||||
$sth = $this->pdo->query("SELECT * FROM {$this->migrations_table}");
|
||||
|
||||
if ($res = $sth->fetch()) {
|
||||
$sth = $this->pdo->prepare("UPDATE {$this->migrations_table} SET schema_version = ?");
|
||||
} else {
|
||||
$sth = $this->pdo->prepare("INSERT INTO {$this->migrations_table} (schema_version) VALUES (?)");
|
||||
}
|
||||
|
||||
$sth->execute([$version]);
|
||||
|
||||
$this->cached_version = $version;
|
||||
}
|
||||
|
||||
function get_version() : int {
|
||||
if (isset($this->cached_version))
|
||||
return $this->cached_version;
|
||||
|
||||
try {
|
||||
$sth = $this->pdo->query("SELECT * FROM {$this->migrations_table}");
|
||||
|
||||
if ($res = $sth->fetch()) {
|
||||
return (int) $res['schema_version'];
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$this->create_migrations_table();
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private function create_migrations_table() {
|
||||
$this->pdo->query("CREATE TABLE IF NOT EXISTS {$this->migrations_table} (schema_version integer not null)");
|
||||
}
|
||||
|
||||
private function migrate_to(int $version) {
|
||||
try {
|
||||
if ($version <= $this->get_version()) {
|
||||
Debug::log("Refusing to apply version $version: current version is higher", Debug::LOG_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($version == 0)
|
||||
Debug::log("Loading base database schema...", Debug::LOG_VERBOSE);
|
||||
else
|
||||
Debug::log("Starting migration to $version...", Debug::LOG_VERBOSE);
|
||||
|
||||
$lines = $this->get_lines($version);
|
||||
|
||||
if (count($lines) > 0) {
|
||||
// mysql doesn't support transactions for DDL statements
|
||||
if (Config::get(Config::DB_TYPE) != "mysql")
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
foreach ($lines as $line) {
|
||||
Debug::log($line, Debug::LOG_EXTENDED);
|
||||
try {
|
||||
$this->pdo->query($line);
|
||||
} catch (PDOException $e) {
|
||||
Debug::log("Failed on line: $line", Debug::LOG_VERBOSE);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if ($version == 0 && $this->base_is_latest)
|
||||
$this->set_version($this->get_max_version());
|
||||
else
|
||||
$this->set_version($version);
|
||||
|
||||
if (Config::get(Config::DB_TYPE) != "mysql")
|
||||
$this->pdo->commit();
|
||||
|
||||
Debug::log("Migration finished, current version: " . $this->get_version(), Debug::LOG_VERBOSE);
|
||||
|
||||
Logger::log(E_USER_NOTICE, "Applied migration to version $version for {$this->migrations_table}");
|
||||
} else {
|
||||
Debug::log("Migration failed: schema file is empty or missing.", Debug::LOG_VERBOSE);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
Debug::log("Migration failed: " . $e->getMessage(), Debug::LOG_VERBOSE);
|
||||
try {
|
||||
$this->pdo->rollback();
|
||||
} catch (PDOException $ie) {
|
||||
//
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function get_max_version() : int {
|
||||
if ($this->max_version_override > 0)
|
||||
return $this->max_version_override;
|
||||
|
||||
if (isset($this->cached_max_version))
|
||||
return $this->cached_max_version;
|
||||
|
||||
$migrations = glob("{$this->migrations_path}/*.sql");
|
||||
|
||||
if (count($migrations) > 0) {
|
||||
natsort($migrations);
|
||||
|
||||
$this->cached_max_version = (int) basename(array_pop($migrations), ".sql");
|
||||
|
||||
} else {
|
||||
$this->cached_max_version = 0;
|
||||
}
|
||||
|
||||
return $this->cached_max_version;
|
||||
}
|
||||
|
||||
function is_migration_needed() : bool {
|
||||
return $this->get_version() != $this->get_max_version();
|
||||
}
|
||||
|
||||
function migrate() : bool {
|
||||
|
||||
if ($this->get_version() == -1) {
|
||||
try {
|
||||
$this->migrate_to(0);
|
||||
} catch (PDOException $e) {
|
||||
user_error("Failed to load base schema for {$this->migrations_table}: " . $e->getMessage(), E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = $this->get_version() + 1; $i <= $this->get_max_version(); $i++) {
|
||||
try {
|
||||
$this->migrate_to($i);
|
||||
} catch (PDOException $e) {
|
||||
user_error("Failed to apply migration ${i} for {$this->migrations_table}: " . $e->getMessage(), E_USER_WARNING);
|
||||
return false;
|
||||
//throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return !$this->is_migration_needed();
|
||||
}
|
||||
|
||||
private function get_lines(int $version) : array {
|
||||
if ($version > 0)
|
||||
$filename = "{$this->migrations_path}/${version}.sql";
|
||||
else
|
||||
$filename = "{$this->base_path}/{$this->base_filename}";
|
||||
|
||||
if (file_exists($filename)) {
|
||||
$lines = array_filter(preg_split("/[\r\n]/", file_get_contents($filename)),
|
||||
function ($line) {
|
||||
return strlen(trim($line)) > 0 && strpos($line, "--") !== 0;
|
||||
});
|
||||
|
||||
return array_filter(explode(";", implode("", $lines)), function ($line) {
|
||||
return strlen(trim($line)) > 0 && !in_array(strtolower($line), ["begin", "commit"]);
|
||||
});
|
||||
|
||||
} else {
|
||||
user_error("Requested schema file ${filename} not found.", E_USER_ERROR);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
<?php
|
||||
class Db_Mysqli implements IDb {
|
||||
private $link;
|
||||
private $last_error;
|
||||
|
||||
function connect($host, $user, $pass, $db, $port) {
|
||||
if ($port)
|
||||
$this->link = mysqli_connect($host, $user, $pass, $db, $port);
|
||||
else
|
||||
$this->link = mysqli_connect($host, $user, $pass, $db);
|
||||
|
||||
if ($this->link) {
|
||||
$this->init();
|
||||
|
||||
return $this->link;
|
||||
} else {
|
||||
print("Unable to connect to database (as $user to $host, database $db): " . mysqli_connect_error());
|
||||
exit(102);
|
||||
}
|
||||
}
|
||||
|
||||
function escape_string($s, $strip_tags = true) {
|
||||
if ($strip_tags) $s = strip_tags($s);
|
||||
|
||||
return mysqli_real_escape_string($this->link, $s);
|
||||
}
|
||||
|
||||
function query($query, $die_on_error = true) {
|
||||
$result = @mysqli_query($this->link, $query);
|
||||
if (!$result) {
|
||||
$this->last_error = @mysqli_error($this->link);
|
||||
|
||||
@mysqli_query($this->link, "ROLLBACK");
|
||||
user_error("Query $query failed: " . ($this->link ? $this->last_error : "No connection"),
|
||||
$die_on_error ? E_USER_ERROR : E_USER_WARNING);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function fetch_assoc($result) {
|
||||
return mysqli_fetch_assoc($result);
|
||||
}
|
||||
|
||||
|
||||
function num_rows($result) {
|
||||
return mysqli_num_rows($result);
|
||||
}
|
||||
|
||||
function fetch_result($result, $row, $param) {
|
||||
if (mysqli_data_seek($result, $row)) {
|
||||
$line = mysqli_fetch_assoc($result);
|
||||
return $line[$param];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
return mysqli_close($this->link);
|
||||
}
|
||||
|
||||
function affected_rows($result) {
|
||||
return mysqli_affected_rows($this->link);
|
||||
}
|
||||
|
||||
function last_error() {
|
||||
return mysqli_error($this->link);
|
||||
}
|
||||
|
||||
function last_query_error() {
|
||||
return $this->last_error;
|
||||
}
|
||||
|
||||
function init() {
|
||||
$this->query("SET time_zone = '+0:0'");
|
||||
|
||||
if (defined('MYSQL_CHARSET') && MYSQL_CHARSET) {
|
||||
mysqli_set_charset($this->link, MYSQL_CHARSET);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
<?php
|
||||
class Db_Pgsql implements IDb {
|
||||
private $link;
|
||||
private $last_error;
|
||||
|
||||
function connect($host, $user, $pass, $db, $port) {
|
||||
$string = "dbname=$db user=$user";
|
||||
|
||||
if ($pass) {
|
||||
$string .= " password=$pass";
|
||||
}
|
||||
|
||||
if ($host) {
|
||||
$string .= " host=$host";
|
||||
}
|
||||
|
||||
if (is_numeric($port) && $port > 0) {
|
||||
$string = "$string port=" . $port;
|
||||
}
|
||||
|
||||
$this->link = pg_connect($string);
|
||||
|
||||
if (!$this->link) {
|
||||
print("Unable to connect to database (as $user to $host, database $db):" . pg_last_error());
|
||||
exit(102);
|
||||
}
|
||||
|
||||
$this->init();
|
||||
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
function escape_string($s, $strip_tags = true) {
|
||||
if ($strip_tags) $s = strip_tags($s);
|
||||
|
||||
return pg_escape_string($s);
|
||||
}
|
||||
|
||||
function query($query, $die_on_error = true) {
|
||||
$result = @pg_query($this->link, $query);
|
||||
|
||||
if (!$result) {
|
||||
$this->last_error = @pg_last_error($this->link);
|
||||
|
||||
@pg_query($this->link, "ROLLBACK");
|
||||
$query = htmlspecialchars($query); // just in case
|
||||
user_error("Query $query failed: " . ($this->link ? $this->last_error : "No connection"),
|
||||
$die_on_error ? E_USER_ERROR : E_USER_WARNING);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function fetch_assoc($result) {
|
||||
return pg_fetch_assoc($result);
|
||||
}
|
||||
|
||||
|
||||
function num_rows($result) {
|
||||
return pg_num_rows($result);
|
||||
}
|
||||
|
||||
function fetch_result($result, $row, $param) {
|
||||
return pg_fetch_result($result, $row, $param);
|
||||
}
|
||||
|
||||
function close() {
|
||||
return pg_close($this->link);
|
||||
}
|
||||
|
||||
function affected_rows($result) {
|
||||
return pg_affected_rows($result);
|
||||
}
|
||||
|
||||
function last_error() {
|
||||
return pg_last_error($this->link);
|
||||
}
|
||||
|
||||
function last_query_error() {
|
||||
return $this->last_error;
|
||||
}
|
||||
|
||||
function init() {
|
||||
$this->query("set client_encoding = 'UTF-8'");
|
||||
pg_set_client_encoding("UNICODE");
|
||||
$this->query("set datestyle = 'ISO, european'");
|
||||
$this->query("set TIME ZONE 0");
|
||||
$this->query("set cpu_tuple_cost = 0.5");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,173 +1,12 @@
|
||||
<?php
|
||||
class Db_Prefs {
|
||||
private $pdo;
|
||||
private static $instance;
|
||||
private $cache;
|
||||
|
||||
function __construct() {
|
||||
$this->pdo = Db::pdo();
|
||||
$this->cache = array();
|
||||
|
||||
if ($_SESSION["uid"]) $this->cache();
|
||||
}
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
public static function get() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
function cache() {
|
||||
$user_id = $_SESSION["uid"];
|
||||
@$profile = $_SESSION["profile"];
|
||||
|
||||
if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null;
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT
|
||||
value,ttrss_prefs_types.type_name as type_name,ttrss_prefs.pref_name AS pref_name
|
||||
FROM
|
||||
ttrss_user_prefs,ttrss_prefs,ttrss_prefs_types
|
||||
WHERE
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND
|
||||
ttrss_prefs.pref_name NOT LIKE '_MOBILE%' AND
|
||||
ttrss_prefs_types.id = type_id AND
|
||||
owner_uid = :uid AND
|
||||
ttrss_user_prefs.pref_name = ttrss_prefs.pref_name");
|
||||
|
||||
$sth->execute([":profile" => $profile, ":uid" => $user_id]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
if ($user_id == $_SESSION["uid"]) {
|
||||
$pref_name = $line["pref_name"];
|
||||
|
||||
$this->cache[$pref_name]["type"] = $line["type_name"];
|
||||
$this->cache[$pref_name]["value"] = $line["value"];
|
||||
}
|
||||
}
|
||||
}
|
||||
// this class is a stub for the time being (to be removed)
|
||||
|
||||
function read($pref_name, $user_id = false, $die_on_error = false) {
|
||||
|
||||
if (!$user_id) {
|
||||
$user_id = $_SESSION["uid"];
|
||||
@$profile = $_SESSION["profile"];
|
||||
} else {
|
||||
$profile = false;
|
||||
}
|
||||
|
||||
if ($user_id == $_SESSION['uid'] && isset($this->cache[$pref_name])) {
|
||||
$tuple = $this->cache[$pref_name];
|
||||
return $this->convert($tuple["value"], $tuple["type"]);
|
||||
}
|
||||
|
||||
if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null;
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT
|
||||
value,ttrss_prefs_types.type_name as type_name
|
||||
FROM
|
||||
ttrss_user_prefs,ttrss_prefs,ttrss_prefs_types
|
||||
WHERE
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND
|
||||
ttrss_user_prefs.pref_name = :pref_name AND
|
||||
ttrss_prefs_types.id = type_id AND
|
||||
owner_uid = :uid AND
|
||||
ttrss_user_prefs.pref_name = ttrss_prefs.pref_name");
|
||||
$sth->execute([":uid" => $user_id, ":profile" => $profile, ":pref_name" => $pref_name]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$value = $row["value"];
|
||||
$type_name = $row["type_name"];
|
||||
|
||||
if ($user_id == $_SESSION["uid"]) {
|
||||
$this->cache[$pref_name]["type"] = $type_name;
|
||||
$this->cache[$pref_name]["value"] = $value;
|
||||
}
|
||||
|
||||
return $this->convert($value, $type_name);
|
||||
|
||||
} else if ($die_on_error) {
|
||||
user_error("Fatal error, unknown preferences key: $pref_name (owner: $user_id)", E_USER_ERROR);
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function convert($value, $type_name) {
|
||||
if ($type_name == "bool") {
|
||||
return $value == "true";
|
||||
} else if ($type_name == "integer") {
|
||||
return (int)$value;
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
return get_pref($pref_name, $user_id);
|
||||
}
|
||||
|
||||
function write($pref_name, $value, $user_id = false, $strip_tags = true) {
|
||||
if ($strip_tags) $value = strip_tags($value);
|
||||
|
||||
if (!$user_id) {
|
||||
$user_id = $_SESSION["uid"];
|
||||
@$profile = $_SESSION["profile"];
|
||||
} else {
|
||||
$profile = null;
|
||||
}
|
||||
|
||||
if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null;
|
||||
|
||||
$type_name = "";
|
||||
$current_value = "";
|
||||
|
||||
if (isset($this->cache[$pref_name])) {
|
||||
$type_name = $this->cache[$pref_name]["type"];
|
||||
$current_value = $this->cache[$pref_name]["value"];
|
||||
}
|
||||
|
||||
if (!$type_name) {
|
||||
$sth = $this->pdo->prepare("SELECT type_name
|
||||
FROM ttrss_prefs,ttrss_prefs_types
|
||||
WHERE pref_name = ? AND type_id = ttrss_prefs_types.id");
|
||||
$sth->execute([$pref_name]);
|
||||
|
||||
if ($row = $sth->fetch())
|
||||
$type_name = $row["type_name"];
|
||||
|
||||
} else if ($current_value == $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($type_name) {
|
||||
if ($type_name == "bool") {
|
||||
if ($value == "1" || $value == "true") {
|
||||
$value = "true";
|
||||
} else {
|
||||
$value = "false";
|
||||
}
|
||||
} else if ($type_name == "integer") {
|
||||
$value = (int)$value;
|
||||
}
|
||||
|
||||
if ($pref_name == 'USER_TIMEZONE' && $value == '') {
|
||||
$value = 'UTC';
|
||||
}
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_prefs SET
|
||||
value = :value WHERE pref_name = :pref_name
|
||||
AND (profile = :profile OR (:profile IS NULL AND profile IS NULL))
|
||||
AND owner_uid = :uid");
|
||||
|
||||
$sth->execute([":pref_name" => $pref_name, ":value" => $value, ":uid" => $user_id, ":profile" => $profile]);
|
||||
|
||||
if ($user_id == $_SESSION["uid"]) {
|
||||
$this->cache[$pref_name]["type"] = $type_name;
|
||||
$this->cache[$pref_name]["value"] = $value;
|
||||
}
|
||||
}
|
||||
return set_pref($pref_name, $value, $user_id, $strip_tags);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,83 +0,0 @@
|
||||
<?php
|
||||
class DbUpdater {
|
||||
|
||||
private $pdo;
|
||||
private $db_type;
|
||||
private $need_version;
|
||||
|
||||
function __construct($pdo, $db_type, $need_version) {
|
||||
$this->pdo = Db::pdo(); //$pdo;
|
||||
$this->db_type = $db_type;
|
||||
$this->need_version = (int) $need_version;
|
||||
}
|
||||
|
||||
function getSchemaVersion() {
|
||||
$row = $this->pdo->query("SELECT schema_version FROM ttrss_version")->fetch();
|
||||
return (int) $row['schema_version'];
|
||||
}
|
||||
|
||||
function isUpdateRequired() {
|
||||
return $this->getSchemaVersion() < $this->need_version;
|
||||
}
|
||||
|
||||
function getSchemaLines($version) {
|
||||
$filename = "schema/versions/".$this->db_type."/$version.sql";
|
||||
|
||||
if (file_exists($filename)) {
|
||||
return explode(";", preg_replace("/[\r\n]/", "", file_get_contents($filename)));
|
||||
} else {
|
||||
user_error("DB Updater: schema file for version $version is not found.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function performUpdateTo($version, $html_output = true) {
|
||||
if ($this->getSchemaVersion() == $version - 1) {
|
||||
|
||||
$lines = $this->getSchemaLines($version);
|
||||
|
||||
if (is_array($lines)) {
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, "--") !== 0 && $line) {
|
||||
|
||||
if ($html_output)
|
||||
print "<pre>$line</pre>";
|
||||
else
|
||||
Debug::log("> $line");
|
||||
|
||||
try {
|
||||
$this->pdo->query($line); // PDO returns errors as exceptions now
|
||||
} catch (PDOException $e) {
|
||||
if ($html_output) {
|
||||
print "<div class='text-error'>Error: " . $e->getMessage() . "</div>";
|
||||
} else {
|
||||
Debug::log("Error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$db_version = $this->getSchemaVersion();
|
||||
|
||||
if ($db_version == $version) {
|
||||
$this->pdo->commit();
|
||||
return true;
|
||||
} else {
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,207 +0,0 @@
|
||||
<?php
|
||||
class Dlg extends Handler_Protected {
|
||||
private $param;
|
||||
private $params;
|
||||
|
||||
function before($method) {
|
||||
if (parent::before($method)) {
|
||||
header("Content-Type: text/html"); # required for iframe
|
||||
|
||||
$this->param = $_REQUEST["param"];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function importOpml() {
|
||||
print_notice("If you have imported labels and/or filters, you might need to reload preferences to see your new data.");
|
||||
|
||||
print "<div class='panel panel-scrollable'>";
|
||||
|
||||
$opml = new Opml($_REQUEST);
|
||||
|
||||
$opml->opml_import($_SESSION["uid"]);
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
print "<button dojoType='dijit.form.Button'
|
||||
onclick=\"dijit.byId('opmlImportDlg').execute()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</footer>";
|
||||
|
||||
print "</div>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function pubOPMLUrl() {
|
||||
$url_path = Opml::opml_publish_url();
|
||||
|
||||
print "<header>" . __("Your Public OPML URL is:") . "</header>";
|
||||
|
||||
print "<section>";
|
||||
|
||||
print "<div class='panel text-center'>";
|
||||
print "<a id='pub_opml_url' href='$url_path' target='_blank'>$url_path</a>";
|
||||
print "</div>";
|
||||
|
||||
print "</section>";
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' onclick=\"return Helpers.OPML.changeKey()\">".
|
||||
__('Generate new URL')."</button> ";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' onclick=\"return CommonDialogs.closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
|
||||
print "</footer>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function explainError() {
|
||||
print "<div class=\"errorExplained\">";
|
||||
|
||||
if ($this->param == 1) {
|
||||
print __("Update daemon is enabled in configuration, but daemon process is not running, which prevents all feeds from updating. Please start the daemon process or contact instance owner.");
|
||||
|
||||
$stamp = (int) file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
|
||||
|
||||
print "<p>" . __("Last update:") . " " . date("Y.m.d, G:i", $stamp);
|
||||
|
||||
}
|
||||
|
||||
if ($this->param == 3) {
|
||||
print __("Update daemon is taking too long to perform a feed update. This could indicate a problem like crash or a hang. Please check the daemon process or contact instance owner.");
|
||||
|
||||
$stamp = (int) file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
|
||||
|
||||
print "<p>" . __("Last update:") . " " . date("Y.m.d, G:i", $stamp);
|
||||
|
||||
}
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
print "<button onclick=\"return CommonDialogs.closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</footer>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function printTagCloud() {
|
||||
print "<div class='panel text-center'>";
|
||||
|
||||
// from here: http://www.roscripts.com/Create_tag_cloud-71.html
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT tag_name, COUNT(post_int_id) AS count
|
||||
FROM ttrss_tags WHERE owner_uid = ?
|
||||
GROUP BY tag_name ORDER BY count DESC LIMIT 50");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
$tags = array();
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$tags[$line["tag_name"]] = $line["count"];
|
||||
}
|
||||
|
||||
if(count($tags) == 0 ){ return; }
|
||||
|
||||
ksort($tags);
|
||||
|
||||
$max_size = 32; // max font size in pixels
|
||||
$min_size = 11; // min font size in pixels
|
||||
|
||||
// largest and smallest array values
|
||||
$max_qty = max(array_values($tags));
|
||||
$min_qty = min(array_values($tags));
|
||||
|
||||
// find the range of values
|
||||
$spread = $max_qty - $min_qty;
|
||||
if ($spread == 0) { // we don't want to divide by zero
|
||||
$spread = 1;
|
||||
}
|
||||
|
||||
// set the font-size increment
|
||||
$step = ($max_size - $min_size) / ($spread);
|
||||
|
||||
// loop through the tag array
|
||||
foreach ($tags as $key => $value) {
|
||||
// calculate font-size
|
||||
// find the $value in excess of $min_qty
|
||||
// multiply by the font-size increment ($size)
|
||||
// and add the $min_size set above
|
||||
$size = round($min_size + (($value - $min_qty) * $step));
|
||||
|
||||
$key_escaped = str_replace("'", "\\'", $key);
|
||||
|
||||
echo "<a href=\"#\" onclick=\"Feeds.open({feed:'$key_escaped'}) \" style=\"font-size: " .
|
||||
$size . "px\" title=\"$value articles tagged with " .
|
||||
$key . '">' . $key . '</a> ';
|
||||
}
|
||||
|
||||
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
print "<button dojoType='dijit.form.Button'
|
||||
onclick=\"return CommonDialogs.closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</footer>";
|
||||
|
||||
}
|
||||
|
||||
function generatedFeed() {
|
||||
|
||||
$this->params = explode(":", $this->param, 3);
|
||||
$feed_id = $this->params[0];
|
||||
$is_cat = (bool) $this->params[1];
|
||||
|
||||
$key = Feeds::get_feed_access_key($feed_id, $is_cat);
|
||||
|
||||
$url_path = htmlspecialchars($this->params[2]) . "&key=" . $key;
|
||||
|
||||
$feed_title = Feeds::getFeedTitle($feed_id, $is_cat);
|
||||
|
||||
print "<header>".T_sprintf("%s can be accessed via the following secret URL:", $feed_title)."</header>";
|
||||
|
||||
print "<section>";
|
||||
print "<div class='panel text-center'>";
|
||||
print "<a id='gen_feed_url' href='$url_path' target='_blank'>$url_path</a>";
|
||||
print "</div>";
|
||||
print "</section>";
|
||||
|
||||
print "<footer>";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' style='float : left' class='alt-info' onclick='window.open(\"https://tt-rss.org/wiki/GeneratedFeeds\")'>
|
||||
<i class='material-icons'>help</i> ".__("More info...")."</button>";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' onclick=\"return CommonDialogs.genUrlChangeKey('$feed_id', '$is_cat')\">".
|
||||
__('Generate new URL')."</button> ";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' onclick=\"return CommonDialogs.closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
|
||||
print "</footer>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function defaultPasswordWarning() {
|
||||
|
||||
print_warning(__("You are using default tt-rss password. Please change it in the Preferences (Personal data / Authentication)."));
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
print "<button dojoType='dijit.form.Button' class='alt-primary'
|
||||
onclick=\"document.location.href = 'prefs.php'\">".
|
||||
__('Open Preferences')."</button> ";
|
||||
print "<button dojoType='dijit.form.Button'
|
||||
onclick=\"return dijit.byId('defaultPasswordDlg').hide();\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</footeer>";
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
class Errors {
|
||||
const E_SUCCESS = "E_SUCCESS";
|
||||
const E_UNAUTHORIZED = "E_UNAUTHORIZED";
|
||||
const E_UNKNOWN_METHOD = "E_UNKNOWN_METHOD";
|
||||
const E_UNKNOWN_PLUGIN = "E_UNKNOWN_PLUGIN";
|
||||
const E_SCHEMA_MISMATCH = "E_SCHEMA_MISMATCH";
|
||||
const E_URL_SCHEME_MISMATCH = "E_URL_SCHEME_MISMATCH";
|
||||
|
||||
static function to_json(string $code, array $params = []) {
|
||||
return json_encode(["error" => ["code" => $code, "params" => $params]]);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
class Handler_Administrative extends Handler_Protected {
|
||||
function before($method) {
|
||||
if (parent::before($method)) {
|
||||
if (($_SESSION["access_level"] ?? 0) >= 10) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
interface IAuthModule {
|
||||
function authenticate($login, $password); // + optional third parameter: $service
|
||||
function hook_auth_user(...$args); // compatibility wrapper due to how hooks work
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
interface IDb {
|
||||
function connect($host, $user, $pass, $db, $port);
|
||||
function escape_string($s, $strip_tags = true);
|
||||
function query($query, $die_on_error = true);
|
||||
function fetch_assoc($result);
|
||||
function num_rows($result);
|
||||
function fetch_result($result, $row, $param);
|
||||
function close();
|
||||
function affected_rows($result);
|
||||
function last_error();
|
||||
function last_query_error();
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
interface Logger_Adapter {
|
||||
function log_error(int $errno, string $errstr, string $file, int $line, $context);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,413 @@
|
||||
<?php
|
||||
class Prefs {
|
||||
// (this is the database-backed version of Config.php)
|
||||
|
||||
const PURGE_OLD_DAYS = "PURGE_OLD_DAYS";
|
||||
const DEFAULT_UPDATE_INTERVAL = "DEFAULT_UPDATE_INTERVAL";
|
||||
//const DEFAULT_ARTICLE_LIMIT = "DEFAULT_ARTICLE_LIMIT";
|
||||
//const ALLOW_DUPLICATE_POSTS = "ALLOW_DUPLICATE_POSTS";
|
||||
const ENABLE_FEED_CATS = "ENABLE_FEED_CATS";
|
||||
const SHOW_CONTENT_PREVIEW = "SHOW_CONTENT_PREVIEW";
|
||||
const SHORT_DATE_FORMAT = "SHORT_DATE_FORMAT";
|
||||
const LONG_DATE_FORMAT = "LONG_DATE_FORMAT";
|
||||
const COMBINED_DISPLAY_MODE = "COMBINED_DISPLAY_MODE";
|
||||
const HIDE_READ_FEEDS = "HIDE_READ_FEEDS";
|
||||
const ON_CATCHUP_SHOW_NEXT_FEED = "ON_CATCHUP_SHOW_NEXT_FEED";
|
||||
const FEEDS_SORT_BY_UNREAD = "FEEDS_SORT_BY_UNREAD";
|
||||
const REVERSE_HEADLINES = "REVERSE_HEADLINES";
|
||||
const DIGEST_ENABLE = "DIGEST_ENABLE";
|
||||
const CONFIRM_FEED_CATCHUP = "CONFIRM_FEED_CATCHUP";
|
||||
const CDM_AUTO_CATCHUP = "CDM_AUTO_CATCHUP";
|
||||
const _DEFAULT_VIEW_MODE = "_DEFAULT_VIEW_MODE";
|
||||
const _DEFAULT_VIEW_LIMIT = "_DEFAULT_VIEW_LIMIT";
|
||||
//const _PREFS_ACTIVE_TAB = "_PREFS_ACTIVE_TAB";
|
||||
//const STRIP_UNSAFE_TAGS = "STRIP_UNSAFE_TAGS";
|
||||
const BLACKLISTED_TAGS = "BLACKLISTED_TAGS";
|
||||
const FRESH_ARTICLE_MAX_AGE = "FRESH_ARTICLE_MAX_AGE";
|
||||
const DIGEST_CATCHUP = "DIGEST_CATCHUP";
|
||||
const CDM_EXPANDED = "CDM_EXPANDED";
|
||||
const PURGE_UNREAD_ARTICLES = "PURGE_UNREAD_ARTICLES";
|
||||
const HIDE_READ_SHOWS_SPECIAL = "HIDE_READ_SHOWS_SPECIAL";
|
||||
const VFEED_GROUP_BY_FEED = "VFEED_GROUP_BY_FEED";
|
||||
const STRIP_IMAGES = "STRIP_IMAGES";
|
||||
const _DEFAULT_VIEW_ORDER_BY = "_DEFAULT_VIEW_ORDER_BY";
|
||||
const ENABLE_API_ACCESS = "ENABLE_API_ACCESS";
|
||||
//const _COLLAPSED_SPECIAL = "_COLLAPSED_SPECIAL";
|
||||
//const _COLLAPSED_LABELS = "_COLLAPSED_LABELS";
|
||||
//const _COLLAPSED_UNCAT = "_COLLAPSED_UNCAT";
|
||||
//const _COLLAPSED_FEEDLIST = "_COLLAPSED_FEEDLIST";
|
||||
//const _MOBILE_ENABLE_CATS = "_MOBILE_ENABLE_CATS";
|
||||
//const _MOBILE_SHOW_IMAGES = "_MOBILE_SHOW_IMAGES";
|
||||
//const _MOBILE_HIDE_READ = "_MOBILE_HIDE_READ";
|
||||
//const _MOBILE_SORT_FEEDS_UNREAD = "_MOBILE_SORT_FEEDS_UNREAD";
|
||||
//const _MOBILE_BROWSE_CATS = "_MOBILE_BROWSE_CATS";
|
||||
//const _THEME_ID = "_THEME_ID";
|
||||
const USER_TIMEZONE = "USER_TIMEZONE";
|
||||
const USER_STYLESHEET = "USER_STYLESHEET";
|
||||
//const SORT_HEADLINES_BY_FEED_DATE = "SORT_HEADLINES_BY_FEED_DATE";
|
||||
const SSL_CERT_SERIAL = "SSL_CERT_SERIAL";
|
||||
const DIGEST_PREFERRED_TIME = "DIGEST_PREFERRED_TIME";
|
||||
//const _PREFS_SHOW_EMPTY_CATS = "_PREFS_SHOW_EMPTY_CATS";
|
||||
const _DEFAULT_INCLUDE_CHILDREN = "_DEFAULT_INCLUDE_CHILDREN";
|
||||
//const AUTO_ASSIGN_LABELS = "AUTO_ASSIGN_LABELS";
|
||||
const _ENABLED_PLUGINS = "_ENABLED_PLUGINS";
|
||||
//const _MOBILE_REVERSE_HEADLINES = "_MOBILE_REVERSE_HEADLINES";
|
||||
const USER_CSS_THEME = "USER_CSS_THEME";
|
||||
const USER_LANGUAGE = "USER_LANGUAGE";
|
||||
const DEFAULT_SEARCH_LANGUAGE = "DEFAULT_SEARCH_LANGUAGE";
|
||||
const _PREFS_MIGRATED = "_PREFS_MIGRATED";
|
||||
const HEADLINES_NO_DISTINCT = "HEADLINES_NO_DISTINCT";
|
||||
const DEBUG_HEADLINE_IDS = "DEBUG_HEADLINE_IDS";
|
||||
const DISABLE_CONDITIONAL_COUNTERS = "DISABLE_CONDITIONAL_COUNTERS";
|
||||
const WIDESCREEN_MODE = "WIDESCREEN_MODE";
|
||||
const CDM_ENABLE_GRID = "CDM_ENABLE_GRID";
|
||||
|
||||
private const _DEFAULTS = [
|
||||
Prefs::PURGE_OLD_DAYS => [ 60, Config::T_INT ],
|
||||
Prefs::DEFAULT_UPDATE_INTERVAL => [ 30, Config::T_INT ],
|
||||
//Prefs::DEFAULT_ARTICLE_LIMIT => [ 30, Config::T_INT ],
|
||||
//Prefs::ALLOW_DUPLICATE_POSTS => [ false, Config::T_BOOL ],
|
||||
Prefs::ENABLE_FEED_CATS => [ true, Config::T_BOOL ],
|
||||
Prefs::SHOW_CONTENT_PREVIEW => [ true, Config::T_BOOL ],
|
||||
Prefs::SHORT_DATE_FORMAT => [ "M d, G:i", Config::T_STRING ],
|
||||
Prefs::LONG_DATE_FORMAT => [ "D, M d Y - G:i", Config::T_STRING ],
|
||||
Prefs::COMBINED_DISPLAY_MODE => [ true, Config::T_BOOL ],
|
||||
Prefs::HIDE_READ_FEEDS => [ false, Config::T_BOOL ],
|
||||
Prefs::ON_CATCHUP_SHOW_NEXT_FEED => [ false, Config::T_BOOL ],
|
||||
Prefs::FEEDS_SORT_BY_UNREAD => [ false, Config::T_BOOL ],
|
||||
Prefs::REVERSE_HEADLINES => [ false, Config::T_BOOL ],
|
||||
Prefs::DIGEST_ENABLE => [ false, Config::T_BOOL ],
|
||||
Prefs::CONFIRM_FEED_CATCHUP => [ true, Config::T_BOOL ],
|
||||
Prefs::CDM_AUTO_CATCHUP => [ false, Config::T_BOOL ],
|
||||
Prefs::_DEFAULT_VIEW_MODE => [ "adaptive", Config::T_STRING ],
|
||||
Prefs::_DEFAULT_VIEW_LIMIT => [ 30, Config::T_INT ],
|
||||
//Prefs::_PREFS_ACTIVE_TAB => [ "", Config::T_STRING ],
|
||||
//Prefs::STRIP_UNSAFE_TAGS => [ true, Config::T_BOOL ],
|
||||
Prefs::BLACKLISTED_TAGS => [ 'main, generic, misc, uncategorized, blog, blogroll, general, news', Config::T_STRING ],
|
||||
Prefs::FRESH_ARTICLE_MAX_AGE => [ 24, Config::T_INT ],
|
||||
Prefs::DIGEST_CATCHUP => [ false, Config::T_BOOL ],
|
||||
Prefs::CDM_EXPANDED => [ true, Config::T_BOOL ],
|
||||
Prefs::PURGE_UNREAD_ARTICLES => [ true, Config::T_BOOL ],
|
||||
Prefs::HIDE_READ_SHOWS_SPECIAL => [ true, Config::T_BOOL ],
|
||||
Prefs::VFEED_GROUP_BY_FEED => [ false, Config::T_BOOL ],
|
||||
Prefs::STRIP_IMAGES => [ false, Config::T_BOOL ],
|
||||
Prefs::_DEFAULT_VIEW_ORDER_BY => [ "default", Config::T_STRING ],
|
||||
Prefs::ENABLE_API_ACCESS => [ false, Config::T_BOOL ],
|
||||
//Prefs::_COLLAPSED_SPECIAL => [ false, Config::T_BOOL ],
|
||||
//Prefs::_COLLAPSED_LABELS => [ false, Config::T_BOOL ],
|
||||
//Prefs::_COLLAPSED_UNCAT => [ false, Config::T_BOOL ],
|
||||
//Prefs::_COLLAPSED_FEEDLIST => [ false, Config::T_BOOL ],
|
||||
//Prefs::_MOBILE_ENABLE_CATS => [ false, Config::T_BOOL ],
|
||||
//Prefs::_MOBILE_SHOW_IMAGES => [ false, Config::T_BOOL ],
|
||||
//Prefs::_MOBILE_HIDE_READ => [ false, Config::T_BOOL ],
|
||||
//Prefs::_MOBILE_SORT_FEEDS_UNREAD => [ false, Config::T_BOOL ],
|
||||
//Prefs::_MOBILE_BROWSE_CATS => [ true, Config::T_BOOL ],
|
||||
//Prefs::_THEME_ID => [ 0, Config::T_BOOL ],
|
||||
Prefs::USER_TIMEZONE => [ "Automatic", Config::T_STRING ],
|
||||
Prefs::USER_STYLESHEET => [ "", Config::T_STRING ],
|
||||
//Prefs::SORT_HEADLINES_BY_FEED_DATE => [ false, Config::T_BOOL ],
|
||||
Prefs::SSL_CERT_SERIAL => [ "", Config::T_STRING ],
|
||||
Prefs::DIGEST_PREFERRED_TIME => [ "00:00", Config::T_STRING ],
|
||||
//Prefs::_PREFS_SHOW_EMPTY_CATS => [ false, Config::T_BOOL ],
|
||||
Prefs::_DEFAULT_INCLUDE_CHILDREN => [ false, Config::T_BOOL ],
|
||||
//Prefs::AUTO_ASSIGN_LABELS => [ false, Config::T_BOOL ],
|
||||
Prefs::_ENABLED_PLUGINS => [ "", Config::T_STRING ],
|
||||
//Prefs::_MOBILE_REVERSE_HEADLINES => [ false, Config::T_BOOL ],
|
||||
Prefs::USER_CSS_THEME => [ "" , Config::T_STRING ],
|
||||
Prefs::USER_LANGUAGE => [ "" , Config::T_STRING ],
|
||||
Prefs::DEFAULT_SEARCH_LANGUAGE => [ "" , Config::T_STRING ],
|
||||
Prefs::_PREFS_MIGRATED => [ false, Config::T_BOOL ],
|
||||
Prefs::HEADLINES_NO_DISTINCT => [ false, Config::T_BOOL ],
|
||||
Prefs::DEBUG_HEADLINE_IDS => [ false, Config::T_BOOL ],
|
||||
Prefs::DISABLE_CONDITIONAL_COUNTERS => [ false, Config::T_BOOL ],
|
||||
Prefs::WIDESCREEN_MODE => [ false, Config::T_BOOL ],
|
||||
Prefs::CDM_ENABLE_GRID => [ false, Config::T_BOOL ],
|
||||
];
|
||||
|
||||
const _PROFILE_BLACKLIST = [
|
||||
//Prefs::ALLOW_DUPLICATE_POSTS,
|
||||
Prefs::PURGE_OLD_DAYS,
|
||||
Prefs::PURGE_UNREAD_ARTICLES,
|
||||
Prefs::DIGEST_ENABLE,
|
||||
Prefs::DIGEST_CATCHUP,
|
||||
Prefs::BLACKLISTED_TAGS,
|
||||
Prefs::ENABLE_API_ACCESS,
|
||||
//Prefs::UPDATE_POST_ON_CHECKSUM_CHANGE,
|
||||
Prefs::DEFAULT_UPDATE_INTERVAL,
|
||||
Prefs::USER_TIMEZONE,
|
||||
//Prefs::SORT_HEADLINES_BY_FEED_DATE,
|
||||
Prefs::SSL_CERT_SERIAL,
|
||||
Prefs::DIGEST_PREFERRED_TIME,
|
||||
Prefs::_PREFS_MIGRATED
|
||||
];
|
||||
|
||||
private static $instance;
|
||||
private $cache = [];
|
||||
|
||||
/** @var PDO */
|
||||
private $pdo;
|
||||
|
||||
public static function get_instance() : Prefs {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
static function is_valid(string $pref_name) {
|
||||
return isset(self::_DEFAULTS[$pref_name]);
|
||||
}
|
||||
|
||||
static function get_default(string $pref_name) {
|
||||
if (self::is_valid($pref_name))
|
||||
return self::_DEFAULTS[$pref_name][0];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
function __construct() {
|
||||
$this->pdo = Db::pdo();
|
||||
|
||||
if (!empty($_SESSION["uid"])) {
|
||||
$owner_uid = (int) $_SESSION["uid"];
|
||||
$profile_id = $_SESSION["profile"] ?? null;
|
||||
|
||||
$this->cache_all($owner_uid, $profile_id);
|
||||
$this->migrate($owner_uid, $profile_id);
|
||||
};
|
||||
}
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
static function get_all(int $owner_uid, int $profile_id = null) {
|
||||
return self::get_instance()->_get_all($owner_uid, $profile_id);
|
||||
}
|
||||
|
||||
private function _get_all(int $owner_uid, int $profile_id = null) {
|
||||
$rv = [];
|
||||
|
||||
$ref = new ReflectionClass(get_class($this));
|
||||
|
||||
foreach ($ref->getConstants() as $const => $cvalue) {
|
||||
if (isset($this::_DEFAULTS[$const])) {
|
||||
list ($def_val, $type_hint) = $this::_DEFAULTS[$const];
|
||||
|
||||
array_push($rv, [
|
||||
"pref_name" => $const,
|
||||
"value" => $this->_get($const, $owner_uid, $profile_id),
|
||||
"type_hint" => $type_hint,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $rv;
|
||||
}
|
||||
|
||||
private function cache_all(int $owner_uid, $profile_id = null) {
|
||||
if (!$profile_id) $profile_id = null;
|
||||
|
||||
// fill cache with defaults
|
||||
$ref = new ReflectionClass(get_class($this));
|
||||
foreach ($ref->getConstants() as $const => $cvalue) {
|
||||
if (isset($this::_DEFAULTS[$const])) {
|
||||
list ($def_val, $type_hint) = $this::_DEFAULTS[$const];
|
||||
|
||||
$this->_set_cache($const, $def_val, $owner_uid, $profile_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (get_schema_version() >= 141) {
|
||||
// fill in any overrides from the database
|
||||
$sth = $this->pdo->prepare("SELECT pref_name, value FROM ttrss_user_prefs2
|
||||
WHERE owner_uid = :uid AND
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL))");
|
||||
|
||||
$sth->execute(["uid" => $owner_uid, "profile" => $profile_id]);
|
||||
|
||||
while ($row = $sth->fetch()) {
|
||||
$this->_set_cache($row["pref_name"], $row["value"], $owner_uid, $profile_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function get(string $pref_name, int $owner_uid, int $profile_id = null) {
|
||||
return self::get_instance()->_get($pref_name, $owner_uid, $profile_id);
|
||||
}
|
||||
|
||||
private function _get(string $pref_name, int $owner_uid, int $profile_id = null) {
|
||||
if (isset(self::_DEFAULTS[$pref_name])) {
|
||||
if (!$profile_id || in_array($pref_name, self::_PROFILE_BLACKLIST)) $profile_id = null;
|
||||
|
||||
list ($def_val, $type_hint) = self::_DEFAULTS[$pref_name];
|
||||
|
||||
$cached_value = $this->_get_cache($pref_name, $owner_uid, $profile_id);
|
||||
|
||||
if ($this->_is_cached($pref_name, $owner_uid, $profile_id)) {
|
||||
$cached_value = $this->_get_cache($pref_name, $owner_uid, $profile_id);
|
||||
return Config::cast_to($cached_value, $type_hint);
|
||||
} else if (get_schema_version() >= 141) {
|
||||
$sth = $this->pdo->prepare("SELECT value FROM ttrss_user_prefs2
|
||||
WHERE pref_name = :name AND owner_uid = :uid AND
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL))");
|
||||
|
||||
$sth->execute(["uid" => $owner_uid, "profile" => $profile_id, "name" => $pref_name ]);
|
||||
|
||||
if ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||
$this->_set_cache($pref_name, $row["value"], $owner_uid, $profile_id);
|
||||
|
||||
return Config::cast_to($row["value"], $type_hint);
|
||||
} else {
|
||||
$this->_set_cache($pref_name, $def_val, $owner_uid, $profile_id);
|
||||
|
||||
return $def_val;
|
||||
}
|
||||
} else {
|
||||
return Config::cast_to($def_val, $type_hint);
|
||||
|
||||
}
|
||||
} else {
|
||||
user_error("Attempt to get invalid preference key: $pref_name (UID: $owner_uid, profile: $profile_id)", E_USER_WARNING);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function _is_cached(string $pref_name, int $owner_uid, int $profile_id = null) {
|
||||
$cache_key = sprintf("%d/%d/%s", $owner_uid, $profile_id, $pref_name);
|
||||
return isset($this->cache[$cache_key]);
|
||||
}
|
||||
|
||||
private function _get_cache(string $pref_name, int $owner_uid, int $profile_id = null) {
|
||||
$cache_key = sprintf("%d/%d/%s", $owner_uid, $profile_id, $pref_name);
|
||||
|
||||
if (isset($this->cache[$cache_key]))
|
||||
return $this->cache[$cache_key];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function _set_cache(string $pref_name, $value, int $owner_uid, int $profile_id = null) {
|
||||
$cache_key = sprintf("%d/%d/%s", $owner_uid, $profile_id, $pref_name);
|
||||
|
||||
$this->cache[$cache_key] = $value;
|
||||
}
|
||||
|
||||
static function set(string $pref_name, $value, int $owner_uid, int $profile_id = null, bool $strip_tags = true) {
|
||||
return self::get_instance()->_set($pref_name, $value, $owner_uid, $profile_id);
|
||||
}
|
||||
|
||||
private function _delete(string $pref_name, int $owner_uid, int $profile_id = null) {
|
||||
$sth = $this->pdo->prepare("DELETE FROM ttrss_user_prefs2
|
||||
WHERE pref_name = :name AND owner_uid = :uid AND
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL))");
|
||||
|
||||
return $sth->execute(["uid" => $owner_uid, "profile" => $profile_id, "name" => $pref_name ]);
|
||||
}
|
||||
|
||||
private function _set(string $pref_name, $value, int $owner_uid, int $profile_id = null, bool $strip_tags = true) {
|
||||
if (!$profile_id) $profile_id = null;
|
||||
|
||||
if ($profile_id && in_array($pref_name, self::_PROFILE_BLACKLIST))
|
||||
return false;
|
||||
|
||||
if (isset(self::_DEFAULTS[$pref_name])) {
|
||||
list ($def_val, $type_hint) = self::_DEFAULTS[$pref_name];
|
||||
|
||||
if ($strip_tags)
|
||||
$value = trim(strip_tags($value));
|
||||
|
||||
$value = Config::cast_to($value, $type_hint);
|
||||
|
||||
// is this a good idea or not? probably not (user-set value remains user-set even if its at default)
|
||||
//if ($value == $def_val)
|
||||
// return $this->_delete($pref_name, $owner_uid, $profile_id);
|
||||
|
||||
if ($value == $this->_get($pref_name, $owner_uid, $profile_id))
|
||||
return false;
|
||||
|
||||
$this->_set_cache($pref_name, $value, $owner_uid, $profile_id);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT COUNT(pref_name) AS count FROM ttrss_user_prefs2
|
||||
WHERE pref_name = :name AND owner_uid = :uid AND
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL))");
|
||||
$sth->execute(["uid" => $owner_uid, "profile" => $profile_id, "name" => $pref_name ]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
if ($row["count"] == 0) {
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_user_prefs2
|
||||
(pref_name, value, owner_uid, profile)
|
||||
VALUES
|
||||
(:name, :value, :uid, :profile)");
|
||||
|
||||
return $sth->execute(["uid" => $owner_uid, "profile" => $profile_id, "name" => $pref_name, "value" => $value ]);
|
||||
|
||||
} else {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_prefs2
|
||||
SET value = :value
|
||||
WHERE pref_name = :name AND owner_uid = :uid AND
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL))");
|
||||
|
||||
return $sth->execute(["uid" => $owner_uid, "profile" => $profile_id, "name" => $pref_name, "value" => $value ]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
user_error("Attempt to set invalid preference key: $pref_name (UID: $owner_uid, profile: $profile_id)", E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function migrate(int $owner_uid, int $profile_id = null) {
|
||||
if (get_schema_version() < 141)
|
||||
return;
|
||||
|
||||
if (!$profile_id) $profile_id = null;
|
||||
|
||||
if (!$this->_get(Prefs::_PREFS_MIGRATED, $owner_uid, $profile_id)) {
|
||||
|
||||
$in_nested_tr = false;
|
||||
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
} catch (PDOException $e) {
|
||||
$in_nested_tr = true;
|
||||
}
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT pref_name, value FROM ttrss_user_prefs
|
||||
WHERE owner_uid = :uid AND
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL))");
|
||||
$sth->execute(["uid" => $owner_uid, "profile" => $profile_id]);
|
||||
|
||||
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||
if (isset(self::_DEFAULTS[$row["pref_name"]])) {
|
||||
list ($def_val, $type_hint) = self::_DEFAULTS[$row["pref_name"]];
|
||||
|
||||
$user_val = Config::cast_to($row["value"], $type_hint);
|
||||
|
||||
if ($user_val != $def_val) {
|
||||
$this->_set($row["pref_name"], $user_val, $owner_uid, $profile_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_set(Prefs::_PREFS_MIGRATED, "1", $owner_uid, $profile_id);
|
||||
|
||||
if (!$in_nested_tr)
|
||||
$this->pdo->commit();
|
||||
|
||||
Logger::log(E_USER_NOTICE, sprintf("Migrated preferences of user %d (profile %d)", $owner_uid, $profile_id));
|
||||
}
|
||||
}
|
||||
|
||||
static function reset(int $owner_uid, int $profile_id = null) {
|
||||
if (!$profile_id) $profile_id = null;
|
||||
|
||||
$sth = Db::pdo()->prepare("DELETE FROM ttrss_user_prefs2
|
||||
WHERE owner_uid = :uid AND pref_name != :mig_key AND
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL))");
|
||||
|
||||
$sth->execute(["uid" => $owner_uid, "mig_key" => self::_PREFS_MIGRATED, "profile" => $profile_id]);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
{
|
||||
"require": {
|
||||
"spomky-labs/otphp": "^10.0",
|
||||
"chillerlan/php-qrcode": "^3.3",
|
||||
"mervick/material-design-icons": "^2.2",
|
||||
"j4mie/idiorm": "^1.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.99"
|
||||
}
|
||||
}
|
@ -0,0 +1,669 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "76e40cf59f811ee42d14ac41159c570a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "beberlei/assert",
|
||||
"version": "v3.2.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/beberlei/assert.git",
|
||||
"reference": "d63a6943fc4fd1a2aedb65994e3548715105abcf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/beberlei/assert/zipball/d63a6943fc4fd1a2aedb65994e3548715105abcf",
|
||||
"reference": "d63a6943fc4fd1a2aedb65994e3548715105abcf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-ctype": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-simplexml": "*",
|
||||
"php": "^7"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "*",
|
||||
"phpstan/phpstan-shim": "*",
|
||||
"phpunit/phpunit": ">=6.0.0 <8"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Assert\\": "lib/Assert"
|
||||
},
|
||||
"files": [
|
||||
"lib/Assert/functions.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de",
|
||||
"role": "Lead Developer"
|
||||
},
|
||||
{
|
||||
"name": "Richard Quadling",
|
||||
"email": "rquadling@gmail.com",
|
||||
"role": "Collaborator"
|
||||
}
|
||||
],
|
||||
"description": "Thin assertion library for input validation in business models.",
|
||||
"keywords": [
|
||||
"assert",
|
||||
"assertion",
|
||||
"validation"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/beberlei/assert/issues",
|
||||
"source": "https://github.com/beberlei/assert/tree/v3"
|
||||
},
|
||||
"time": "2019-12-19T17:51:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chillerlan/php-qrcode",
|
||||
"version": "3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chillerlan/php-qrcode.git",
|
||||
"reference": "d8bf297e6843a53aeaa8f3285ce04fc349d133d6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/d8bf297e6843a53aeaa8f3285ce04fc349d133d6",
|
||||
"reference": "d8bf297e6843a53aeaa8f3285ce04fc349d133d6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"chillerlan/php-settings-container": "^1.2",
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5",
|
||||
"setasign/fpdf": "^1.8.2"
|
||||
},
|
||||
"suggest": {
|
||||
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
|
||||
"setasign/fpdf": "Required to use the QR FPDF output."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"chillerlan\\QRCode\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kazuhiko Arase",
|
||||
"homepage": "https://github.com/kazuhikoarase"
|
||||
},
|
||||
{
|
||||
"name": "Smiley",
|
||||
"email": "smiley@chillerlan.net",
|
||||
"homepage": "https://github.com/codemasher"
|
||||
},
|
||||
{
|
||||
"name": "Contributors",
|
||||
"homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A QR code generator. PHP 7.2+",
|
||||
"homepage": "https://github.com/chillerlan/php-qrcode",
|
||||
"keywords": [
|
||||
"phpqrcode",
|
||||
"qr",
|
||||
"qr code",
|
||||
"qrcode",
|
||||
"qrcode-generator"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/chillerlan/php-qrcode/issues",
|
||||
"source": "https://github.com/chillerlan/php-qrcode/tree/3.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/codemasher",
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2020-11-18T20:51:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chillerlan/php-settings-container",
|
||||
"version": "1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chillerlan/php-settings-container.git",
|
||||
"reference": "b9b0431dffd74102ee92348a63b4c33fc8ba639b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/b9b0431dffd74102ee92348a63b4c33fc8ba639b",
|
||||
"reference": "b9b0431dffd74102ee92348a63b4c33fc8ba639b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": "^7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"chillerlan\\Settings\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Smiley",
|
||||
"email": "smiley@chillerlan.net",
|
||||
"homepage": "https://github.com/codemasher"
|
||||
}
|
||||
],
|
||||
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.2+",
|
||||
"homepage": "https://github.com/chillerlan/php-settings-container",
|
||||
"keywords": [
|
||||
"PHP7",
|
||||
"Settings",
|
||||
"container",
|
||||
"helper"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/chillerlan/php-settings-container/issues",
|
||||
"source": "https://github.com/chillerlan/php-settings-container"
|
||||
},
|
||||
"time": "2019-09-10T00:09:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "j4mie/idiorm",
|
||||
"version": "v1.5.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/j4mie/idiorm.git",
|
||||
"reference": "d23f97053ef5d0b988a02c6a71eb5c6118b2f5b4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/j4mie/idiorm/zipball/d23f97053ef5d0b988a02c6a71eb5c6118b2f5b4",
|
||||
"reference": "d23f97053ef5d0b988a02c6a71eb5c6118b2f5b4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-pdo_sqlite": "*",
|
||||
"phpunit/phpunit": "^4.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"idiorm.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSD-4-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jamie Matthews",
|
||||
"email": "jamie.matthews@gmail.com",
|
||||
"homepage": "http://j4mie.org",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Simon Holywell",
|
||||
"email": "treffynnon@php.net",
|
||||
"homepage": "http://simonholywell.com",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Durham Hale",
|
||||
"email": "me@durhamhale.com",
|
||||
"homepage": "http://durhamhale.com",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "A lightweight nearly-zero-configuration object-relational mapper and fluent query builder for PHP5",
|
||||
"homepage": "http://j4mie.github.com/idiormandparis",
|
||||
"keywords": [
|
||||
"idiorm",
|
||||
"orm",
|
||||
"query builder"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/j4mie/idiorm/issues",
|
||||
"source": "https://github.com/j4mie/idiorm"
|
||||
},
|
||||
"time": "2020-04-29T00:37:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mervick/material-design-icons",
|
||||
"version": "2.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mervick/material-design-icons.git",
|
||||
"reference": "635435c8d3df3a6da3241648caf8a65d1c07cc1a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mervick/material-design-icons/zipball/635435c8d3df3a6da3241648caf8a65d1c07cc1a",
|
||||
"reference": "635435c8d3df3a6da3241648caf8a65d1c07cc1a",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT",
|
||||
"CC-BY-4.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Andrey Izman",
|
||||
"email": "izmanw@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Google Material Design Icons For Using With Bootstrap",
|
||||
"homepage": "http://github.com/mervick/material-design-icons",
|
||||
"keywords": [
|
||||
"bootstrap",
|
||||
"google",
|
||||
"icons",
|
||||
"icons-web-font",
|
||||
"material",
|
||||
"material-design",
|
||||
"web-font"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mervick/material-design-icons/issues",
|
||||
"source": "http://github.com/mervick/material-design-icons"
|
||||
},
|
||||
"time": "2016-02-22T01:05:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/constant_time_encoding",
|
||||
"version": "v2.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/constant_time_encoding.git",
|
||||
"reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c",
|
||||
"reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7|^8"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6|^7|^8|^9",
|
||||
"vimeo/psalm": "^1|^2|^3|^4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ParagonIE\\ConstantTime\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Steve 'Sc00bz' Thomas",
|
||||
"email": "steve@tobtu.com",
|
||||
"homepage": "https://www.tobtu.com",
|
||||
"role": "Original Developer"
|
||||
}
|
||||
],
|
||||
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
|
||||
"keywords": [
|
||||
"base16",
|
||||
"base32",
|
||||
"base32_decode",
|
||||
"base32_encode",
|
||||
"base64",
|
||||
"base64_decode",
|
||||
"base64_encode",
|
||||
"bin2hex",
|
||||
"encoding",
|
||||
"hex",
|
||||
"hex2bin",
|
||||
"rfc4648"
|
||||
],
|
||||
"support": {
|
||||
"email": "info@paragonie.com",
|
||||
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
|
||||
"source": "https://github.com/paragonie/constant_time_encoding"
|
||||
},
|
||||
"time": "2020-12-06T15:14:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spomky-labs/otphp",
|
||||
"version": "v10.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Spomky-Labs/otphp.git",
|
||||
"reference": "f44cce5a9db4b8da410215d992110482c931232f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/f44cce5a9db4b8da410215d992110482c931232f",
|
||||
"reference": "f44cce5a9db4b8da410215d992110482c931232f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"beberlei/assert": "^3.0",
|
||||
"ext-mbstring": "*",
|
||||
"paragonie/constant_time_encoding": "^2.0",
|
||||
"php": "^7.2|^8.0",
|
||||
"thecodingmachine/safe": "^0.1.14|^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.0",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpstan/phpstan-beberlei-assert": "^0.12",
|
||||
"phpstan/phpstan-deprecation-rules": "^0.12",
|
||||
"phpstan/phpstan-phpunit": "^0.12",
|
||||
"phpstan/phpstan-strict-rules": "^0.12",
|
||||
"phpunit/phpunit": "^8.0",
|
||||
"thecodingmachine/phpstan-safe-rule": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"v10.0": "10.0.x-dev",
|
||||
"v9.0": "9.0.x-dev",
|
||||
"v8.3": "8.3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"OTPHP\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Florent Morselli",
|
||||
"homepage": "https://github.com/Spomky"
|
||||
},
|
||||
{
|
||||
"name": "All contributors",
|
||||
"homepage": "https://github.com/Spomky-Labs/otphp/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator",
|
||||
"homepage": "https://github.com/Spomky-Labs/otphp",
|
||||
"keywords": [
|
||||
"FreeOTP",
|
||||
"RFC 4226",
|
||||
"RFC 6238",
|
||||
"google authenticator",
|
||||
"hotp",
|
||||
"otp",
|
||||
"totp"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Spomky-Labs/otphp/issues",
|
||||
"source": "https://github.com/Spomky-Labs/otphp/tree/v10.0.1"
|
||||
},
|
||||
"time": "2020-01-28T09:24:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "thecodingmachine/safe",
|
||||
"version": "v1.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thecodingmachine/safe.git",
|
||||
"reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
|
||||
"reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"squizlabs/php_codesniffer": "^3.2",
|
||||
"thecodingmachine/phpstan-strict-rules": "^0.12"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Safe\\": [
|
||||
"lib/",
|
||||
"deprecated/",
|
||||
"generated/"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"deprecated/apc.php",
|
||||
"deprecated/libevent.php",
|
||||
"deprecated/mssql.php",
|
||||
"deprecated/stats.php",
|
||||
"lib/special_cases.php",
|
||||
"generated/apache.php",
|
||||
"generated/apcu.php",
|
||||
"generated/array.php",
|
||||
"generated/bzip2.php",
|
||||
"generated/calendar.php",
|
||||
"generated/classobj.php",
|
||||
"generated/com.php",
|
||||
"generated/cubrid.php",
|
||||
"generated/curl.php",
|
||||
"generated/datetime.php",
|
||||
"generated/dir.php",
|
||||
"generated/eio.php",
|
||||
"generated/errorfunc.php",
|
||||
"generated/exec.php",
|
||||
"generated/fileinfo.php",
|
||||
"generated/filesystem.php",
|
||||
"generated/filter.php",
|
||||
"generated/fpm.php",
|
||||
"generated/ftp.php",
|
||||
"generated/funchand.php",
|
||||
"generated/gmp.php",
|
||||
"generated/gnupg.php",
|
||||
"generated/hash.php",
|
||||
"generated/ibase.php",
|
||||
"generated/ibmDb2.php",
|
||||
"generated/iconv.php",
|
||||
"generated/image.php",
|
||||
"generated/imap.php",
|
||||
"generated/info.php",
|
||||
"generated/ingres-ii.php",
|
||||
"generated/inotify.php",
|
||||
"generated/json.php",
|
||||
"generated/ldap.php",
|
||||
"generated/libxml.php",
|
||||
"generated/lzf.php",
|
||||
"generated/mailparse.php",
|
||||
"generated/mbstring.php",
|
||||
"generated/misc.php",
|
||||
"generated/msql.php",
|
||||
"generated/mysql.php",
|
||||
"generated/mysqli.php",
|
||||
"generated/mysqlndMs.php",
|
||||
"generated/mysqlndQc.php",
|
||||
"generated/network.php",
|
||||
"generated/oci8.php",
|
||||
"generated/opcache.php",
|
||||
"generated/openssl.php",
|
||||
"generated/outcontrol.php",
|
||||
"generated/password.php",
|
||||
"generated/pcntl.php",
|
||||
"generated/pcre.php",
|
||||
"generated/pdf.php",
|
||||
"generated/pgsql.php",
|
||||
"generated/posix.php",
|
||||
"generated/ps.php",
|
||||
"generated/pspell.php",
|
||||
"generated/readline.php",
|
||||
"generated/rpminfo.php",
|
||||
"generated/rrd.php",
|
||||
"generated/sem.php",
|
||||
"generated/session.php",
|
||||
"generated/shmop.php",
|
||||
"generated/simplexml.php",
|
||||
"generated/sockets.php",
|
||||
"generated/sodium.php",
|
||||
"generated/solr.php",
|
||||
"generated/spl.php",
|
||||
"generated/sqlsrv.php",
|
||||
"generated/ssdeep.php",
|
||||
"generated/ssh2.php",
|
||||
"generated/stream.php",
|
||||
"generated/strings.php",
|
||||
"generated/swoole.php",
|
||||
"generated/uodbc.php",
|
||||
"generated/uopz.php",
|
||||
"generated/url.php",
|
||||
"generated/var.php",
|
||||
"generated/xdiff.php",
|
||||
"generated/xml.php",
|
||||
"generated/xmlrpc.php",
|
||||
"generated/yaml.php",
|
||||
"generated/yaz.php",
|
||||
"generated/zip.php",
|
||||
"generated/zlib.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
|
||||
"support": {
|
||||
"issues": "https://github.com/thecodingmachine/safe/issues",
|
||||
"source": "https://github.com/thecodingmachine/safe/tree/v1.3.3"
|
||||
},
|
||||
"time": "2020-10-28T17:51:34+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "0.12.99",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4d40f1d759942f523be267a1bab6884f46ca3f7",
|
||||
"reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1|^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan-shim": "*"
|
||||
},
|
||||
"bin": [
|
||||
"phpstan",
|
||||
"phpstan.phar"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.12-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHPStan - PHP Static Analysis Tool",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||
"source": "https://github.com/phpstan/phpstan/tree/0.12.99"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/ondrejmirtes",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/phpstan",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpstan",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-09-12T20:09:55+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
<?php
|
||||
set_include_path(dirname(__FILE__) ."/include" . PATH_SEPARATOR .
|
||||
get_include_path());
|
||||
|
||||
require_once "functions.php";
|
||||
|
||||
$ERRORS[0] = "";
|
||||
|
||||
$ERRORS[1] = __("This program requires XmlHttpRequest " .
|
||||
"to function properly. Your browser doesn't seem to support it.");
|
||||
|
||||
$ERRORS[2] = __("This program requires cookies " .
|
||||
"to function properly. Your browser doesn't seem to support them.");
|
||||
|
||||
$ERRORS[3] = __("Backend sanity check failed.");
|
||||
|
||||
$ERRORS[4] = __("Frontend sanity check failed.");
|
||||
|
||||
$ERRORS[5] = __("Incorrect database schema version. <a href='db-updater.php'>Please update</a>.");
|
||||
|
||||
$ERRORS[6] = __("Request not authorized.");
|
||||
|
||||
$ERRORS[7] = __("No operation to perform.");
|
||||
|
||||
$ERRORS[8] = __("Could not display feed: query failed. Please check label match syntax or local configuration.");
|
||||
|
||||
$ERRORS[8] = __("Denied. Your access level is insufficient to access this page.");
|
||||
|
||||
$ERRORS[9] = __("Configuration check failed");
|
||||
|
||||
$ERRORS[10] = __("Your version of MySQL is not currently supported. Please see official site for more information.");
|
||||
|
||||
$ERRORS[11] = "[This error is not returned by server]";
|
||||
|
||||
$ERRORS[12] = __("SQL escaping test failed, check your database and PHP configuration");
|
||||
|
||||
$ERRORS[13] = __("Method not found");
|
||||
|
||||
$ERRORS[14] = __("Plugin not found");
|
||||
|
||||
$ERRORS[15] = __("Encoding data as JSON failed");
|
||||
|
||||
if ($_REQUEST['mode'] == 'js') {
|
||||
header("Content-Type: text/javascript; charset=UTF-8");
|
||||
|
||||
print "var ERRORS = [];\n";
|
||||
|
||||
foreach ($ERRORS as $id => $error) {
|
||||
|
||||
$error = preg_replace("/\n/", "", $error);
|
||||
$error = preg_replace("/\"/", "\\\"", $error);
|
||||
|
||||
print "ERRORS[$id] = \"$error\";\n";
|
||||
}
|
||||
}
|
||||
?>
|
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.0 KiB |
@ -0,0 +1,17 @@
|
||||
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
|
||||
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#257aa7">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g stroke-width="8" transform="matrix(0.83009609,0,0,0.83009609,4.0582705,4.0582705)">
|
||||
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
|
||||
<path d="M 36,18 C 36,8.06 27.94,0 18,0">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 18 18"
|
||||
to="360 18 18"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"/>
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 740 B |
@ -0,0 +1,33 @@
|
||||
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
|
||||
<svg width="120" height="30" viewBox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="#257aa7">
|
||||
<circle cx="15" cy="15" r="15">
|
||||
<animate attributeName="r" from="15" to="15"
|
||||
begin="0s" dur="0.8s"
|
||||
values="15;9;15" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="fill-opacity" from="1" to="1"
|
||||
begin="0s" dur="0.8s"
|
||||
values="1;.5;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="60" cy="15" r="9" fill-opacity="0.3">
|
||||
<animate attributeName="r" from="9" to="9"
|
||||
begin="0s" dur="0.8s"
|
||||
values="9;15;9" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="fill-opacity" from="0.5" to="0.5"
|
||||
begin="0s" dur="0.8s"
|
||||
values=".5;1;.5" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="105" cy="15" r="15">
|
||||
<animate attributeName="r" from="15" to="15"
|
||||
begin="0s" dur="0.8s"
|
||||
values="15;9;15" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="fill-opacity" from="1" to="1"
|
||||
begin="0s" dur="0.8s"
|
||||
values="1;.5;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -1,26 +1,17 @@
|
||||
<?php
|
||||
require_once "functions.php";
|
||||
|
||||
spl_autoload_register(function($class) {
|
||||
$namespace = '';
|
||||
$class_name = $class;
|
||||
|
||||
if (strpos($class, '\\') !== false)
|
||||
list ($namespace, $class_name) = explode('\\', $class, 2);
|
||||
|
||||
$root_dir = dirname(__DIR__); // we're in tt-rss/include
|
||||
$root_dir = dirname(__DIR__); // we were in tt-rss/include
|
||||
|
||||
// 1. third party libraries with namespaces are loaded from vendor/
|
||||
// 2. internal tt-rss classes are loaded from classes/ and use special naming logic instead of namespaces
|
||||
// 3. plugin classes are loaded by PluginHandler from plugins.local/ and plugins/ (TODO: use generic autoloader?)
|
||||
// - internal tt-rss classes are loaded from classes/ and use special naming logic instead of namespaces
|
||||
// - plugin classes are loaded by PluginHandler from plugins.local/ and plugins/
|
||||
|
||||
if ($namespace && $class_name) {
|
||||
$class_file = "$root_dir/vendor/$namespace/" . str_replace('\\', '/', $class_name) . ".php";
|
||||
} else {
|
||||
$class_file = "$root_dir/classes/" . str_replace("_", "/", strtolower($class)) . ".php";
|
||||
}
|
||||
$class_file = "$root_dir/classes/" . str_replace("_", "/", strtolower($class)) . ".php";
|
||||
|
||||
if (file_exists($class_file))
|
||||
include $class_file;
|
||||
|
||||
});
|
||||
|
||||
// also pull composer autoloader
|
||||
require_once "vendor/autoload.php";
|
||||
|
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
|
||||
function stylesheet_tag($filename, $attributes = []) {
|
||||
|
||||
$attributes_str = \Controls\attributes_to_string(
|
||||
array_merge(
|
||||
[
|
||||
"href" => "$filename?" . filemtime($filename),
|
||||
"rel" => "stylesheet",
|
||||
"type" => "text/css",
|
||||
"data-orig-href" => $filename
|
||||
],
|
||||
$attributes));
|
||||
|
||||
return "<link $attributes_str/>\n";
|
||||
}
|
||||
|
||||
function javascript_tag($filename, $attributes = []) {
|
||||
$attributes_str = \Controls\attributes_to_string(
|
||||
array_merge(
|
||||
[
|
||||
"src" => "$filename?" . filemtime($filename),
|
||||
"type" => "text/javascript",
|
||||
"charset" => "utf-8"
|
||||
],
|
||||
$attributes));
|
||||
|
||||
return "<script $attributes_str></script>\n";
|
||||
}
|
||||
|
||||
function format_warning($msg, $id = "") {
|
||||
return "<div class=\"alert\" id=\"$id\">$msg</div>";
|
||||
}
|
||||
|
||||
function format_notice($msg, $id = "") {
|
||||
return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
|
||||
}
|
||||
|
||||
function format_error($msg, $id = "") {
|
||||
return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
|
||||
}
|
||||
|
||||
function print_notice($msg) {
|
||||
return print format_notice($msg);
|
||||
}
|
||||
|
||||
function print_warning($msg) {
|
||||
return print format_warning($msg);
|
||||
}
|
||||
|
||||
function print_error($msg) {
|
||||
return print format_error($msg);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
require_once "db.php";
|
||||
|
||||
function get_pref($pref_name, $user_id = false, $die_on_error = false) {
|
||||
return Db_Prefs::get()->read($pref_name, $user_id, $die_on_error);
|
||||
}
|
||||
|
||||
function set_pref($pref_name, $value, $user_id = false, $strip_tags = true) {
|
||||
return Db_Prefs::get()->write($pref_name, $value, $user_id, $strip_tags);
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
function db_escape_string($s, $strip_tags = true) {
|
||||
return Db::get()->escape_string($s, $strip_tags);
|
||||
}
|
||||
|
||||
function db_query($query, $die_on_error = true) {
|
||||
return Db::get()->query($query, $die_on_error);
|
||||
}
|
||||
|
||||
function db_fetch_assoc($result) {
|
||||
return Db::get()->fetch_assoc($result);
|
||||
}
|
||||
|
||||
|
||||
function db_num_rows($result) {
|
||||
return Db::get()->num_rows($result);
|
||||
}
|
||||
|
||||
function db_fetch_result($result, $row, $param) {
|
||||
return Db::get()->fetch_result($result, $row, $param);
|
||||
}
|
||||
|
||||
function db_affected_rows($result) {
|
||||
return Db::get()->affected_rows($result);
|
||||
}
|
||||
|
||||
function db_last_error() {
|
||||
return Db::get()->last_error();
|
||||
}
|
||||
|
||||
function db_last_query_error() {
|
||||
return Db::get()->last_query_error();
|
||||
}
|
||||
|
||||
function db_quote($str){
|
||||
return Db::get()->quote($str);
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
<?php
|
||||
/* WARNING!
|
||||
*
|
||||
* If you modify this file, you are ON YOUR OWN!
|
||||
*
|
||||
* Believe it or not, all of the checks below are required to succeed for
|
||||
* tt-rss to actually function properly.
|
||||
*
|
||||
* If you think you have a better idea about what is or isn't required, feel
|
||||
* free to modify the file, note though that you are therefore automatically
|
||||
* disqualified from any further support by official channels, e.g. tt-rss.org
|
||||
* issue tracker or the forums.
|
||||
*
|
||||
* If you come crying when stuff inevitably breaks, you will be mocked and told
|
||||
* to get out. */
|
||||
|
||||
function make_self_url() {
|
||||
$proto = is_server_https() ? 'https' : 'http';
|
||||
|
||||
return $proto . '://' . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
|
||||
}
|
||||
|
||||
function make_self_url_path() {
|
||||
$proto = is_server_https() ? 'https' : 'http';
|
||||
$url_path = $proto . '://' . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
|
||||
|
||||
return $url_path;
|
||||
}
|
||||
|
||||
function check_mysql_tables() {
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT engine, table_name FROM information_schema.tables WHERE
|
||||
table_schema = ? AND table_name LIKE 'ttrss_%' AND engine != 'InnoDB'");
|
||||
$sth->execute([DB_NAME]);
|
||||
|
||||
$bad_tables = [];
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
array_push($bad_tables, $line);
|
||||
}
|
||||
|
||||
return $bad_tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
|
||||
*/
|
||||
function initial_sanity_check() {
|
||||
|
||||
$errors = array();
|
||||
|
||||
if (!file_exists("config.php")) {
|
||||
array_push($errors, "Configuration file not found. Looks like you forgot to copy config.php-dist to config.php and edit it.");
|
||||
} else {
|
||||
|
||||
require_once "sanity_config.php";
|
||||
|
||||
if (file_exists("install") && !file_exists("config.php")) {
|
||||
array_push($errors, "Please copy config.php-dist to config.php or run the installer in install/");
|
||||
}
|
||||
|
||||
if (strpos(PLUGINS, "auth_") === false) {
|
||||
array_push($errors, "Please enable at least one authentication module via PLUGINS constant in config.php");
|
||||
}
|
||||
|
||||
if (function_exists('posix_getuid') && posix_getuid() == 0) {
|
||||
array_push($errors, "Please don't run this script as root.");
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.6.0', '<')) {
|
||||
array_push($errors, "PHP version 5.6.0 or newer required. You're using " . PHP_VERSION . ".");
|
||||
}
|
||||
|
||||
if (!class_exists("UConverter")) {
|
||||
array_push($errors, "PHP UConverter class is missing, it's provided by the Internationalization (intl) module.");
|
||||
}
|
||||
|
||||
if (CONFIG_VERSION != EXPECTED_CONFIG_VERSION) {
|
||||
array_push($errors, "Configuration file (config.php) has incorrect version. Update it with new options from config.php-dist and set CONFIG_VERSION to the correct value.");
|
||||
}
|
||||
|
||||
if (!is_writable(CACHE_DIR . "/images")) {
|
||||
array_push($errors, "Image cache is not writable (chmod -R 777 ".CACHE_DIR."/images)");
|
||||
}
|
||||
|
||||
if (!is_writable(CACHE_DIR . "/upload")) {
|
||||
array_push($errors, "Upload cache is not writable (chmod -R 777 ".CACHE_DIR."/upload)");
|
||||
}
|
||||
|
||||
if (!is_writable(CACHE_DIR . "/export")) {
|
||||
array_push($errors, "Data export cache is not writable (chmod -R 777 ".CACHE_DIR."/export)");
|
||||
}
|
||||
|
||||
if (GENERATED_CONFIG_CHECK != EXPECTED_CONFIG_VERSION) {
|
||||
array_push($errors,
|
||||
"Configuration option checker sanity_config.php is outdated, please recreate it using ./utils/regen_config_checks.sh");
|
||||
}
|
||||
|
||||
foreach ($required_defines as $d) {
|
||||
if (!defined($d)) {
|
||||
array_push($errors,
|
||||
"Required configuration file parameter $d is not defined in config.php. You might need to copy it from config.php-dist.");
|
||||
}
|
||||
}
|
||||
|
||||
if (SINGLE_USER_MODE && class_exists("PDO")) {
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$res = $pdo->query("SELECT id FROM ttrss_users WHERE id = 1");
|
||||
|
||||
if (!$res->fetch()) {
|
||||
array_push($errors, "SINGLE_USER_MODE is enabled in config.php but default admin account is not found.");
|
||||
}
|
||||
}
|
||||
|
||||
$ref_self_url_path = make_self_url_path();
|
||||
$ref_self_url_path = preg_replace("/\w+\.php$/", "", $ref_self_url_path);
|
||||
|
||||
if (SELF_URL_PATH == "http://example.org/tt-rss/") {
|
||||
array_push($errors,
|
||||
"Please set SELF_URL_PATH to the correct value for your server (possible value: <b>$ref_self_url_path</b>)");
|
||||
}
|
||||
|
||||
if (isset($_SERVER["HTTP_HOST"]) &&
|
||||
(!defined('_SKIP_SELF_URL_PATH_CHECKS') || !_SKIP_SELF_URL_PATH_CHECKS) &&
|
||||
SELF_URL_PATH != $ref_self_url_path && SELF_URL_PATH != mb_substr($ref_self_url_path, 0, mb_strlen($ref_self_url_path)-1)) {
|
||||
array_push($errors,
|
||||
"Please set SELF_URL_PATH to the correct value detected for your server: <b>$ref_self_url_path</b>");
|
||||
}
|
||||
|
||||
if (!is_writable(ICONS_DIR)) {
|
||||
array_push($errors, "ICONS_DIR defined in config.php is not writable (chmod -R 777 ".ICONS_DIR.").\n");
|
||||
}
|
||||
|
||||
if (!is_writable(LOCK_DIRECTORY)) {
|
||||
array_push($errors, "LOCK_DIRECTORY defined in config.php is not writable (chmod -R 777 ".LOCK_DIRECTORY.").\n");
|
||||
}
|
||||
|
||||
if (!function_exists("curl_init") && !ini_get("allow_url_fopen")) {
|
||||
array_push($errors, "PHP configuration option allow_url_fopen is disabled, and CURL functions are not present. Either enable allow_url_fopen or install PHP extension for CURL.");
|
||||
}
|
||||
|
||||
if (!function_exists("json_encode")) {
|
||||
array_push($errors, "PHP support for JSON is required, but was not found.");
|
||||
}
|
||||
|
||||
if (DB_TYPE == "mysql" && !function_exists("mysqli_connect")) {
|
||||
array_push($errors, "PHP support for MySQL is required for configured DB_TYPE in config.php.");
|
||||
}
|
||||
|
||||
if (DB_TYPE == "pgsql" && !function_exists("pg_connect")) {
|
||||
array_push($errors, "PHP support for PostgreSQL is required for configured DB_TYPE in config.php");
|
||||
}
|
||||
|
||||
if (!class_exists("PDO")) {
|
||||
array_push($errors, "PHP support for PDO is required but was not found.");
|
||||
}
|
||||
|
||||
if (!function_exists("mb_strlen")) {
|
||||
array_push($errors, "PHP support for mbstring functions is required but was not found.");
|
||||
}
|
||||
|
||||
if (!function_exists("hash")) {
|
||||
array_push($errors, "PHP support for hash() function is required but was not found.");
|
||||
}
|
||||
|
||||
if (ini_get("safe_mode")) {
|
||||
array_push($errors, "PHP safe mode setting is obsolete and not supported by tt-rss.");
|
||||
}
|
||||
|
||||
if (!function_exists("mime_content_type")) {
|
||||
array_push($errors, "PHP function mime_content_type() is missing, try enabling fileinfo module.");
|
||||
}
|
||||
|
||||
if (!class_exists("DOMDocument")) {
|
||||
array_push($errors, "PHP support for DOMDocument is required, but was not found.");
|
||||
}
|
||||
|
||||
if (DB_TYPE == "mysql") {
|
||||
$bad_tables = check_mysql_tables();
|
||||
|
||||
if (count($bad_tables) > 0) {
|
||||
$bad_tables_fmt = [];
|
||||
|
||||
foreach ($bad_tables as $bt) {
|
||||
array_push($bad_tables_fmt, sprintf("%s (%s)", $bt['table_name'], $bt['engine']));
|
||||
}
|
||||
|
||||
$msg = "<p>The following tables use an unsupported MySQL engine: <b>" .
|
||||
implode(", ", $bad_tables_fmt) . "</b>.</p>";
|
||||
|
||||
$msg .= "<p>The only supported engine on MySQL is InnoDB. MyISAM lacks functionality to run
|
||||
tt-rss.
|
||||
Please backup your data (via OPML) and re-import the schema before continuing.</p>
|
||||
<p><b>WARNING: importing the schema would mean LOSS OF ALL YOUR DATA.</b></p>";
|
||||
|
||||
|
||||
array_push($errors, $msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($errors) > 0 && $_SERVER['REQUEST_URI']) { ?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Startup failed</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="themes/light.css">
|
||||
</head>
|
||||
<body class='sanity_failed claro ttrss_utility'>
|
||||
<div class="content">
|
||||
|
||||
<h1>Startup failed</h1>
|
||||
|
||||
<p>Tiny Tiny RSS was unable to start properly. This usually means a misconfiguration or an incomplete upgrade. Please fix
|
||||
errors indicated by the following messages:</p>
|
||||
|
||||
<?php foreach ($errors as $error) { echo format_error($error); } ?>
|
||||
|
||||
<p>You might want to check tt-rss <a href="https://tt-rss.org/wiki.php">wiki</a> or the
|
||||
<a href="https://community.tt-rss.org/">forums</a> for more information. Please search the forums before creating new topic
|
||||
for your question.</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<?php
|
||||
die;
|
||||
} else if (count($errors) > 0) {
|
||||
echo "Tiny Tiny RSS was unable to start properly. This usually means a misconfiguration or an incomplete upgrade.\n";
|
||||
echo "Please fix errors indicated by the following messages:\n\n";
|
||||
|
||||
foreach ($errors as $error) {
|
||||
echo " * $error\n";
|
||||
}
|
||||
|
||||
echo "\nYou might want to check tt-rss wiki or the forums for more information.\n";
|
||||
echo "Please search the forums before creating new topic for your question.\n";
|
||||
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
initial_sanity_check();
|
||||
|
||||
?>
|
@ -1,3 +0,0 @@
|
||||
<?php # This file has been generated at: Fri Dec 11 09:30:20 MSK 2020
|
||||
define('GENERATED_CONFIG_CHECK', 26);
|
||||
$required_defines = array( 'DB_TYPE', 'DB_HOST', 'DB_USER', 'DB_NAME', 'DB_PASS', 'MYSQL_CHARSET', 'SELF_URL_PATH', 'SINGLE_USER_MODE', 'SIMPLE_UPDATE_MODE', 'PHP_EXECUTABLE', 'LOCK_DIRECTORY', 'CACHE_DIR', 'ICONS_DIR', 'ICONS_URL', 'AUTH_AUTO_CREATE', 'AUTH_AUTO_LOGIN', 'FORCE_ARTICLE_PURGE', 'ENABLE_REGISTRATION', 'REG_NOTIFY_ADDRESS', 'REG_MAX_USERS', 'SESSION_COOKIE_LIFETIME', 'SMTP_FROM_NAME', 'SMTP_FROM_ADDRESS', 'DIGEST_SUBJECT', 'CHECK_FOR_UPDATES', 'ENABLE_GZIP_OUTPUT', 'PLUGINS', 'LOG_DESTINATION', 'CONFIG_VERSION'); ?>
|
@ -1,503 +0,0 @@
|
||||
<?php
|
||||
function stylesheet_tag($filename, $id = false) {
|
||||
$timestamp = filemtime($filename);
|
||||
|
||||
$id_part = $id ? "id=\"$id\"" : "";
|
||||
|
||||
return "<link rel=\"stylesheet\" $id_part type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
|
||||
}
|
||||
|
||||
function javascript_tag($filename) {
|
||||
$query = "";
|
||||
|
||||
if (!(strpos($filename, "?") === false)) {
|
||||
$query = substr($filename, strpos($filename, "?")+1);
|
||||
$filename = substr($filename, 0, strpos($filename, "?"));
|
||||
}
|
||||
|
||||
$timestamp = filemtime($filename);
|
||||
|
||||
if ($query) $timestamp .= "&$query";
|
||||
|
||||
return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tiny Tiny RSS - Installer</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<style type="text/css">
|
||||
textarea { font-size : 12px; }
|
||||
</style>
|
||||
<?php
|
||||
echo stylesheet_tag("../themes/light.css");
|
||||
echo javascript_tag("../lib/prototype.js");
|
||||
echo javascript_tag("../lib/dojo/dojo.js");
|
||||
echo javascript_tag("../lib/dojo/tt-rss-layer.js");
|
||||
?>
|
||||
</head>
|
||||
<body class="flat ttrss_utility installer">
|
||||
|
||||
<script type="text/javascript">
|
||||
require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form',
|
||||
'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){
|
||||
ready(function() {
|
||||
parser.parse();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
|
||||
// could be needed because of existing config.php
|
||||
function define_default($param, $value) {
|
||||
//
|
||||
}
|
||||
|
||||
function make_password($length = 12) {
|
||||
$password = "";
|
||||
$possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ*%+^";
|
||||
|
||||
$i = 0;
|
||||
|
||||
while ($i < $length) {
|
||||
|
||||
try {
|
||||
$idx = function_exists("random_int") ? random_int(0, strlen($possible) - 1) : mt_rand(0, strlen($possible) - 1);
|
||||
} catch (Exception $e) {
|
||||
$idx = mt_rand(0, strlen($possible) - 1);
|
||||
}
|
||||
|
||||
$char = substr($possible, $idx, 1);
|
||||
|
||||
if (!strstr($password, $char)) {
|
||||
$password .= $char;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
return $password;
|
||||
}
|
||||
|
||||
|
||||
function sanity_check($db_type) {
|
||||
$errors = array();
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.6.0', '<')) {
|
||||
array_push($errors, "PHP version 5.6.0 or newer required. You're using " . PHP_VERSION . ".");
|
||||
}
|
||||
|
||||
if (!function_exists("curl_init") && !ini_get("allow_url_fopen")) {
|
||||
array_push($errors, "PHP configuration option allow_url_fopen is disabled, and CURL functions are not present. Either enable allow_url_fopen or install PHP extension for CURL.");
|
||||
}
|
||||
|
||||
if (!function_exists("json_encode")) {
|
||||
array_push($errors, "PHP support for JSON is required, but was not found.");
|
||||
}
|
||||
|
||||
if (!class_exists("PDO")) {
|
||||
array_push($errors, "PHP support for PDO is required but was not found.");
|
||||
}
|
||||
|
||||
if (!function_exists("mb_strlen")) {
|
||||
array_push($errors, "PHP support for mbstring functions is required but was not found.");
|
||||
}
|
||||
|
||||
if (!function_exists("hash")) {
|
||||
array_push($errors, "PHP support for hash() function is required but was not found.");
|
||||
}
|
||||
|
||||
if (!function_exists("iconv")) {
|
||||
array_push($errors, "PHP support for iconv is required to handle multiple charsets.");
|
||||
}
|
||||
|
||||
if (ini_get("safe_mode")) {
|
||||
array_push($errors, "PHP safe mode setting is obsolete and not supported by tt-rss.");
|
||||
}
|
||||
|
||||
if (!class_exists("DOMDocument")) {
|
||||
array_push($errors, "PHP support for DOMDocument is required, but was not found.");
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
function print_error($msg) {
|
||||
print "<div class='alert alert-error'>$msg</div>";
|
||||
}
|
||||
|
||||
function print_notice($msg) {
|
||||
print "<div class=\"alert alert-info\">$msg</div>";
|
||||
}
|
||||
|
||||
function pdo_connect($host, $user, $pass, $db, $type, $port = false) {
|
||||
|
||||
$db_port = $port ? ';port=' . $port : '';
|
||||
$db_host = $host ? ';host=' . $host : '';
|
||||
|
||||
try {
|
||||
$pdo = new PDO($type . ':dbname=' . $db . $db_host . $db_port,
|
||||
$user,
|
||||
$pass);
|
||||
|
||||
return $pdo;
|
||||
} catch (Exception $e) {
|
||||
print "<div class='alert alert-danger'>" . $e->getMessage() . "</div>";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function make_config($DB_TYPE, $DB_HOST, $DB_USER, $DB_NAME, $DB_PASS,
|
||||
$DB_PORT, $SELF_URL_PATH) {
|
||||
|
||||
$rv = file_get_contents("../config.php-dist");
|
||||
|
||||
$escape_chars = "\\'";
|
||||
|
||||
$settings = [
|
||||
"%DB_TYPE" => $DB_TYPE == 'pgsql' ? 'pgsql' : 'mysql',
|
||||
"%DB_HOST" => addcslashes($DB_HOST, $escape_chars),
|
||||
"%DB_USER" => addcslashes($DB_USER, $escape_chars),
|
||||
"%DB_NAME" => addcslashes($DB_NAME, $escape_chars),
|
||||
"%DB_PASS" => addcslashes($DB_PASS, $escape_chars),
|
||||
"%DB_PORT" => $DB_PORT ? intval($DB_PORT) : '',
|
||||
"%SELF_URL_PATH" => addcslashes($SELF_URL_PATH, $escape_chars)
|
||||
];
|
||||
|
||||
$rv = str_replace(array_keys($settings), array_values($settings), $rv);
|
||||
|
||||
return $rv;
|
||||
}
|
||||
|
||||
function is_server_https() {
|
||||
return (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] != 'off')) || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https');
|
||||
}
|
||||
|
||||
function make_self_url_path() {
|
||||
$url_path = (is_server_https() ? 'https://' : 'http://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
|
||||
|
||||
return $url_path;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<h1>Tiny Tiny RSS Installer</h1>
|
||||
|
||||
<div class='content'>
|
||||
|
||||
<?php
|
||||
|
||||
if (file_exists("../config.php")) {
|
||||
require "../config.php";
|
||||
|
||||
if (!defined('_INSTALLER_IGNORE_CONFIG_CHECK')) {
|
||||
print_error("Error: config.php already exists in tt-rss directory; aborting.");
|
||||
|
||||
print "<form method='GET' action='../index.php'>
|
||||
<button type='submit' dojoType='dijit.form.Button' class='alt-primary'>Return to Tiny Tiny RSS</button>
|
||||
</form>";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@$op = $_REQUEST['op'];
|
||||
|
||||
@$DB_HOST = strip_tags($_POST['DB_HOST']);
|
||||
@$DB_TYPE = strip_tags($_POST['DB_TYPE']);
|
||||
@$DB_USER = strip_tags($_POST['DB_USER']);
|
||||
@$DB_NAME = strip_tags($_POST['DB_NAME']);
|
||||
@$DB_PASS = strip_tags($_POST['DB_PASS']);
|
||||
@$DB_PORT = strip_tags($_POST['DB_PORT']);
|
||||
@$SELF_URL_PATH = strip_tags($_POST['SELF_URL_PATH']);
|
||||
|
||||
if (!$SELF_URL_PATH) {
|
||||
$SELF_URL_PATH = preg_replace("/\/install\/$/", "/", make_self_url_path());
|
||||
}
|
||||
?>
|
||||
|
||||
<form action="" method="post">
|
||||
<input type="hidden" name="op" value="testconfig">
|
||||
|
||||
<h2>Database settings</h2>
|
||||
|
||||
<?php
|
||||
$issel_pgsql = $DB_TYPE == "pgsql" ? "selected='selected'" : "";
|
||||
$issel_mysql = $DB_TYPE == "mysql" ? "selected='selected'" : "";
|
||||
?>
|
||||
|
||||
<fieldset>
|
||||
<label>Database type:</label>
|
||||
<select name="DB_TYPE" dojoType="dijit.form.Select">
|
||||
<option <?php echo $issel_pgsql ?> value="pgsql">PostgreSQL</option>
|
||||
<option <?php echo $issel_mysql ?> value="mysql">MySQL</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Username:</label>
|
||||
<input dojoType="dijit.form.TextBox" required name="DB_USER" size="20" value="<?php echo htmlspecialchars($DB_USER) ?>"/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Password:</label>
|
||||
<input dojoType="dijit.form.TextBox" name="DB_PASS" size="20" type="password" value="<?php echo htmlspecialchars($DB_PASS) ?>"/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Database name:</label>
|
||||
<input dojoType="dijit.form.TextBox" required name="DB_NAME" size="20" value="<?php echo htmlspecialchars($DB_NAME) ?>"/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Host name:</label>
|
||||
<input dojoType="dijit.form.TextBox" name="DB_HOST" size="20" value="<?php echo htmlspecialchars($DB_HOST) ?>"/>
|
||||
<span class="hint">If needed</span>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Port:</label>
|
||||
<input dojoType="dijit.form.TextBox" name="DB_PORT" type="number" size="20" value="<?php echo htmlspecialchars($DB_PORT) ?>"/>
|
||||
<span class="hint">Usually 3306 for MySQL or 5432 for PostgreSQL</span>
|
||||
</fieldset>
|
||||
|
||||
<h2>Other settings</h2>
|
||||
|
||||
<p>This should be set to the location your Tiny Tiny RSS will be available on.</p>
|
||||
|
||||
<fieldset>
|
||||
<label>Tiny Tiny RSS URL:</label>
|
||||
<input dojoType="dijit.form.TextBox" type="url" name="SELF_URL_PATH" placeholder="<?php echo htmlspecialchars($SELF_URL_PATH); ?>" value="<?php echo htmlspecialchars($SELF_URL_PATH) ?>"/>
|
||||
</fieldset>
|
||||
|
||||
<p><button type="submit" dojoType="dijit.form.Button" class="alt-primary">Test configuration</button></p>
|
||||
</form>
|
||||
|
||||
<?php if ($op == 'testconfig') { ?>
|
||||
|
||||
<h2>Checking configuration</h2>
|
||||
|
||||
<?php
|
||||
$errors = sanity_check($DB_TYPE);
|
||||
|
||||
if (count($errors) > 0) {
|
||||
print "<p>Some configuration tests failed. Please correct them before continuing.</p>";
|
||||
|
||||
print "<ul>";
|
||||
|
||||
foreach ($errors as $error) {
|
||||
print "<li style='color : red'>$error</li>";
|
||||
}
|
||||
|
||||
print "</ul>";
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
$notices = array();
|
||||
|
||||
if (!function_exists("curl_init")) {
|
||||
array_push($notices, "It is highly recommended to enable support for CURL in PHP.");
|
||||
}
|
||||
|
||||
if (function_exists("curl_init") && ini_get("open_basedir")) {
|
||||
array_push($notices, "CURL and open_basedir combination breaks support for HTTP redirects. See the FAQ for more information.");
|
||||
}
|
||||
|
||||
if (!function_exists("idn_to_ascii")) {
|
||||
array_push($notices, "PHP support for Internationalization Functions is required to handle Internationalized Domain Names.");
|
||||
}
|
||||
|
||||
if ($DB_TYPE == "mysql" && !function_exists("mysqli_connect")) {
|
||||
array_push($notices, "PHP extension for MySQL (mysqli) is missing. This may prevent legacy plugins from working.");
|
||||
}
|
||||
|
||||
if ($DB_TYPE == "pgsql" && !function_exists("pg_connect")) {
|
||||
array_push($notices, "PHP extension for PostgreSQL is missing. This may prevent legacy plugins from working.");
|
||||
}
|
||||
|
||||
if (count($notices) > 0) {
|
||||
print_notice("Configuration check succeeded with minor problems:");
|
||||
|
||||
print "<ul>";
|
||||
|
||||
foreach ($notices as $notice) {
|
||||
print "<li>$notice</li>";
|
||||
}
|
||||
|
||||
print "</ul>";
|
||||
} else {
|
||||
print_notice("Configuration check succeeded.");
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<h2>Checking database</h2>
|
||||
|
||||
<?php
|
||||
$pdo = pdo_connect($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_TYPE, $DB_PORT);
|
||||
|
||||
if (!$pdo) {
|
||||
print_error("Unable to connect to database using specified parameters (driver: " . htmlspecialchars($DB_TYPE) . ").");
|
||||
exit;
|
||||
}
|
||||
|
||||
print_notice("Database test succeeded.");
|
||||
?>
|
||||
|
||||
<h2>Initialize database</h2>
|
||||
|
||||
<p>Before you can start using tt-rss, database needs to be initialized. Click on the button below to do that now.</p>
|
||||
|
||||
<?php
|
||||
$res = $pdo->query("SELECT true FROM ttrss_feeds");
|
||||
|
||||
if ($res && $res->fetch()) {
|
||||
print_error("Some tt-rss data already exists in this database. If you continue with database initialization your current data <b>WILL BE LOST</b>.");
|
||||
$need_confirm = true;
|
||||
} else {
|
||||
$need_confirm = false;
|
||||
}
|
||||
?>
|
||||
|
||||
<table><tr><td>
|
||||
<form method="post">
|
||||
<input type="hidden" name="op" value="installschema">
|
||||
|
||||
<input type="hidden" name="DB_USER" value="<?php echo htmlspecialchars($DB_USER) ?>"/>
|
||||
<input type="hidden" name="DB_PASS" value="<?php echo htmlspecialchars($DB_PASS) ?>"/>
|
||||
<input type="hidden" name="DB_NAME" value="<?php echo htmlspecialchars($DB_NAME) ?>"/>
|
||||
<input type="hidden" name="DB_HOST" value="<?php echo htmlspecialchars($DB_HOST) ?>"/>
|
||||
<input type="hidden" name="DB_PORT" value="<?php echo htmlspecialchars($DB_PORT) ?>"/>
|
||||
<input type="hidden" name="DB_TYPE" value="<?php echo htmlspecialchars($DB_TYPE) ?>"/>
|
||||
<input type="hidden" name="SELF_URL_PATH" value="<?php echo htmlspecialchars($SELF_URL_PATH) ?>"/>
|
||||
|
||||
<p>
|
||||
<?php if ($need_confirm) { ?>
|
||||
<button onclick="return confirm('Please read the warning above. Continue?')" type="submit"
|
||||
class="alt-danger" dojoType="dijit.form.Button">Initialize database</button>
|
||||
<?php } else { ?>
|
||||
<button type="submit" class="alt-danger" dojoType="dijit.form.Button">Initialize database</button>
|
||||
<?php } ?>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
</td><td>
|
||||
<form method="post">
|
||||
<input type="hidden" name="DB_USER" value="<?php echo htmlspecialchars($DB_USER) ?>"/>
|
||||
<input type="hidden" name="DB_PASS" value="<?php echo htmlspecialchars($DB_PASS) ?>"/>
|
||||
<input type="hidden" name="DB_NAME" value="<?php echo htmlspecialchars($DB_NAME) ?>"/>
|
||||
<input type="hidden" name="DB_HOST" value="<?php echo htmlspecialchars($DB_HOST) ?>"/>
|
||||
<input type="hidden" name="DB_PORT" value="<?php echo htmlspecialchars($DB_PORT) ?>"/>
|
||||
<input type="hidden" name="DB_TYPE" value="<?php echo htmlspecialchars($DB_TYPE) ?>"/>
|
||||
<input type="hidden" name="SELF_URL_PATH" value="<?php echo htmlspecialchars($SELF_URL_PATH) ?>"/>
|
||||
|
||||
<input type="hidden" name="op" value="skipschema">
|
||||
|
||||
<p><button type="submit" dojoType="dijit.form.Button">Skip initialization</button></p>
|
||||
</form>
|
||||
|
||||
</td></tr></table>
|
||||
|
||||
<?php
|
||||
|
||||
} else if ($op == 'installschema' || $op == 'skipschema') {
|
||||
|
||||
$pdo = pdo_connect($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_TYPE, $DB_PORT);
|
||||
|
||||
if (!$pdo) {
|
||||
print_error("Unable to connect to database using specified parameters.");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($op == 'installschema') {
|
||||
|
||||
print "<h2>Initializing database...</h2>";
|
||||
|
||||
$lines = explode(";", preg_replace("/[\r\n]/", "",
|
||||
file_get_contents("../schema/ttrss_schema_".basename($DB_TYPE).".sql")));
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, "--") !== 0 && $line) {
|
||||
$res = $pdo->query($line);
|
||||
|
||||
if (!$res) {
|
||||
print_notice("Query: $line");
|
||||
print_error("Error: " . implode(", ", $pdo->errorInfo()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_notice("Database initialization completed.");
|
||||
|
||||
} else {
|
||||
print_notice("Database initialization skipped.");
|
||||
}
|
||||
|
||||
print "<h2>Generated configuration file</h2>";
|
||||
|
||||
print "<p>Copy following text and save as <code>config.php</code> in tt-rss main directory. It is suggested to read through the file to the end in case you need any options changed fom default values.</p>";
|
||||
|
||||
print "<p>After copying the file, you will be able to login with default username and password combination: <code>admin</code> and <code>password</code>. Don't forget to change the password immediately!</p>"; ?>
|
||||
|
||||
<form action="" method="post">
|
||||
<input type="hidden" name="op" value="saveconfig">
|
||||
<input type="hidden" name="DB_USER" value="<?php echo htmlspecialchars($DB_USER) ?>"/>
|
||||
<input type="hidden" name="DB_PASS" value="<?php echo htmlspecialchars($DB_PASS) ?>"/>
|
||||
<input type="hidden" name="DB_NAME" value="<?php echo htmlspecialchars($DB_NAME) ?>"/>
|
||||
<input type="hidden" name="DB_HOST" value="<?php echo htmlspecialchars($DB_HOST) ?>"/>
|
||||
<input type="hidden" name="DB_PORT" value="<?php echo htmlspecialchars($DB_PORT) ?>"/>
|
||||
<input type="hidden" name="DB_TYPE" value="<?php echo htmlspecialchars($DB_TYPE) ?>"/>
|
||||
<input type="hidden" name="SELF_URL_PATH" value="<?php echo htmlspecialchars($SELF_URL_PATH) ?>"/>
|
||||
<?php print "<textarea rows='20' style='width : 100%'>";
|
||||
echo htmlspecialchars(make_config($DB_TYPE, $DB_HOST, $DB_USER, $DB_NAME, $DB_PASS,
|
||||
$DB_PORT, $SELF_URL_PATH));
|
||||
print "</textarea>"; ?>
|
||||
|
||||
<hr/>
|
||||
|
||||
<?php if (is_writable("..")) { ?>
|
||||
<p>We can also try saving the file automatically now.</p>
|
||||
|
||||
<p><button type="submit" dojoType='dijit.form.Button' class='alt-primary'>Save configuration</button></p>
|
||||
</form>
|
||||
<?php } else {
|
||||
print_error("Unfortunately, parent directory is not writable, so we're unable to save config.php automatically.");
|
||||
}
|
||||
|
||||
print_notice("You can generate the file again by changing the form above.");
|
||||
|
||||
} else if ($op == "saveconfig") {
|
||||
|
||||
print "<h2>Saving configuration file to parent directory...</h2>";
|
||||
|
||||
if (!file_exists("../config.php")) {
|
||||
|
||||
$fp = fopen("../config.php", "w");
|
||||
|
||||
if ($fp) {
|
||||
$written = fwrite($fp, make_config($DB_TYPE, $DB_HOST,
|
||||
$DB_USER, $DB_NAME, $DB_PASS,
|
||||
$DB_PORT, $SELF_URL_PATH));
|
||||
|
||||
if ($written > 0) {
|
||||
print_notice("Successfully saved config.php. You can try <a href=\"..\">loading tt-rss now</a>.");
|
||||
|
||||
} else {
|
||||
print_notice("Unable to write into config.php in tt-rss directory.");
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
} else {
|
||||
print_error("Unable to open config.php in tt-rss directory for writing.");
|
||||
}
|
||||
} else {
|
||||
print_error("config.php already present in tt-rss directory, refusing to overwrite.");
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,19 @@
|
||||
/* eslint-disable prefer-rest-params */
|
||||
/* global dijit, define */
|
||||
define(["dojo/_base/declare", "dijit/Dialog"], function (declare) {
|
||||
return declare("fox.SingleUseDialog", dijit.Dialog, {
|
||||
create: function(params) {
|
||||
const extant = dijit.byId(params.id);
|
||||
|
||||
if (extant) {
|
||||
console.warn('SingleUseDialog: destroying existing widget:', params.id, '=', extant)
|
||||
extant.destroyRecursive();
|
||||
}
|
||||
|
||||
return this.inherited(arguments);
|
||||
},
|
||||
onHide: function() {
|
||||
this.destroyRecursive();
|
||||
}
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue