Compare commits
	
		
			605 Commits
		
	
	
		
			dev
			...
			feature.be
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b36b8e3afb | ||
| 
						 | 
					dc31155de1 | ||
| 
						 | 
					0bae69908b | ||
| 
						 | 
					47a077bc82 | ||
| 
						 | 
					836ff71364 | ||
| 
						 | 
					576c06ffaf | ||
| 
						 | 
					352dc3e179 | ||
| 
						 | 
					aa164936f4 | ||
| 
						 | 
					9a93acd1a5 | ||
| 
						 | 
					587aa11cda | ||
| 
						 | 
					d04ecd60c4 | ||
| 
						 | 
					aa68fe3485 | ||
| 
						 | 
					e408fe8733 | ||
| 
						 | 
					836986462a | ||
| 
						 | 
					51c6cdb20d | ||
| 
						 | 
					766232f29b | ||
| 
						 | 
					2ed021f30b | ||
| 
						 | 
					899fe561df | ||
| 
						 | 
					67d8936083 | ||
| 
						 | 
					78f5cef5bd | ||
| 
						 | 
					8928b1654c | ||
| 
						 | 
					977fa0d1d4 | ||
| 
						 | 
					1d89cec2cf | ||
| 
						 | 
					6c6b7d6bb2 | ||
| 
						 | 
					f92e88e9b4 | ||
| 
						 | 
					6e58899fdf | ||
| 
						 | 
					1513598420 | ||
| 
						 | 
					9cfc060760 | ||
| 
						 | 
					a531e34ec2 | ||
| 
						 | 
					2cce23b5b4 | ||
| 
						 | 
					e518bc9b68 | ||
| 
						 | 
					8e02d05ba9 | ||
| 
						 | 
					f131083315 | ||
| 
						 | 
					39ba5b37ef | ||
| 
						 | 
					002fa05c7f | ||
| 
						 | 
					1a52133fd4 | ||
| 
						 | 
					30900f7196 | ||
| 
						 | 
					b753cf0837 | ||
| 
						 | 
					53ab5b6aff | ||
| 
						 | 
					78a0402351 | ||
| 
						 | 
					5b5705fe90 | ||
| 
						 | 
					f08b5c5e3f | ||
| 
						 | 
					aa87acd432 | ||
| 
						 | 
					7af7ef8357 | ||
| 
						 | 
					627bc4e45f | ||
| 
						 | 
					909c76ebfb | ||
| 
						 | 
					0899ba8515 | ||
| 
						 | 
					b7c0b40491 | ||
| 
						 | 
					48257ccfed | ||
| 
						 | 
					269949a65f | ||
| 
						 | 
					c2de7123e4 | ||
| 
						 | 
					e0c6dcd5a4 | ||
| 
						 | 
					734ddda363 | ||
| 
						 | 
					02cd448f89 | ||
| 
						 | 
					5fc3a987de | ||
| 
						 | 
					be39d72d3c | ||
| 
						 | 
					045eb766f1 | ||
| 
						 | 
					22d4bd0e6f | ||
| 
						 | 
					5a7bf49cbb | ||
| 
						 | 
					a3dd1c45f3 | ||
| 
						 | 
					be7725694e | ||
| 
						 | 
					366d278182 | ||
| 
						 | 
					5f008a14f5 | ||
| 
						 | 
					21a8c281ec | ||
| 
						 | 
					51dde5766d | ||
| 
						 | 
					61d7b4a58d | ||
| 
						 | 
					19b35565a2 | ||
| 
						 | 
					baa88412b9 | ||
| 
						 | 
					6fb9b72e22 | ||
| 
						 | 
					b75884c57e | ||
| 
						 | 
					603188f6ce | ||
| 
						 | 
					15442140cf | ||
| 
						 | 
					9f877d4309 | ||
| 
						 | 
					8631529268 | ||
| 
						 | 
					96bc198b0f | ||
| 
						 | 
					146ffb5f4c | ||
| 
						 | 
					867c3d3382 | ||
| 
						 | 
					75b0f27fe2 | ||
| 
						 | 
					4b001d8f03 | ||
| 
						 | 
					a7bec96a80 | ||
| 
						 | 
					a8706e38ca | ||
| 
						 | 
					265fd41717 | ||
| 
						 | 
					67c23ffe24 | ||
| 
						 | 
					e9a09889e4 | ||
| 
						 | 
					e6756a2072 | ||
| 
						 | 
					f4050b8a76 | ||
| 
						 | 
					bb4fa7ca2f | ||
| 
						 | 
					2648603f24 | ||
| 
						 | 
					b7a9a1c8fd | ||
| 
						 | 
					d45566d692 | ||
| 
						 | 
					4411086f29 | ||
| 
						 | 
					0b3ce03036 | ||
| 
						 | 
					027886737d | ||
| 
						 | 
					35f45015ea | ||
| 
						 | 
					618f412bed | ||
| 
						 | 
					6ab9c754a5 | ||
| 
						 | 
					c59332c8f8 | ||
| 
						 | 
					32d9d04b4b | ||
| 
						 | 
					62d0fb6eee | ||
| 
						 | 
					bc82ee24d8 | ||
| 
						 | 
					6ab1f4a4f0 | ||
| 
						 | 
					3e6527bab3 | ||
| 
						 | 
					9b7691c1ae | ||
| 
						 | 
					c3cd494386 | ||
| 
						 | 
					fba5841bdc | ||
| 
						 | 
					da7fc3e7e3 | ||
| 
						 | 
					00ebe1633c | ||
| 
						 | 
					f63bfcb4f6 | ||
| 
						 | 
					177cf836b4 | ||
| 
						 | 
					04069871bb | ||
| 
						 | 
					31469204e0 | ||
| 
						 | 
					9b148c8c4a | ||
| 
						 | 
					98f4eac82a | ||
| 
						 | 
					29d966cdb3 | ||
| 
						 | 
					23d2bd552c | ||
| 
						 | 
					41cfbbd0d9 | ||
| 
						 | 
					cc89a7e6f8 | ||
| 
						 | 
					c486fe98fb | ||
| 
						 | 
					f0ac256ff6 | ||
| 
						 | 
					e4b3adb2f5 | ||
| 
						 | 
					58a5bc46d9 | ||
| 
						 | 
					6e5c3a551d | ||
| 
						 | 
					52f8261d8c | ||
| 
						 | 
					721c385993 | ||
| 
						 | 
					9c80de19d7 | ||
| 
						 | 
					4ffd11caf5 | ||
| 
						 | 
					f2c4faf054 | ||
| 
						 | 
					bc54b663e0 | ||
| 
						 | 
					6f1bb309d4 | ||
| 
						 | 
					cdedaa4604 | ||
| 
						 | 
					2130b327df | ||
| 
						 | 
					3e238bea0b | ||
| 
						 | 
					266c297632 | ||
| 
						 | 
					4477b07bc6 | ||
| 
						 | 
					74b44186d2 | ||
| 
						 | 
					c2bf1bac38 | ||
| 
						 | 
					c279313048 | ||
| 
						 | 
					0301ba0336 | ||
| 
						 | 
					5e6d61b3a7 | ||
| 
						 | 
					4e00051a10 | ||
| 
						 | 
					f8b49c805a | ||
| 
						 | 
					7ac254169c | ||
| 
						 | 
					f95ff57db9 | ||
| 
						 | 
					c2207452bd | ||
| 
						 | 
					a28604ae4f | ||
| 
						 | 
					4e53b7d178 | ||
| 
						 | 
					38e6e4f8d3 | ||
| 
						 | 
					dfa223b39e | ||
| 
						 | 
					0a8d771c11 | ||
| 
						 | 
					5afd073ff3 | ||
| 
						 | 
					46fda14e08 | ||
| 
						 | 
					bbf9ef8b3d | ||
| 
						 | 
					2b7de0b7a4 | ||
| 
						 | 
					f590b1c5f8 | ||
| 
						 | 
					bae73f266c | ||
| 
						 | 
					b00dbfaeaa | ||
| 
						 | 
					e2e1e50890 | ||
| 
						 | 
					423548dc2c | ||
| 
						 | 
					545117eeae | ||
| 
						 | 
					0b0392a274 | ||
| 
						 | 
					0c0236c766 | ||
| 
						 | 
					067d88136e | ||
| 
						 | 
					d0cd0bb9bf | ||
| 
						 | 
					69ecb1a049 | ||
| 
						 | 
					e50f2f2b5f | ||
| 
						 | 
					905a81a715 | ||
| 
						 | 
					4d0d41ef02 | ||
| 
						 | 
					15e7adaefb | ||
| 
						 | 
					fea67af4df | ||
| 
						 | 
					86832e4a36 | ||
| 
						 | 
					c6ffc47c24 | ||
| 
						 | 
					40881bb4e5 | ||
| 
						 | 
					7cedd4af7a | ||
| 
						 | 
					6832e45b1f | ||
| 
						 | 
					e31f10e648 | ||
| 
						 | 
					336faa4503 | ||
| 
						 | 
					fbd1ebc294 | ||
| 
						 | 
					7bcfb806b0 | ||
| 
						 | 
					2908e28999 | ||
| 
						 | 
					a188e1b275 | ||
| 
						 | 
					52d76cd146 | ||
| 
						 | 
					4eb34044ff | ||
| 
						 | 
					ccb1bd33d4 | ||
| 
						 | 
					ba14aec7a3 | ||
| 
						 | 
					41fb1bf13c | ||
| 
						 | 
					ddb05e68a3 | ||
| 
						 | 
					b544792f36 | ||
| 
						 | 
					a069661b4a | ||
| 
						 | 
					07c94b97f9 | ||
| 
						 | 
					116a7e34db | ||
| 
						 | 
					f4fc2342d5 | ||
| 
						 | 
					f7228ef2ec | ||
| 
						 | 
					207eb1322d | ||
| 
						 | 
					390c9b5048 | ||
| 
						 | 
					58804923ef | ||
| 
						 | 
					296091e977 | ||
| 
						 | 
					77ba17eb61 | ||
| 
						 | 
					a489cb15b5 | ||
| 
						 | 
					b0bebb44e6 | ||
| 
						 | 
					74baebde96 | ||
| 
						 | 
					caac979263 | ||
| 
						 | 
					bbc9bd6ef4 | ||
| 
						 | 
					d445a65788 | ||
| 
						 | 
					cdebac0b13 | ||
| 
						 | 
					651617b8bf | ||
| 
						 | 
					0e6eb9059a | ||
| 
						 | 
					22d6453d56 | ||
| 
						 | 
					a7b62a30ff | ||
| 
						 | 
					d5e6d355ab | ||
| 
						 | 
					431e199c1c | ||
| 
						 | 
					3884bcdd01 | ||
| 
						 | 
					fa974eb8dd | ||
| 
						 | 
					023a3ce879 | ||
| 
						 | 
					c67e61cbaa | ||
| 
						 | 
					cdeefb43a5 | ||
| 
						 | 
					93af6a68bd | ||
| 
						 | 
					146e956432 | ||
| 
						 | 
					afa91188d8 | ||
| 
						 | 
					c2a9a60aa7 | ||
| 
						 | 
					d654c95689 | ||
| 
						 | 
					439f1fefe2 | ||
| 
						 | 
					4245617c6f | ||
| 
						 | 
					5635cdcac7 | ||
| 
						 | 
					dd05a58c8c | ||
| 
						 | 
					a437da6a4c | ||
| 
						 | 
					0334e88625 | ||
| 
						 | 
					05b081e6d4 | ||
| 
						 | 
					c7c39c6fa1 | ||
| 
						 | 
					0a7c2bca85 | ||
| 
						 | 
					fd7a53a854 | ||
| 
						 | 
					9ccbcea743 | ||
| 
						 | 
					4b75a55639 | ||
| 
						 | 
					90b9279c3a | ||
| 
						 | 
					80be99890a | ||
| 
						 | 
					d4929622bf | ||
| 
						 | 
					7059150ac8 | ||
| 
						 | 
					777937b70c | ||
| 
						 | 
					b4b53d3e22 | ||
| 
						 | 
					97bc383bc0 | ||
| 
						 | 
					dbd088458e | ||
| 
						 | 
					0739b856e0 | ||
| 
						 | 
					81acb276d8 | ||
| 
						 | 
					80e965a904 | ||
| 
						 | 
					39c29dbe10 | ||
| 
						 | 
					51c42a65c5 | ||
| 
						 | 
					784c9718eb | ||
| 
						 | 
					874b5aa7e9 | ||
| 
						 | 
					bf28a24b72 | ||
| 
						 | 
					dffeb1f9d5 | ||
| 
						 | 
					6d7d7ebbfa | ||
| 
						 | 
					5b41fe3167 | ||
| 
						 | 
					28f36dc7b0 | ||
| 
						 | 
					dc2881ad02 | ||
| 
						 | 
					653299f4d8 | ||
| 
						 | 
					704850f5c6 | ||
| 
						 | 
					1548d4184e | ||
| 
						 | 
					abc8b8f4ab | ||
| 
						 | 
					08ef485ca9 | ||
| 
						 | 
					35e670a1ab | ||
| 
						 | 
					deb1ea4d2b | ||
| 
						 | 
					3aac4d528a | ||
| 
						 | 
					70e8d12420 | ||
| 
						 | 
					ad15b13f7e | ||
| 
						 | 
					b4bc6abace | ||
| 
						 | 
					e6f255bc56 | ||
| 
						 | 
					470a961a80 | ||
| 
						 | 
					d08fee129b | ||
| 
						 | 
					8993d2988b | ||
| 
						 | 
					e776fc0713 | ||
| 
						 | 
					c0ccdc768a | ||
| 
						 | 
					039ae01cfa | ||
| 
						 | 
					612d00ade2 | ||
| 
						 | 
					fe001abd26 | ||
| 
						 | 
					b800ffcc1f | ||
| 
						 | 
					8a55952204 | ||
| 
						 | 
					8b2363fd6f | ||
| 
						 | 
					e7a0d9d497 | ||
| 
						 | 
					c807c4b734 | ||
| 
						 | 
					0af51d90ba | ||
| 
						 | 
					972df3b231 | ||
| 
						 | 
					1887e9effd | ||
| 
						 | 
					7e10524f92 | ||
| 
						 | 
					c3ff90c4f8 | ||
| 
						 | 
					2c4f2d3037 | ||
| 
						 | 
					07aec736f5 | ||
| 
						 | 
					b30bdc9833 | ||
| 
						 | 
					d171a249c5 | ||
| 
						 | 
					d994e0efb0 | ||
| 
						 | 
					0379b862ec | ||
| 
						 | 
					5af2ab4d97 | ||
| 
						 | 
					b518385962 | ||
| 
						 | 
					d25bc74d71 | ||
| 
						 | 
					5641173a04 | ||
| 
						 | 
					806e69b858 | ||
| 
						 | 
					a29a2a0fa4 | ||
| 
						 | 
					4951498c0b | ||
| 
						 | 
					97ea828532 | ||
| 
						 | 
					d539eaf64b | ||
| 
						 | 
					90edbf0d8b | ||
| 
						 | 
					0c575ccc74 | ||
| 
						 | 
					eebdc5ad12 | ||
| 
						 | 
					617589f41b | ||
| 
						 | 
					50638c768f | ||
| 
						 | 
					29b8bf8a4e | ||
| 
						 | 
					42936cc51d | ||
| 
						 | 
					b138548a10 | ||
| 
						 | 
					7d5ba6248e | ||
| 
						 | 
					36a58dcae2 | ||
| 
						 | 
					2106a5fbdc | ||
| 
						 | 
					0897c8608f | ||
| 
						 | 
					a32d6d8265 | ||
| 
						 | 
					0f9f337e66 | ||
| 
						 | 
					bb13722a2f | ||
| 
						 | 
					77cdbefe0c | ||
| 
						 | 
					f68bba1292 | ||
| 
						 | 
					37d0ccc728 | ||
| 
						 | 
					7557a73014 | ||
| 
						 | 
					f3f00dd182 | ||
| 
						 | 
					a2297decfd | ||
| 
						 | 
					07b2b2f305 | ||
| 
						 | 
					85df9e5ab2 | ||
| 
						 | 
					439014b0b1 | ||
| 
						 | 
					f6fc5ae466 | ||
| 
						 | 
					132c4f6c89 | ||
| 
						 | 
					21cdf60c31 | ||
| 
						 | 
					b4f8cf8c50 | ||
| 
						 | 
					f4dbabe8de | ||
| 
						 | 
					c5c7ebff04 | ||
| 
						 | 
					5d263e78e0 | ||
| 
						 | 
					5f4e1da0c8 | ||
| 
						 | 
					187365db76 | ||
| 
						 | 
					e26a5c8cdf | ||
| 
						 | 
					a384c60937 | ||
| 
						 | 
					6b4abac08e | ||
| 
						 | 
					e789263068 | ||
| 
						 | 
					82aaf43d5d | ||
| 
						 | 
					adae73080c | ||
| 
						 | 
					b648e6f7a7 | ||
| 
						 | 
					194dcad0e3 | ||
| 
						 | 
					af3df142d4 | ||
| 
						 | 
					f2a9518b5c | ||
| 
						 | 
					593240b420 | ||
| 
						 | 
					8aae0f29d4 | ||
| 
						 | 
					14d07a22e2 | ||
| 
						 | 
					3eb0247b37 | ||
| 
						 | 
					7f32a5679d | ||
| 
						 | 
					a442fd588e | ||
| 
						 | 
					e7ce32fc26 | ||
| 
						 | 
					f0a606e5cf | ||
| 
						 | 
					0c4846c986 | ||
| 
						 | 
					31e81118dd | ||
| 
						 | 
					e47d51d14c | ||
| 
						 | 
					ce436cc6ec | ||
| 
						 | 
					3d1ac97dc3 | ||
| 
						 | 
					7a2065c687 | ||
| 
						 | 
					9e6c7dad88 | ||
| 
						 | 
					4f23ea4dd5 | ||
| 
						 | 
					f25dbd8872 | ||
| 
						 | 
					de036920a4 | ||
| 
						 | 
					061c806588 | ||
| 
						 | 
					780267978d | ||
| 
						 | 
					0ad7f47885 | ||
| 
						 | 
					d2f0bac907 | ||
| 
						 | 
					e46a6fa171 | ||
| 
						 | 
					7cbc690890 | ||
| 
						 | 
					a46fd28dae | ||
| 
						 | 
					8decc383aa | ||
| 
						 | 
					f3268b3d37 | ||
| 
						 | 
					4fdf248cb6 | ||
| 
						 | 
					1fd1e63043 | ||
| 
						 | 
					5ce324f35f | ||
| 
						 | 
					7303e9dd58 | ||
| 
						 | 
					e57fc18bcb | ||
| 
						 | 
					9a8148079d | ||
| 
						 | 
					24762adecc | ||
| 
						 | 
					2ee665ac96 | ||
| 
						 | 
					2e1a91622e | ||
| 
						 | 
					4687355351 | ||
| 
						 | 
					38dd610319 | ||
| 
						 | 
					a4408de74d | ||
| 
						 | 
					7a952215aa | ||
| 
						 | 
					982b330739 | ||
| 
						 | 
					598451f470 | ||
| 
						 | 
					3e9229be5f | ||
| 
						 | 
					ad6148cae7 | ||
| 
						 | 
					2aebc0e099 | ||
| 
						 | 
					06022bf378 | ||
| 
						 | 
					430c9cb00e | ||
| 
						 | 
					89780b0317 | ||
| 
						 | 
					c534894995 | ||
| 
						 | 
					a91ce1600f | ||
| 
						 | 
					145f6d0d1d | ||
| 
						 | 
					04e7d7c99b | ||
| 
						 | 
					7ed69c7f4a | ||
| 
						 | 
					8f914c44a1 | ||
| 
						 | 
					28ce82c6f6 | ||
| 
						 | 
					641a3b24a5 | ||
| 
						 | 
					0d61866b89 | ||
| 
						 | 
					c1a477e7d0 | ||
| 
						 | 
					692d2e53b2 | ||
| 
						 | 
					47acc73451 | ||
| 
						 | 
					2f22d48dd0 | ||
| 
						 | 
					e0b0a732b4 | ||
| 
						 | 
					59ad0f5b11 | ||
| 
						 | 
					3b53ceedcd | ||
| 
						 | 
					15a2bd90b3 | ||
| 
						 | 
					c49a8204e0 | ||
| 
						 | 
					601e894935 | ||
| 
						 | 
					445c90fefe | ||
| 
						 | 
					b7ced5fa69 | ||
| 
						 | 
					995f1a13c3 | ||
| 
						 | 
					cf22909722 | ||
| 
						 | 
					02603fd8fd | ||
| 
						 | 
					af4f5aaeb0 | ||
| 
						 | 
					f8012c37d1 | ||
| 
						 | 
					5954cd0ad1 | ||
| 
						 | 
					5ef6e6c08f | ||
| 
						 | 
					94a5464155 | ||
| 
						 | 
					18ba8d0ac5 | ||
| 
						 | 
					9fd4334dec | ||
| 
						 | 
					d7843a55bd | ||
| 
						 | 
					b7833b5d23 | ||
| 
						 | 
					0f617f8e09 | ||
| 
						 | 
					fbc1fab68d | ||
| 
						 | 
					68890c3c48 | ||
| 
						 | 
					10f08386f7 | ||
| 
						 | 
					45fa309c07 | ||
| 
						 | 
					2ad13a96f9 | ||
| 
						 | 
					73f95de843 | ||
| 
						 | 
					c13d1aba30 | ||
| 
						 | 
					42f561dc75 | ||
| 
						 | 
					8280add935 | ||
| 
						 | 
					bbc29c889f | ||
| 
						 | 
					e5ee6d8fca | ||
| 
						 | 
					738a8bdd60 | ||
| 
						 | 
					119faf55cf | ||
| 
						 | 
					7b0ece42f0 | ||
| 
						 | 
					3c29e4e6ae | ||
| 
						 | 
					a5f97c59d9 | ||
| 
						 | 
					77c7f8ecfe | ||
| 
						 | 
					dc34829afe | ||
| 
						 | 
					10245113d5 | ||
| 
						 | 
					349970cfaf | ||
| 
						 | 
					0056e3f0b2 | ||
| 
						 | 
					4e7d3618a7 | ||
| 
						 | 
					548a427148 | ||
| 
						 | 
					49e5aa783d | ||
| 
						 | 
					b9d8eeea9e | ||
| 
						 | 
					75777daf4b | ||
| 
						 | 
					d3b76975cd | ||
| 
						 | 
					64fb5b3bf0 | ||
| 
						 | 
					51842c02f6 | ||
| 
						 | 
					cc6f1c7cd8 | ||
| 
						 | 
					5d24931d9b | ||
| 
						 | 
					e2bce19526 | ||
| 
						 | 
					9653f1a341 | ||
| 
						 | 
					a5a5c7b2b6 | ||
| 
						 | 
					c486ca928b | ||
| 
						 | 
					3a28fe9d28 | ||
| 
						 | 
					8da6fe7287 | ||
| 
						 | 
					52be41186f | ||
| 
						 | 
					96e8f4ae3c | ||
| 
						 | 
					355f9ca05f | ||
| 
						 | 
					ef40b0a34e | ||
| 
						 | 
					73f6430a60 | ||
| 
						 | 
					2b107c1610 | ||
| 
						 | 
					93d379f293 | ||
| 
						 | 
					6d415e9ec5 | ||
| 
						 | 
					577aac2069 | ||
| 
						 | 
					73641a03d5 | ||
| 
						 | 
					0b580ba2ec | ||
| 
						 | 
					a8a292a3c5 | ||
| 
						 | 
					ec1d21a625 | ||
| 
						 | 
					078c6feae8 | ||
| 
						 | 
					63aedd6b42 | ||
| 
						 | 
					1e3dfef438 | ||
| 
						 | 
					c72e9f4eb0 | ||
| 
						 | 
					857c11933a | ||
| 
						 | 
					30a3515e94 | ||
| 
						 | 
					ff4f8afde8 | ||
| 
						 | 
					8050463a12 | ||
| 
						 | 
					73dba2fa1a | ||
| 
						 | 
					3c46b7734f | ||
| 
						 | 
					48ff314487 | ||
| 
						 | 
					4fa7a20fd3 | ||
| 
						 | 
					75280d6892 | ||
| 
						 | 
					6c5fde77b4 | ||
| 
						 | 
					646cf6479b | ||
| 
						 | 
					1d24432f8d | ||
| 
						 | 
					37aa3d8b69 | ||
| 
						 | 
					9437a19ee0 | ||
| 
						 | 
					6c04fa80bd | ||
| 
						 | 
					a667227943 | ||
| 
						 | 
					8d32905913 | ||
| 
						 | 
					58ab16432c | ||
| 
						 | 
					aa383ffb1b | ||
| 
						 | 
					f3cadfe013 | ||
| 
						 | 
					c7aa91fc29 | ||
| 
						 | 
					e5934e0a3a | ||
| 
						 | 
					b6c5e803cb | ||
| 
						 | 
					ec8ff4f76b | ||
| 
						 | 
					06668db697 | ||
| 
						 | 
					cab5d35771 | ||
| 
						 | 
					a82b4b7b6b | ||
| 
						 | 
					37e0604647 | ||
| 
						 | 
					4765ca2232 | ||
| 
						 | 
					6edaf03faa | ||
| 
						 | 
					0dedee4429 | ||
| 
						 | 
					078c75514e | ||
| 
						 | 
					838645463d | ||
| 
						 | 
					157819195b | ||
| 
						 | 
					25d7a5a776 | ||
| 
						 | 
					9d9541ee11 | ||
| 
						 | 
					c1e23ae9e2 | ||
| 
						 | 
					14e074b122 | ||
| 
						 | 
					c3ae1970ca | ||
| 
						 | 
					fbcdacdccf | ||
| 
						 | 
					f473691479 | ||
| 
						 | 
					f7a7ac39f3 | ||
| 
						 | 
					ca5270a074 | ||
| 
						 | 
					0e421c0b34 | ||
| 
						 | 
					cb375dea26 | ||
| 
						 | 
					6845f59150 | ||
| 
						 | 
					021805f1e1 | ||
| 
						 | 
					95881bd7f1 | ||
| 
						 | 
					3c59c80bcc | ||
| 
						 | 
					b3bacf25ff | ||
| 
						 | 
					99992442f5 | ||
| 
						 | 
					dd56d681b3 | ||
| 
						 | 
					ba18f30c4f | ||
| 
						 | 
					82e2684004 | ||
| 
						 | 
					dc6f25cdef | ||
| 
						 | 
					36c1f98438 | ||
| 
						 | 
					ac7be1bde2 | ||
| 
						 | 
					1a067ca107 | ||
| 
						 | 
					c321e4828b | ||
| 
						 | 
					68cc1ed5d3 | ||
| 
						 | 
					69a7a58ce7 | ||
| 
						 | 
					c0dfd6b5dc | ||
| 
						 | 
					7d6cb22291 | ||
| 
						 | 
					32507085b4 | ||
| 
						 | 
					c488278ce1 | ||
| 
						 | 
					ad17d797b2 | ||
| 
						 | 
					2b1a741946 | ||
| 
						 | 
					cbae598893 | ||
| 
						 | 
					d153cd9049 | ||
| 
						 | 
					2a15e2a350 | ||
| 
						 | 
					1815aaa909 | ||
| 
						 | 
					bdea6e4cbb | ||
| 
						 | 
					75af09062f | ||
| 
						 | 
					c366124917 | ||
| 
						 | 
					16ce0da5f9 | ||
| 
						 | 
					2ab52e7eaf | ||
| 
						 | 
					fcefbe719f | ||
| 
						 | 
					7d3f7e226c | ||
| 
						 | 
					3848bd8d22 | ||
| 
						 | 
					0873d44162 | ||
| 
						 | 
					6cf4f00f92 | ||
| 
						 | 
					d6c0b7a680 | ||
| 
						 | 
					25e296fbb6 | ||
| 
						 | 
					e235242ea6 | ||
| 
						 | 
					9b39d63104 | ||
| 
						 | 
					8915b45948 | ||
| 
						 | 
					29e053bf6b | ||
| 
						 | 
					a15107558c | ||
| 
						 | 
					b320375d60 | ||
| 
						 | 
					fb66e1e98f | ||
| 
						 | 
					2f0156e63b | ||
| 
						 | 
					e31d822d74 | ||
| 
						 | 
					15cba014ba | ||
| 
						 | 
					88a1bf9c94 | ||
| 
						 | 
					d014b36ee2 | ||
| 
						 | 
					cefdceba13 | ||
| 
						 | 
					1c8490be6b | ||
| 
						 | 
					1243a8f6ad | ||
| 
						 | 
					d98dda645e | ||
| 
						 | 
					44ea8f1817 | ||
| 
						 | 
					6ef721d3a8 | ||
| 
						 | 
					7d84da4d7c | ||
| 
						 | 
					a53c6a1778 | ||
| 
						 | 
					b75286375e | ||
| 
						 | 
					ad8a6d3071 | ||
| 
						 | 
					54b37ac5b6 | ||
| 
						 | 
					bc434504b1 | ||
| 
						 | 
					88ce10b8e6 | ||
| 
						 | 
					0abb706ded | ||
| 
						 | 
					54abd7bc7b | ||
| 
						 | 
					c866d09e27 | ||
| 
						 | 
					668b49df40 | ||
| 
						 | 
					3476541bf4 | ||
| 
						 | 
					2c9cf96245 | ||
| 
						 | 
					39699ba723 | ||
| 
						 | 
					0fd0c40e89 | ||
| 
						 | 
					d31cd2aef4 | ||
| 
						 | 
					c83e2e6f30 | ||
| 
						 | 
					f55769a946 | ||
| 
						 | 
					8345a02aaa | ||
| 
						 | 
					1c51437569 | ||
| 
						 | 
					63cdede854 | ||
| 
						 | 
					d216e21858 | ||
| 
						 | 
					358a774017 | ||
| 
						 | 
					3c162486e4 | ||
| 
						 | 
					a3f40447c0 | ||
| 
						 | 
					96784b6e7c | ||
| 
						 | 
					37cb05ff38 | 
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
src/IDF/version.php     export-subst
 | 
			
		||||
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,4 @@
 | 
			
		||||
*~
 | 
			
		||||
tmp
 | 
			
		||||
src/IDF/conf/idf.php
 | 
			
		||||
src/IDF/conf/idf.test.php
 | 
			
		||||
@@ -5,3 +6,9 @@ www/test.php
 | 
			
		||||
www/media/upload
 | 
			
		||||
src/IDF/gettexttemplates
 | 
			
		||||
indefero-*.zip
 | 
			
		||||
src/IDF/conf/path.php
 | 
			
		||||
.tx/config
 | 
			
		||||
src/IDF/locale/idf.pot.bak
 | 
			
		||||
test/test.db
 | 
			
		||||
test/tmp
 | 
			
		||||
test/config.php
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								AUTHORS
									
									
									
									
									
								
							@@ -1,18 +1,43 @@
 | 
			
		||||
InDefero was originally created during summer 2008 
 | 
			
		||||
InDefero was originally created during summer 2008
 | 
			
		||||
by Loïc d'Anterroches with the support of Céondo Ltd.
 | 
			
		||||
 | 
			
		||||
Much appreciated contributors:
 | 
			
		||||
Much appreciated contributors (in alphabetical order):
 | 
			
		||||
 | 
			
		||||
    Nicolas Lassalle <http://www.beroot.org/> - Subversion support
 | 
			
		||||
    bohwaz <http://bohwaz.net/>
 | 
			
		||||
    Benjamin Jorand <benjamin.jorand@gmail.com> - Mercurial support
 | 
			
		||||
    Baptiste Michaud <bactisme@gmail.com> - Subversion synchronization
 | 
			
		||||
    Julien Issler 
 | 
			
		||||
    Adrien Bustany <madcat@mymadcat.com>
 | 
			
		||||
    Andrew Nguyen <andrew-git-indefero@na-consulting.net>
 | 
			
		||||
    Baptiste Durand-Bret <bathizte@ozazar.org>
 | 
			
		||||
    Baptiste Michaud <bactisme@gmail.com>           - Subversion sync
 | 
			
		||||
    Benjamin Jorand <benjamin.jorand@gmail.com>     - Mercurial support
 | 
			
		||||
    Brenda Wallace <shiny@cpan.org>
 | 
			
		||||
    Brian Armstrong <brianar>
 | 
			
		||||
    Charles Melbye <charlie@yourwiki.net>
 | 
			
		||||
    Ciaran Gultnieks <ciaran@ciarang.com>
 | 
			
		||||
    David Feeney <davidf>
 | 
			
		||||
    Denis Kot <denis.kot@gmail.com>                 - Russian translation
 | 
			
		||||
    Dmitry Dulepov <dmitryd>
 | 
			
		||||
    Fernando Sayago Gil <mikados.mikados@gmail.com> - Spanish translation
 | 
			
		||||
    Jakub Viták <mainiak@gmail.com>                 - Czech translation
 | 
			
		||||
    Janez Troha <http://www.dz0ny.info>             - Slovenian translation
 | 
			
		||||
    Jean-Philippe Fleury <jpfleury>
 | 
			
		||||
    Jerry <lxb429@gmail.com>                        - Chinese translation
 | 
			
		||||
    Julien Issler <julien@issler.net>
 | 
			
		||||
    Ludovic Bellière <xrogaan>
 | 
			
		||||
    Manuel Eidenberger <eidenberger@gmail.com>
 | 
			
		||||
    Ciaran Gultnieks
 | 
			
		||||
    Mehdi Kabab
 | 
			
		||||
    Sindre R. Myren
 | 
			
		||||
    Matthew Dawson <mjd>
 | 
			
		||||
    Matías Halles <matias@halles.cl>
 | 
			
		||||
    Mehdi Kabab <http://pioupioum.fr/>
 | 
			
		||||
    Nicolas Lassalle <nicolas@beroot.org>           - Subversion support
 | 
			
		||||
    Patrick Georgi <patrick.georgi@coresystems.de>
 | 
			
		||||
    Raphaël Emourgeon <raphael>
 | 
			
		||||
    Samuel Suther <info@suther.de>                  - German translation
 | 
			
		||||
    Sindre R. Myren <sindrero@stud.ntnu.no>
 | 
			
		||||
    Stewart Platt <stew@futurete.ch>
 | 
			
		||||
    Thomas Keller <me@thomaskeller.biz>             - Monotone support
 | 
			
		||||
    Vladimir Solomatin <slash>
 | 
			
		||||
    William Martin <william.martin@lcpc.fr>
 | 
			
		||||
    Xavier Brochard <xavier@alternatif.org>
 | 
			
		||||
    bohwaz <http://bohwaz.net/>
 | 
			
		||||
 | 
			
		||||
And all the nice users who spent time reporting issues and promoting
 | 
			
		||||
the project. The project could not live without them.
 | 
			
		||||
the project. The project could not live without them.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										116
									
								
								CONTRIBUTE.mdtext
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								CONTRIBUTE.mdtext
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
[Indefero][idf] is not only a software you can use either hosted for
 | 
			
		||||
you or hosted by you, but also a free software you can contribute to. 
 | 
			
		||||
 | 
			
		||||
Here you will get how to contribute and what to contribute.
 | 
			
		||||
 | 
			
		||||
[idf]: http://www.indefero.net 
 | 
			
		||||
 | 
			
		||||
# Quick Way on How to Contribute
 | 
			
		||||
 | 
			
		||||
Simple contribution:
 | 
			
		||||
 | 
			
		||||
1. Open a ticket with your idea. You can directly propose a patch if
 | 
			
		||||
   you have it.
 | 
			
		||||
 | 
			
		||||
2. Wait for it to be checked by the devs or meet us on the #indefero
 | 
			
		||||
   channel on [FreeNode][freenode].
 | 
			
		||||
 | 
			
		||||
Bigger contribution:
 | 
			
		||||
 | 
			
		||||
1. Fork Indefero where you want (fork from the develop branch).
 | 
			
		||||
 | 
			
		||||
2. Code your change and document it.
 | 
			
		||||
 | 
			
		||||
3. Open a ticket with a pull request and talk about it on IRC.
 | 
			
		||||
 | 
			
		||||
# The General Contribution Workflow for Regular Contributors
 | 
			
		||||
 | 
			
		||||
1. Fork Indefero from the **develop** branch.
 | 
			
		||||
2. Request a pull request if you do not have write access on the repository.
 | 
			
		||||
3. Merge your changes without fast forward in develop. This keeps track of 
 | 
			
		||||
   the history of the changes and makes understanding what is going on easy.
 | 
			
		||||
4. Merge your changes with fast forward **only if a single commit**.
 | 
			
		||||
 | 
			
		||||
Indefero is composed of two main branches:
 | 
			
		||||
 | 
			
		||||
1. **master**: this is the shipped branch, only a select number of people
 | 
			
		||||
   can push into it.
 | 
			
		||||
2. **develop**: this is the development branch, all the people having write
 | 
			
		||||
   access to the repository are welcomed to push in.
 | 
			
		||||
 | 
			
		||||
**Note:** The branching model we use is [explained in details here][bmi]. You
 | 
			
		||||
**must** understand it to really contribute to the code base in an
 | 
			
		||||
efficient way. 
 | 
			
		||||
 | 
			
		||||
[bmi]: http://nvie.com/git-model "A successful Git branching model"
 | 
			
		||||
 | 
			
		||||
# What to Contribute
 | 
			
		||||
 | 
			
		||||
Contribution is easy, you can contribute in a lot of different fields,
 | 
			
		||||
contributions small or big are always appreciated. Here is an example
 | 
			
		||||
list of what you can do:
 | 
			
		||||
 | 
			
		||||
- Install InDefero on your system and report the problem you had.
 | 
			
		||||
- Find the bad English and typos and propose corrections.
 | 
			
		||||
- Help with the translation effort.
 | 
			
		||||
- Find little bugs or usability problems and provide ideas on how to fix them.
 | 
			
		||||
- Register to the [discussion group][group] and help new users.
 | 
			
		||||
- Come and chat on IRC #indefero on the [FreeNode][freenode] servers.
 | 
			
		||||
- Find ways to improve the design while keeping it **beautifully simple**.
 | 
			
		||||
- Write a blog post about the project, what you think is good or bad.
 | 
			
		||||
- Translate InDefero for the sake of the community.
 | 
			
		||||
- Or maybe really hack into the code.
 | 
			
		||||
 | 
			
		||||
As you can see, the real hacking into the code is just a small part of the work, so even if you are not a coder you can do a lot.
 | 
			
		||||
 | 
			
		||||
[group]: http://groups.google.com/group/indefero-users
 | 
			
		||||
[freenode]: http://freenode.net/ 
 | 
			
		||||
 | 
			
		||||
## I am a simple user
 | 
			
		||||
 | 
			
		||||
Thanks a lot! Really! As a project leader, I consider **you** as 
 | 
			
		||||
**the most important person in the success of the project**. So do not
 | 
			
		||||
worry, I will really listen to your needs and make you love this
 | 
			
		||||
project.
 | 
			
		||||
 | 
			
		||||
What you can do to help:
 | 
			
		||||
 | 
			
		||||
- Use the software and each time you find something a bit annoying in your daily use, report a bug. Usability issues are high priority issues.
 | 
			
		||||
- Find typos, grammar mistakes, etc. and report a bug.
 | 
			
		||||
- Write about InDefero on your blog/website.
 | 
			
		||||
- Read the issues submitted by the users and provide answers if you have them.
 | 
			
		||||
- ...
 | 
			
		||||
 | 
			
		||||
## I am a designer
 | 
			
		||||
 | 
			
		||||
A lot of things to do for you:
 | 
			
		||||
 | 
			
		||||
- Check the design and find the flaws in it. Is the space well used, does it look really nice and is it also functional for the first users?
 | 
			
		||||
- Do we have good support of all the major browsers?
 | 
			
		||||
- ...
 | 
			
		||||
 | 
			
		||||
## I am a coder
 | 
			
		||||
 | 
			
		||||
Checkout the code and have fun, but keep in mind that your results
 | 
			
		||||
must be simple to use. Do not worry about the beautiful part, the
 | 
			
		||||
designers can work on that.
 | 
			
		||||
 | 
			
		||||
## I am a security guy
 | 
			
		||||
 | 
			
		||||
Please, do try to break it, if you find a problem, come on IRC or
 | 
			
		||||
contact the developers to get the issue fixed as soon as
 | 
			
		||||
possible. Please, be nice, do not release the issue in the wild
 | 
			
		||||
without first talking to us.
 | 
			
		||||
 | 
			
		||||
## I am a translator
 | 
			
		||||
 | 
			
		||||
We currently use [transifex](http://trac.transifex.org) to help our
 | 
			
		||||
users translate indefero. You don't have to use it, but it's an easy
 | 
			
		||||
way to do the job. You can visit the indefero page at transifex here:
 | 
			
		||||
 | 
			
		||||
    http://www.transifex.net/projects/p/indefero/
 | 
			
		||||
 | 
			
		||||
Please understand that your changes will not be commited instantly,
 | 
			
		||||
but are sent to the maintainers e-mails before. Then, your changes
 | 
			
		||||
will not be in the main repository until the maintainer pushs the
 | 
			
		||||
changes. In that way, try to do big changes with less submissions.
 | 
			
		||||
@@ -4,6 +4,12 @@ The installation of InDefero is composed of 2 parts, first the
 | 
			
		||||
installation of the [Pluf framework](http://www.pluf.org) and second,
 | 
			
		||||
the installation of InDefero by itself.
 | 
			
		||||
 | 
			
		||||
## PHP modules for indefero
 | 
			
		||||
 | 
			
		||||
Indefero need the GD module for PHP. It's named "php5-gd" in debian.
 | 
			
		||||
 | 
			
		||||
    $ apt-get install php5-gd
 | 
			
		||||
 | 
			
		||||
## Recommended Layout of the Files
 | 
			
		||||
 | 
			
		||||
If your server document root is in `/var/www` a good thing is to keep
 | 
			
		||||
@@ -37,6 +43,7 @@ docroot.
 | 
			
		||||
    $ sudo pear upgrade-all
 | 
			
		||||
    $ sudo pear install --alldeps Mail
 | 
			
		||||
    $ sudo pear install --alldeps Mail_mime
 | 
			
		||||
    $ sudo pear install --alldeps Console_Getopt
 | 
			
		||||
 | 
			
		||||
If you already have some of the PEAR packages installed with your
 | 
			
		||||
distribution, the `Mail` package is often not up-to-date, 
 | 
			
		||||
@@ -59,6 +66,8 @@ Here is the step-by-step installation procedure:
 | 
			
		||||
* The InDefero installation folder is the folder containing this file INSTALL.mdtext.
 | 
			
		||||
* Make a copy of `src/IDF/conf/idf.php-dist` as `src/IDF/conf/idf.php`.
 | 
			
		||||
* Update the idf.php file to match your system.
 | 
			
		||||
* Make a copy of `src/IDF/conf/path.php-dist` as `src/IDF/conf/path.php`.
 | 
			
		||||
* Update the path.php file to match your installation paths. It should work out of the box if you followed the recommended file layout.
 | 
			
		||||
* Open a terminal/shell and go into the `src` folder in the InDefero installation folder.
 | 
			
		||||
 | 
			
		||||
**Command:**
 | 
			
		||||
@@ -73,8 +82,7 @@ Here is the step-by-step installation procedure:
 | 
			
		||||
**Bootstrap script:**
 | 
			
		||||
 | 
			
		||||
    <?php
 | 
			
		||||
    set_include_path(get_include_path().PATH_SEPARATOR.'/home/www/indefero/src');
 | 
			
		||||
    set_include_path(get_include_path().PATH_SEPARATOR.'/home/www/pluf/src');
 | 
			
		||||
    require '/home/www/indefero/src/IDF/conf/path.php';
 | 
			
		||||
    require 'Pluf.php';
 | 
			
		||||
    Pluf::start('/home/www/indefero/src/IDF/conf/idf.php');
 | 
			
		||||
    Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
 | 
			
		||||
@@ -97,7 +105,7 @@ Here is the step-by-step installation procedure:
 | 
			
		||||
* Open the `www/index.php` file and ensure that the path to Pluf and
 | 
			
		||||
  Indefero are correctly set for your configuration.
 | 
			
		||||
* Now you can login with this user into the interface.
 | 
			
		||||
* Click on the Administer link on top and create your first project.
 | 
			
		||||
* Click on the Forge Management link on top and create your first project.
 | 
			
		||||
 | 
			
		||||
## Upgrade InDefero
 | 
			
		||||
 | 
			
		||||
@@ -112,11 +120,11 @@ To upgrade:
 | 
			
		||||
 | 
			
		||||
**Upgrade commands:**
 | 
			
		||||
 | 
			
		||||
    $ cd /home/www/indefero/src
 | 
			
		||||
    $ php /home/www/pluf/src/migrate.php --conf=IDF/conf/idf.php -a -d -u
 | 
			
		||||
    $ php /home/www/pluf/src/migrate.php --conf=IDF/conf/idf.php -a -d 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Repository Synchronization
 | 
			
		||||
 | 
			
		||||
The documentation is available in the `doc` folder.
 | 
			
		||||
@@ -124,6 +132,7 @@ The documentation is available in the `doc` folder.
 | 
			
		||||
* Subversion: `doc/syncsvn.mdtext`.
 | 
			
		||||
* Mercurial: `doc/syncmercurial.mdtext`.
 | 
			
		||||
* Git: `doc/syncgit.mdtext`.
 | 
			
		||||
* Monotone: `doc/syncmonotone.mdtext`
 | 
			
		||||
 | 
			
		||||
## For the Apache Webserver Users
 | 
			
		||||
 | 
			
		||||
@@ -162,6 +171,11 @@ this in your configuration file:
 | 
			
		||||
 | 
			
		||||
    $cfg['idf_mimetypes_db'] = '/home/mime.types';
 | 
			
		||||
 | 
			
		||||
## FreeBSD Installation
 | 
			
		||||
 | 
			
		||||
You need to install `/usr/ports/lang/php5-extensions` which contains
 | 
			
		||||
the Standard PHP Library (SPL).
 | 
			
		||||
 | 
			
		||||
## Using a SMTP server with authentication
 | 
			
		||||
 | 
			
		||||
If your SMTP server requires authentication, for example,
 | 
			
		||||
@@ -177,3 +191,43 @@ If your SMTP server requires authentication, for example,
 | 
			
		||||
 | 
			
		||||
Check with your provider to get the right settings.
 | 
			
		||||
 | 
			
		||||
## Git Daemon on Ubuntu Karmic
 | 
			
		||||
 | 
			
		||||
If you have problems getting it to run, you can follow this procedure
 | 
			
		||||
proposed by Mathias in ticket 369.
 | 
			
		||||
 | 
			
		||||
1. Install git-daemon-run in addition to git-core
 | 
			
		||||
2. Edit /etc/sv/git-daemon/run to look as follows:
 | 
			
		||||
 | 
			
		||||
    #!/bin/sh
 | 
			
		||||
    exec 2>&1
 | 
			
		||||
    echo 'git-daemon starting.'
 | 
			
		||||
    exec chpst -ugit:git \
 | 
			
		||||
      /usr/lib/git-core/git-daemon \
 | 
			
		||||
      --reuseaddr \
 | 
			
		||||
      --syslog \
 | 
			
		||||
      --verbose \
 | 
			
		||||
      --base-path=/home/git/repositories \
 | 
			
		||||
      /home/git/repositories
 | 
			
		||||
 | 
			
		||||
3. Restart git-daemon-run
 | 
			
		||||
 | 
			
		||||
    sv restart git-daemon
 | 
			
		||||
 | 
			
		||||
## If Subversion is not working
 | 
			
		||||
 | 
			
		||||
If you access a Subversion server with a self-signed certificate, you
 | 
			
		||||
may have problems as your certificate is not trusted, check the
 | 
			
		||||
[procedure provided here][svnfix] to solve the problem.
 | 
			
		||||
 | 
			
		||||
[svnfix]: http://projects.ceondo.com/p/indefero/issues/319/#ic1358
 | 
			
		||||
 | 
			
		||||
## If the registration links are not working
 | 
			
		||||
 | 
			
		||||
If You have standard instalaction of PHP ie in Debian, php.ini sets
 | 
			
		||||
mbstring.func_overload to value "2" for overloading str*
 | 
			
		||||
functions. You need to prevent the overload as it does not make sense
 | 
			
		||||
anyway (magic in the background is bad!). 
 | 
			
		||||
See the [corresponding ticket][reglink].
 | 
			
		||||
 | 
			
		||||
[reglink]: http://projects.ceondo.com/p/indefero/issues/481/
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										141
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2010 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
#   Installation of external tools : transifex-client
 | 
			
		||||
#   sudo apt-get install python-setuptools
 | 
			
		||||
#   sudo easy_install -U transifex-client
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.PHONY: help
 | 
			
		||||
help:
 | 
			
		||||
	@printf "Rules for generating distributable files :\n"
 | 
			
		||||
	@for b in `git branch | sed "s/^. //g"`; do \
 | 
			
		||||
		printf "\t"$$b"-zipfile - Generate a zip archive of the "$$b" branch.\n"; \
 | 
			
		||||
	done
 | 
			
		||||
	@printf "\nRules for internationalization :\n";
 | 
			
		||||
	@printf "\tpot-update - Update the POT file from HTML templates and PHP sources, then merge it with PO file.\n"
 | 
			
		||||
	@printf "\tpot-push   - Send the POT file to the transifex server.\n"
 | 
			
		||||
	@printf "\tpo-update  - Merge the POT file into the PO file. The POT is not regenerated.\n"
 | 
			
		||||
	@printf "\tpo-push    - Send the all PO files to the transifex server.\n"
 | 
			
		||||
	@printf "\tpo-pull    - Get all PO files from the transifex server.\n"
 | 
			
		||||
	@printf "\tpo-stats   - Show translation statistics of all PO files.\n"
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
#   Internationalization rule, POT & PO file manipulation
 | 
			
		||||
#
 | 
			
		||||
.PHONY: pluf_path
 | 
			
		||||
pluf_path:
 | 
			
		||||
ifeq (src/IDF/conf/path.php, $(wildcard src/IDF/conf/path.php))
 | 
			
		||||
PLUF_PATH=$(shell php -r "require_once('src/IDF/conf/path.php'); echo PLUF_PATH;")
 | 
			
		||||
else
 | 
			
		||||
	@printf "File 'src/IDF/conf/path.php' don't exist. Please configure it !\n"
 | 
			
		||||
	@exit 1
 | 
			
		||||
endif
 | 
			
		||||
 
 | 
			
		||||
.PHONY: pot-update po-update
 | 
			
		||||
pot-update: pluf_path
 | 
			
		||||
	# Backup pot file
 | 
			
		||||
	@if [ -e src/IDF/locale/idf.pot ]; then                     \
 | 
			
		||||
	mv -f src/IDF/locale/idf.pot src/IDF/locale/idf.pot.bak;    \
 | 
			
		||||
	fi
 | 
			
		||||
	touch src/IDF/locale/idf.pot;
 | 
			
		||||
	# Extract string
 | 
			
		||||
	@cd src; php $(PLUF_PATH)/extracttemplates.php IDF/conf/idf.php IDF/gettexttemplates
 | 
			
		||||
	@cd src; for phpfile in `find . -iname "*.php"`; do \
 | 
			
		||||
		printf "Parsing file : "$$phpfile"\n"; \
 | 
			
		||||
		xgettext -o idf.pot -p ./IDF/locale/ --from-code=UTF-8 -j \
 | 
			
		||||
			--keyword --keyword=__ --keyword=_n:1,2 -L PHP $$phpfile ; \
 | 
			
		||||
		done
 | 
			
		||||
	#	Remove tmp folder
 | 
			
		||||
	rm -Rf src/IDF/gettexttemplates
 | 
			
		||||
	# Update PO
 | 
			
		||||
	@make po-update
 | 
			
		||||
 | 
			
		||||
po-update: pluf_path
 | 
			
		||||
	@for pofile in `ls src/IDF/locale/*/idf.po`; do \
 | 
			
		||||
		printf "Updating file : "$$pofile"\n"; \
 | 
			
		||||
		msgmerge -v -U $$pofile src/IDF/locale/idf.pot; \
 | 
			
		||||
		printf "\n"; \
 | 
			
		||||
	done
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
#   Transifex
 | 
			
		||||
#
 | 
			
		||||
.PHONY: check-tx-config
 | 
			
		||||
check-tx-config:
 | 
			
		||||
	@if [ ! -e .tx/config ]; then                                       \
 | 
			
		||||
	mkdir -p .tx;                                                       \
 | 
			
		||||
	touch .tx/config;                                                   \
 | 
			
		||||
	printf "[main]\n" >> .tx/config;                                        \
 | 
			
		||||
	printf "host = http://www.transifex.net\n" >> .tx/config;               \
 | 
			
		||||
	printf "\n" >> .tx/config;                                              \
 | 
			
		||||
	printf "[indefero.idfpot]\n" >> .tx/config;                             \
 | 
			
		||||
	printf "file_filter = src/IDF/locale/<lang>/idf.po\n" >> .tx/config;    \
 | 
			
		||||
	printf "source_file = src/IDF/locale/idf.pot\n" >> .tx/config;          \
 | 
			
		||||
	printf "source_lang = en\n" >> .tx/config;                              \
 | 
			
		||||
	fi
 | 
			
		||||
	@if [ ! -e $(HOME)/.transifexrc ]; then					\
 | 
			
		||||
	touch $(HOME)/.transifexrc;												\
 | 
			
		||||
	printf "[http://www.transifex.net]\n" >> $(HOME)/.transifexrc;						\
 | 
			
		||||
	printf "username = \n" >> $(HOME)/.transifexrc;								\
 | 
			
		||||
	printf "token = \n" >> $(HOME)/.transifexrc;								\
 | 
			
		||||
	printf "password = \n" >> $(HOME)/.transifexrc;								\
 | 
			
		||||
	printf "hostname = http://www.transifex.net\n" >> $(HOME)/.transifexrc;					\
 | 
			
		||||
	printf "You must edit the file ~/.transifexrc to setup your transifex account (login & password) !\n";	\
 | 
			
		||||
	exit 1;																\
 | 
			
		||||
	fi
 | 
			
		||||
 | 
			
		||||
pot-push: check-tx-config
 | 
			
		||||
	@tx push -s
 | 
			
		||||
 | 
			
		||||
po-push: check-tx-config
 | 
			
		||||
	@tx push -t
 | 
			
		||||
 | 
			
		||||
po-pull: check-tx-config
 | 
			
		||||
	# Save PO
 | 
			
		||||
	@for pofile in `ls src/IDF/locale/*/idf.po`; do \
 | 
			
		||||
	    cp $$pofile $$pofile".save"; \
 | 
			
		||||
	done
 | 
			
		||||
	# Get new one
 | 
			
		||||
	@tx pull -a
 | 
			
		||||
	# Merge Transifex PO into local PO (so fuzzy entry is correctly saved)
 | 
			
		||||
	@for pofile in `ls src/IDF/locale/*/idf.po`; do \
 | 
			
		||||
	    msgmerge -U $$pofile".save" $$pofile; \
 | 
			
		||||
	    rm -f $$pofile; \
 | 
			
		||||
	    mv $$pofile".save" $$pofile; \
 | 
			
		||||
	done
 | 
			
		||||
 | 
			
		||||
po-stats:
 | 
			
		||||
	@msgfmt --statistics -v src/IDF/locale/idf.pot
 | 
			
		||||
	@for pofile in `ls src/IDF/locale/*/idf.po`; do \
 | 
			
		||||
	    msgfmt --statistics -v $$pofile; \
 | 
			
		||||
	done
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
#   Generic rule to build a zipfile of indefero for a specified branch
 | 
			
		||||
#   ex: make master_zipfile
 | 
			
		||||
#       make develop_zipfile
 | 
			
		||||
#
 | 
			
		||||
%-zipfile:
 | 
			
		||||
	@git archive --format=zip --prefix="indefero/" $(@:-zipfile=) \
 | 
			
		||||
		> indefero-$(@:-zipfile=)-`git log $(@:-zipfile=) -n 1 \
 | 
			
		||||
		--pretty=format:%h`.zip
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										104
									
								
								NEWS.mdtext
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								NEWS.mdtext
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
# InDefero 1.2 - xxx xxx xx xx:xx 2011 UTC
 | 
			
		||||
 | 
			
		||||
## New Features
 | 
			
		||||
 | 
			
		||||
## Bugfixes 
 | 
			
		||||
 | 
			
		||||
- monotone zip archive entries now all carry the revision date as mtime (issue 645)
 | 
			
		||||
- Timeline only displays filter options for items a user has actually access to (issue 655)
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
## Translations
 | 
			
		||||
 | 
			
		||||
# InDefero 1.1.1 - Mon Mar 28 15:52 2011 UTC
 | 
			
		||||
 | 
			
		||||
## Bugfixes
 | 
			
		||||
 | 
			
		||||
- Fix an incompatibility with Python 3.1 in gitserve.py (issue 554)
 | 
			
		||||
- Fix PHP error when the commit view shows a commit with changed binary files (issue 643)
 | 
			
		||||
- A migration problem prevented the preferences page being displayed properly (issues 644 and 653)
 | 
			
		||||
- Fix PHP error when trying to create Mercurial source archives (issue 648)
 | 
			
		||||
- Improve the French translation (issue 651)
 | 
			
		||||
- Registration page missed a link to password recovery that was mentioned in a form error (issue 652)
 | 
			
		||||
 | 
			
		||||
# InDefero 1.1 - Sun Mar 20 11:44 2011 UTC
 | 
			
		||||
 | 
			
		||||
## New Features
 | 
			
		||||
 | 
			
		||||
* _Version control_:
 | 
			
		||||
 - Support for the monotone Version Control system (see [[InstallationScmMonotone]])
 | 
			
		||||
 - Display detailed changeset information in the commit details (git, mtn) (issue 544)
 | 
			
		||||
 - Show branch in the commit details (git, mtn, hg) (issue 450)
 | 
			
		||||
 - Render branch and tag names in a popup and make them filterable (git, hg, mtn) (issue 601)
 | 
			
		||||
 | 
			
		||||
* _Issue tracking_:
 | 
			
		||||
 - Forge-wide and per-project watch lists of starred issues (issue 589)
 | 
			
		||||
 - Configure a default issue template for each project (issues 212 and 540)
 | 
			
		||||
 - Pick default issue labels from the configured project settings (issue 556)
 | 
			
		||||
 - Navigate to a preceding / following issue in the issue detail view
 | 
			
		||||
 - Many new text syntaxes to auto-link revisions (see [[AutomaticLinks]], issue 569)
 | 
			
		||||
 | 
			
		||||
* _Documentation wiki_:
 | 
			
		||||
 - Automatically create a table of contents on wiki pages (issue 350)
 | 
			
		||||
 - Allow the usage of text labels for Wiki text links (issue 456)
 | 
			
		||||
 | 
			
		||||
* _Other_:
 | 
			
		||||
 - Enhanced user profile page (issue 510)
 | 
			
		||||
 - Manage multiple (commit) emails in the account settings (issues 136 and 500)
 | 
			
		||||
 - Filter the timeline and its feed by item type (issue 543)
 | 
			
		||||
 - Add multiple email addresses for project notifications (issue 372)
 | 
			
		||||
 - Direct links to other projects via the new `Project List` dropdown
 | 
			
		||||
 - InDefero gained a favicon (issue 594)
 | 
			
		||||
 | 
			
		||||
## Bugfixes
 | 
			
		||||
 | 
			
		||||
- Fix `Need SSH_ORIGINAL_COMMAND in environment` error for git sync (issue 198)
 | 
			
		||||
- Added an option to disable lengthy project size calculation in the forge (issue 403)
 | 
			
		||||
- Fix a problem when deleting an orphaned git repository (issue 467)
 | 
			
		||||
- Ignore XML parsing problems when trying to retrieve commit messages for svn (issues 469 and 518)
 | 
			
		||||
- Sort the project list by the display name of the project (issue 477)
 | 
			
		||||
- Project creation form now has a short description field as well (issue 479)
 | 
			
		||||
- Add more file extensions supported by our in-tree prettify version (issues 490 and 567)
 | 
			
		||||
- Improve the parsing of hg's log output (issues 507 and 508)
 | 
			
		||||
- Do not clean `<ins>` and `<del>` HTML markup from user input (issue 509)
 | 
			
		||||
- Improve HTML validation by switching from `strict` to `transitional` DOCTYPE (issue 511)
 | 
			
		||||
- Properly handle git commits without a changelog title (issue 520)
 | 
			
		||||
- Improve BSD compatibility in shell scripts (issue 526)
 | 
			
		||||
- Properly render inner whitespaces in viewed issue attachments (issue 528)
 | 
			
		||||
- Support for uploading SSH keys without optional comment (issue 531)
 | 
			
		||||
- Recognize irc: and git: protocols in Markdown renderer (issue 546)
 | 
			
		||||
- New config option `git_core_quotepath` to handle non-ASCII git file names (issue 553)
 | 
			
		||||
- Ensured that active views are rendered in the menu as such (issue 555)
 | 
			
		||||
- Add CSS for nested and mixed ordered and unordered lists (issue 557)
 | 
			
		||||
- Directories are now sorted before files in source tree views (issue 573)
 | 
			
		||||
- File attachments now have explicit view and download links in issue views (issue 575)
 | 
			
		||||
- Display anonymous access URL when user has no SSH key registered (issue 588)
 | 
			
		||||
- Improve the rendering of tag lists in issue views (issue 591)
 | 
			
		||||
- Improved error page when a commit or branch was not found (issue 609)
 | 
			
		||||
- Disable browser autocomplete of password fields in the account settings (issue 616)
 | 
			
		||||
- Improvements in the automatic linker parser (issue 618)
 | 
			
		||||
- The `createIssue` API method did not check the API authentication (issue 619)
 | 
			
		||||
- Reduce the memory footprint and compatibility of the internal diff parser (issues 627 and 633)
 | 
			
		||||
- Print the git branches and tags in bold which contain the currently displayed revision
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
- Document how to contribute to Indefero in `CONTRIBUTE.mdtext` (issue 486)
 | 
			
		||||
- Note possible problems with mbstring.func_overload in `INSTALL.mdtext` (issue 481)
 | 
			
		||||
- Improve links to Markdown documentation (issue 489)
 | 
			
		||||
- Explain purpose of `idf_strong_key_check` in `idf.php-dist` (issue 516)
 | 
			
		||||
 | 
			
		||||
## Translations
 | 
			
		||||
 | 
			
		||||
- Spanish translation added
 | 
			
		||||
- Started with a Simplified Chinese translation (issue 521)
 | 
			
		||||
- Started with a Russian translation
 | 
			
		||||
- Updates and fixes to the French translation (issue 574)
 | 
			
		||||
- Updates and fixes to the German translation
 | 
			
		||||
- English source language has been improved (issues 441, 478, and 631)
 | 
			
		||||
 | 
			
		||||
# InDefero 1.0 - Tue Apr 20 07:00 2010 UTC
 | 
			
		||||
 | 
			
		||||
First stable release.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								contrib/zipstream-php-0.2.2/COPYING
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								contrib/zipstream-php-0.2.2/COPYING
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
Copyright (C) 2007-2009 Paul Duncan <pabs@pablotron.org>
 | 
			
		||||
 
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a
 | 
			
		||||
copy of this software and associated documentation files (the
 | 
			
		||||
"Software"), to deal in the Software without restriction, including
 | 
			
		||||
without limitation the rights to use, copy, modify, merge, publish,
 | 
			
		||||
distribute, sublicense, and/or sell copies of the Software, and to
 | 
			
		||||
permit persons to whom the Software is furnished to do so, subject to
 | 
			
		||||
the following conditions:
 | 
			
		||||
  
 | 
			
		||||
The above copyright notice and this permission notice shall be included
 | 
			
		||||
in all copies or substantial portions of the of the Software.
 | 
			
		||||
   
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 | 
			
		||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 | 
			
		||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 | 
			
		||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 | 
			
		||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 | 
			
		||||
DEALINGS IN THE SOFTWARE.  
 | 
			
		||||
							
								
								
									
										42
									
								
								contrib/zipstream-php-0.2.2/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								contrib/zipstream-php-0.2.2/README
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
ZipStream 0.2.2 README
 | 
			
		||||
======================
 | 
			
		||||
 | 
			
		||||
Please see the file COPYING for licensing and warranty information.  The
 | 
			
		||||
latest version of this software is available at the following URL:
 | 
			
		||||
 | 
			
		||||
    http://pablotron.org/software/zipstream-php/
 | 
			
		||||
 | 
			
		||||
Overview
 | 
			
		||||
========
 | 
			
		||||
A fast and simple streaming zip file downloader for PHP.  Here's a
 | 
			
		||||
simple example:
 | 
			
		||||
 | 
			
		||||
    # create a new zipstream object
 | 
			
		||||
    $zip = new ZipStream('example.zip');
 | 
			
		||||
 | 
			
		||||
    # create a file named 'hello.txt' 
 | 
			
		||||
    $zip->add_file('some_image.jpg', 'This is the contents of hello.txt');
 | 
			
		||||
 | 
			
		||||
    # add a file named 'image.jpg' from a local file 'path/to/image.jpg'
 | 
			
		||||
    $zip->add_file_from_path('some_image.jpg', 'path/to/image.jpg');
 | 
			
		||||
 | 
			
		||||
    # finish the zip stream
 | 
			
		||||
    $zip->finish();
 | 
			
		||||
 | 
			
		||||
You can also add comments, modify file timestamps, and customize (or
 | 
			
		||||
disable) the HTTP headers.  See the class file for details.  There are a
 | 
			
		||||
couple of additional examples in the initial release announcement at the
 | 
			
		||||
following URL:
 | 
			
		||||
 | 
			
		||||
    http://pablotron.org/?cid=1535
 | 
			
		||||
 | 
			
		||||
Requirements
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
  * PHP version 5.1.2 or newer (specifically, the hash_init and
 | 
			
		||||
    hash_file functions).
 | 
			
		||||
 | 
			
		||||
About the Author
 | 
			
		||||
================
 | 
			
		||||
Paul Duncan <pabs@pablotron.org>
 | 
			
		||||
http://pablotron.org/
 | 
			
		||||
							
								
								
									
										2
									
								
								contrib/zipstream-php-0.2.2/extras/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								contrib/zipstream-php-0.2.2/extras/README
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
Based on PKZIP appnotes, which are included here.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3071
									
								
								contrib/zipstream-php-0.2.2/extras/zip-appnote-6.3.1-20070411.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3071
									
								
								contrib/zipstream-php-0.2.2/extras/zip-appnote-6.3.1-20070411.txt
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										52
									
								
								contrib/zipstream-php-0.2.2/test/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								contrib/zipstream-php-0.2.2/test/index.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
# load zipstream class
 | 
			
		||||
require '../zipstream.php';
 | 
			
		||||
 | 
			
		||||
# get path to current file
 | 
			
		||||
$pwd = dirname(__FILE__);
 | 
			
		||||
 | 
			
		||||
# add some random files
 | 
			
		||||
$files = array(
 | 
			
		||||
  '../extras/zip-appnote-6.3.1-20070411.txt',
 | 
			
		||||
  '../zipstream.php',
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
# create new zip stream object
 | 
			
		||||
$zip = new ZipStream('test.zip', array(
 | 
			
		||||
  'comment' => 'this is a zip file comment.  hello?'
 | 
			
		||||
));
 | 
			
		||||
 | 
			
		||||
# common file options
 | 
			
		||||
$file_opt = array(
 | 
			
		||||
  # file creation time (2 hours ago)
 | 
			
		||||
  'time'    => time() - 2 * 3600,
 | 
			
		||||
 | 
			
		||||
  # file comment
 | 
			
		||||
  'comment' => 'this is a file comment. hi!',
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
# add files under folder 'asdf'
 | 
			
		||||
foreach ($files as $file) {
 | 
			
		||||
  # build absolute path and get file data
 | 
			
		||||
  $path = ($file[0] == '/') ? $file : "$pwd/$file";
 | 
			
		||||
  $data = file_get_contents($path);
 | 
			
		||||
 | 
			
		||||
  # add file to archive
 | 
			
		||||
  $zip->add_file('asdf/' . basename($file), $data, $file_opt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# add same files again wihtout a folder
 | 
			
		||||
foreach ($files as $file) {
 | 
			
		||||
  # build absolute path and get file data
 | 
			
		||||
  $path = ($file[0] == '/') ? $file : "$pwd/$file";
 | 
			
		||||
  $data = file_get_contents($path);
 | 
			
		||||
 | 
			
		||||
  # add file to archive
 | 
			
		||||
  $zip->add_file(basename($file), $data, $file_opt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# finish archive
 | 
			
		||||
$zip->finish();
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										580
									
								
								contrib/zipstream-php-0.2.2/zipstream.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										580
									
								
								contrib/zipstream-php-0.2.2/zipstream.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,580 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
##########################################################################
 | 
			
		||||
# ZipStream - Streamed, dynamically generated zip archives.              #
 | 
			
		||||
# by Paul Duncan <pabs@pablotron.org>                                    #
 | 
			
		||||
#                                                                        #
 | 
			
		||||
# Copyright (C) 2007-2009 Paul Duncan <pabs@pablotron.org>               #
 | 
			
		||||
#                                                                        #
 | 
			
		||||
# Permission is hereby granted, free of charge, to any person obtaining  #
 | 
			
		||||
# a copy of this software and associated documentation files (the        #
 | 
			
		||||
# "Software"), to deal in the Software without restriction, including    #
 | 
			
		||||
# without limitation the rights to use, copy, modify, merge, publish,    #
 | 
			
		||||
# distribute, sublicense, and/or sell copies of the Software, and to     #
 | 
			
		||||
# permit persons to whom the Software is furnished to do so, subject to  #
 | 
			
		||||
# the following conditions:                                              #
 | 
			
		||||
#                                                                        #
 | 
			
		||||
# The above copyright notice and this permission notice shall be         #
 | 
			
		||||
# included in all copies or substantial portions of the of the Software. #
 | 
			
		||||
#                                                                        #
 | 
			
		||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        #
 | 
			
		||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     #
 | 
			
		||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #
 | 
			
		||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR      #
 | 
			
		||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,  #
 | 
			
		||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  #
 | 
			
		||||
# OTHER DEALINGS IN THE SOFTWARE.                                        #
 | 
			
		||||
##########################################################################
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# ZipStream - Streamed, dynamically generated zip archives.
 | 
			
		||||
# by Paul Duncan <pabs@pablotron.org>
 | 
			
		||||
#
 | 
			
		||||
# Requirements:
 | 
			
		||||
#
 | 
			
		||||
# * PHP version 5.1.2 or newer.
 | 
			
		||||
#
 | 
			
		||||
# Usage:
 | 
			
		||||
#
 | 
			
		||||
# Streaming zip archives is a simple, three-step process:
 | 
			
		||||
#
 | 
			
		||||
# 1.  Create the zip stream:
 | 
			
		||||
#
 | 
			
		||||
#     $zip = new ZipStream('example.zip');
 | 
			
		||||
#
 | 
			
		||||
# 2.  Add one or more files to the archive:
 | 
			
		||||
#
 | 
			
		||||
#     # add first file
 | 
			
		||||
#     $data = file_get_contents('some_file.gif');
 | 
			
		||||
#     $zip->add_file('some_file.gif', $data);
 | 
			
		||||
#
 | 
			
		||||
#     # add second file
 | 
			
		||||
#     $data = file_get_contents('some_file.gif');
 | 
			
		||||
#     $zip->add_file('another_file.png', $data);
 | 
			
		||||
#
 | 
			
		||||
# 3.  Finish the zip stream:
 | 
			
		||||
#
 | 
			
		||||
#     $zip->finish();
 | 
			
		||||
#
 | 
			
		||||
# You can also add an archive comment, add comments to individual files,
 | 
			
		||||
# and adjust the timestamp of files.  See the API documentation for each
 | 
			
		||||
# method below for additional information.
 | 
			
		||||
#
 | 
			
		||||
# Example:
 | 
			
		||||
#
 | 
			
		||||
#   # create a new zip stream object
 | 
			
		||||
#   $zip = new ZipStream('some_files.zip');
 | 
			
		||||
#
 | 
			
		||||
#   # list of local files
 | 
			
		||||
#   $files = array('foo.txt', 'bar.jpg');
 | 
			
		||||
#
 | 
			
		||||
#   # read and add each file to the archive
 | 
			
		||||
#   foreach ($files as $path)
 | 
			
		||||
#     $zip->add_file($path, file_get_contents($path));
 | 
			
		||||
# 
 | 
			
		||||
#   # write archive footer to stream
 | 
			
		||||
#   $zip->finish();
 | 
			
		||||
#
 | 
			
		||||
class ZipStream {
 | 
			
		||||
  const VERSION = '0.2.2';
 | 
			
		||||
 | 
			
		||||
  var $opt = array(),
 | 
			
		||||
      $files = array(),
 | 
			
		||||
      $cdr_ofs = 0,
 | 
			
		||||
      $need_headers = false,
 | 
			
		||||
      $ofs = 0; 
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Create a new ZipStream object.
 | 
			
		||||
  #
 | 
			
		||||
  # Parameters:
 | 
			
		||||
  #
 | 
			
		||||
  #   $name - Name of output file (optional).
 | 
			
		||||
  #   $opt  - Hash of archive options (optional, see "Archive Options"
 | 
			
		||||
  #           below).
 | 
			
		||||
  #
 | 
			
		||||
  # Archive Options:
 | 
			
		||||
  #
 | 
			
		||||
  #   comment             - Comment for this archive.
 | 
			
		||||
  #   content_type        - HTTP Content-Type.  Defaults to 'application/x-zip'.
 | 
			
		||||
  #   content_disposition - HTTP Content-Disposition.  Defaults to 
 | 
			
		||||
  #                         'attachment; filename=\"FILENAME\"', where
 | 
			
		||||
  #                         FILENAME is the specified filename.
 | 
			
		||||
  #   large_file_size     - Size, in bytes, of the largest file to try
 | 
			
		||||
  #                         and load into memory (used by
 | 
			
		||||
  #                         add_file_from_path()).  Large files may also
 | 
			
		||||
  #                         be compressed differently; see the
 | 
			
		||||
  #                         'large_file_method' option.
 | 
			
		||||
  #   large_file_method   - How to handle large files.  Legal values are
 | 
			
		||||
  #                         'store' (the default), or 'deflate'.  Store
 | 
			
		||||
  #                         sends the file raw and is significantly
 | 
			
		||||
  #                         faster, while 'deflate' compresses the file
 | 
			
		||||
  #                         and is much, much slower.  Note that deflate
 | 
			
		||||
  #                         must compress the file twice and extremely
 | 
			
		||||
  #                         slow.
 | 
			
		||||
  #   send_http_headers   - Boolean indicating whether or not to send
 | 
			
		||||
  #                         the HTTP headers for this file.
 | 
			
		||||
  #
 | 
			
		||||
  # Note that content_type and content_disposition do nothing if you are
 | 
			
		||||
  # not sending HTTP headers.
 | 
			
		||||
  #
 | 
			
		||||
  # Large File Support:
 | 
			
		||||
  #
 | 
			
		||||
  # By default, the method add_file_from_path() will send send files
 | 
			
		||||
  # larger than 20 megabytes along raw rather than attempting to
 | 
			
		||||
  # compress them.  You can change both the maximum size and the
 | 
			
		||||
  # compression behavior using the large_file_* options above, with the
 | 
			
		||||
  # following caveats:
 | 
			
		||||
  #
 | 
			
		||||
  # * For "small" files (e.g. files smaller than large_file_size), the
 | 
			
		||||
  #   memory use can be up to twice that of the actual file.  In other
 | 
			
		||||
  #   words, adding a 10 megabyte file to the archive could potentially
 | 
			
		||||
  #   occupty 20 megabytes of memory.
 | 
			
		||||
  #
 | 
			
		||||
  # * Enabling compression on large files (e.g. files larger than
 | 
			
		||||
  #   large_file_size) is extremely slow, because ZipStream has to pass
 | 
			
		||||
  #   over the large file once to calculate header information, and then
 | 
			
		||||
  #   again to compress and send the actual data.
 | 
			
		||||
  #
 | 
			
		||||
  # Examples:
 | 
			
		||||
  #
 | 
			
		||||
  #   # create a new zip file named 'foo.zip'
 | 
			
		||||
  #   $zip = new ZipStream('foo.zip');
 | 
			
		||||
  #
 | 
			
		||||
  #   # create a new zip file named 'bar.zip' with a comment
 | 
			
		||||
  #   $zip = new ZipStream('bar.zip', array(
 | 
			
		||||
  #     'comment' => 'this is a comment for the zip file.',
 | 
			
		||||
  #   ));
 | 
			
		||||
  #
 | 
			
		||||
  # Notes:
 | 
			
		||||
  #
 | 
			
		||||
  # If you do not set a filename, then this library _DOES NOT_ send HTTP
 | 
			
		||||
  # headers by default.  This behavior is to allow software to send its
 | 
			
		||||
  # own headers (including the filename), and still use this library.
 | 
			
		||||
  #
 | 
			
		||||
  function __construct($name = null, $opt = array()) {
 | 
			
		||||
    # save options
 | 
			
		||||
    $this->opt = $opt;
 | 
			
		||||
 | 
			
		||||
    # set large file defaults: size = 20 megabytes, method = store
 | 
			
		||||
    if (!isset($this->opt['large_file_size']))
 | 
			
		||||
      $this->opt['large_file_size'] = 20 * 1024 * 1024;
 | 
			
		||||
    if (!isset($this->opt['large_file_method']))
 | 
			
		||||
      $this->opt['large_file_method'] = 'store';
 | 
			
		||||
 | 
			
		||||
    $this->output_name = $name;
 | 
			
		||||
    if ($name || (isset($opt['send_http_headers']) 
 | 
			
		||||
        && $opt['send_http_headers']))
 | 
			
		||||
      $this->need_headers = true; 
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # add_file - add a file to the archive
 | 
			
		||||
  #
 | 
			
		||||
  # Parameters:
 | 
			
		||||
  #   
 | 
			
		||||
  #  $name - path of file in archive (including directory).
 | 
			
		||||
  #  $data - contents of file
 | 
			
		||||
  #  $opt  - Hash of options for file (optional, see "File Options"
 | 
			
		||||
  #          below).  
 | 
			
		||||
  #
 | 
			
		||||
  # File Options: 
 | 
			
		||||
  #  time     - Last-modified timestamp (seconds since the epoch) of
 | 
			
		||||
  #             this file.  Defaults to the current time.
 | 
			
		||||
  #  comment  - Comment related to this file.
 | 
			
		||||
  #
 | 
			
		||||
  # Examples:
 | 
			
		||||
  #
 | 
			
		||||
  #   # add a file named 'foo.txt'
 | 
			
		||||
  #   $data = file_get_contents('foo.txt');
 | 
			
		||||
  #   $zip->add_file('foo.txt', $data);
 | 
			
		||||
  # 
 | 
			
		||||
  #   # add a file named 'bar.jpg' with a comment and a last-modified
 | 
			
		||||
  #   # time of two hours ago
 | 
			
		||||
  #   $data = file_get_contents('bar.jpg');
 | 
			
		||||
  #   $zip->add_file('bar.jpg', $data, array(
 | 
			
		||||
  #     'time'    => time() - 2 * 3600,
 | 
			
		||||
  #     'comment' => 'this is a comment about bar.jpg',
 | 
			
		||||
  #   ));
 | 
			
		||||
  # 
 | 
			
		||||
  function add_file($name, $data, $opt = array()) {
 | 
			
		||||
    # compress data
 | 
			
		||||
    $zdata = gzdeflate($data);
 | 
			
		||||
 | 
			
		||||
    # calculate header attributes
 | 
			
		||||
    $crc  = crc32($data);
 | 
			
		||||
    $zlen = strlen($zdata);
 | 
			
		||||
    $len  = strlen($data);
 | 
			
		||||
    $meth = 0x08;
 | 
			
		||||
 | 
			
		||||
    # send file header
 | 
			
		||||
    $this->add_file_header($name, $opt, $meth, $crc, $zlen, $len);
 | 
			
		||||
 | 
			
		||||
    # print data
 | 
			
		||||
    $this->send($zdata);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # add_file_from_path - add a file at path to the archive.
 | 
			
		||||
  #
 | 
			
		||||
  # Note that large files may be compresed differently than smaller
 | 
			
		||||
  # files; see the "Large File Support" section above for more
 | 
			
		||||
  # information.
 | 
			
		||||
  #
 | 
			
		||||
  # Parameters:
 | 
			
		||||
  #   
 | 
			
		||||
  #  $name - name of file in archive (including directory path).
 | 
			
		||||
  #  $path - path to file on disk (note: paths should be encoded using
 | 
			
		||||
  #          UNIX-style forward slashes -- e.g '/path/to/some/file').
 | 
			
		||||
  #  $opt  - Hash of options for file (optional, see "File Options"
 | 
			
		||||
  #          below).  
 | 
			
		||||
  #
 | 
			
		||||
  # File Options: 
 | 
			
		||||
  #  time     - Last-modified timestamp (seconds since the epoch) of
 | 
			
		||||
  #             this file.  Defaults to the current time.
 | 
			
		||||
  #  comment  - Comment related to this file.
 | 
			
		||||
  #
 | 
			
		||||
  # Examples:
 | 
			
		||||
  #
 | 
			
		||||
  #   # add a file named 'foo.txt' from the local file '/tmp/foo.txt'
 | 
			
		||||
  #   $zip->add_file_from_path('foo.txt', '/tmp/foo.txt');
 | 
			
		||||
  # 
 | 
			
		||||
  #   # add a file named 'bigfile.rar' from the local file
 | 
			
		||||
  #   # '/usr/share/bigfile.rar' with a comment and a last-modified
 | 
			
		||||
  #   # time of two hours ago
 | 
			
		||||
  #   $path = '/usr/share/bigfile.rar';
 | 
			
		||||
  #   $zip->add_file_from_path('bigfile.rar', $path, array(
 | 
			
		||||
  #     'time'    => time() - 2 * 3600,
 | 
			
		||||
  #     'comment' => 'this is a comment about bar.jpg',
 | 
			
		||||
  #   ));
 | 
			
		||||
  # 
 | 
			
		||||
  function add_file_from_path($name, $path, $opt = array()) {
 | 
			
		||||
    if ($this->is_large_file($path)) {
 | 
			
		||||
      # file is too large to be read into memory; add progressively
 | 
			
		||||
      $this->add_large_file($name, $path, $opt);
 | 
			
		||||
    } else {
 | 
			
		||||
      # file is small enough to read into memory; read file contents and
 | 
			
		||||
      # handle with add_file()
 | 
			
		||||
      $data = file_get_contents($path);
 | 
			
		||||
      $this->add_file($name, $data, $opt);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # finish - Write zip footer to stream.
 | 
			
		||||
  #
 | 
			
		||||
  # Example:
 | 
			
		||||
  #
 | 
			
		||||
  #   # add a list of files to the archive
 | 
			
		||||
  #   $files = array('foo.txt', 'bar.jpg');
 | 
			
		||||
  #   foreach ($files as $path)
 | 
			
		||||
  #     $zip->add_file($path, file_get_contents($path));
 | 
			
		||||
  # 
 | 
			
		||||
  #   # write footer to stream
 | 
			
		||||
  #   $zip->finish();
 | 
			
		||||
  # 
 | 
			
		||||
  function finish() {
 | 
			
		||||
    # add trailing cdr record
 | 
			
		||||
    $this->add_cdr($this->opt);
 | 
			
		||||
    $this->clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ###################
 | 
			
		||||
  # PRIVATE METHODS #
 | 
			
		||||
  ###################
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Create and send zip header for this file.
 | 
			
		||||
  #
 | 
			
		||||
  private function add_file_header($name, $opt, $meth, $crc, $zlen, $len) {
 | 
			
		||||
    # strip leading slashes from file name
 | 
			
		||||
    # (fixes bug in windows archive viewer)
 | 
			
		||||
    $name = preg_replace('/^\\/+/', '', $name);
 | 
			
		||||
 | 
			
		||||
    # calculate name length
 | 
			
		||||
    $nlen = strlen($name);
 | 
			
		||||
 | 
			
		||||
    # create dos timestamp
 | 
			
		||||
    $opt['time'] = isset($opt['time']) ? $opt['time'] : time();
 | 
			
		||||
    $dts = $this->dostime($opt['time']);
 | 
			
		||||
 | 
			
		||||
    # build file header
 | 
			
		||||
    $fields = array(            # (from V.A of APPNOTE.TXT)
 | 
			
		||||
      array('V', 0x04034b50),     # local file header signature
 | 
			
		||||
      array('v', (6 << 8) + 3),   # version needed to extract
 | 
			
		||||
      array('v', 0x00),           # general purpose bit flag
 | 
			
		||||
      array('v', $meth),          # compresion method (deflate or store)
 | 
			
		||||
      array('V', $dts),           # dos timestamp
 | 
			
		||||
      array('V', $crc),           # crc32 of data
 | 
			
		||||
      array('V', $zlen),          # compressed data length
 | 
			
		||||
      array('V', $len),           # uncompressed data length
 | 
			
		||||
      array('v', $nlen),          # filename length
 | 
			
		||||
      array('v', 0),              # extra data len
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    # pack fields and calculate "total" length
 | 
			
		||||
    $ret = $this->pack_fields($fields);
 | 
			
		||||
    $cdr_len = strlen($ret) + $nlen + $zlen;
 | 
			
		||||
 | 
			
		||||
    # print header and filename
 | 
			
		||||
    $this->send($ret . $name);
 | 
			
		||||
 | 
			
		||||
    # add to central directory record and increment offset
 | 
			
		||||
    $this->add_to_cdr($name, $opt, $meth, $crc, $zlen, $len, $cdr_len);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Add a large file from the given path.
 | 
			
		||||
  #
 | 
			
		||||
  private function add_large_file($name, $path, $opt = array()) {
 | 
			
		||||
    $st = stat($path);
 | 
			
		||||
    $block_size = 1048576; # process in 1 megabyte chunks
 | 
			
		||||
    $algo = 'crc32b';
 | 
			
		||||
 | 
			
		||||
    # calculate header attributes
 | 
			
		||||
    $zlen = $len = $st['size'];
 | 
			
		||||
 | 
			
		||||
    $meth_str = $this->opt['large_file_method'];
 | 
			
		||||
    if ($meth_str == 'store') {
 | 
			
		||||
      # store method
 | 
			
		||||
      $meth = 0x00;
 | 
			
		||||
      $crc  = unpack('V', hash_file($algo, $path, true));
 | 
			
		||||
      $crc = $crc[1];
 | 
			
		||||
    } elseif ($meth_str == 'deflate') {
 | 
			
		||||
      # deflate method
 | 
			
		||||
      $meth = 0x08;
 | 
			
		||||
 | 
			
		||||
      # open file, calculate crc and compressed file length
 | 
			
		||||
      $fh = fopen($path, 'rb');
 | 
			
		||||
      $hash_ctx = hash_init($algo);
 | 
			
		||||
      $zlen = 0;
 | 
			
		||||
 | 
			
		||||
      # read each block, update crc and zlen
 | 
			
		||||
      while ($data = fgets($fh, $block_size)) {
 | 
			
		||||
        hash_update($hash_ctx, $data);
 | 
			
		||||
        $data = gzdeflate($data);
 | 
			
		||||
        $zlen += strlen($data);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      # close file and finalize crc
 | 
			
		||||
      fclose($fh);
 | 
			
		||||
      $crc = unpack('V', hash_final($hash_ctx, true));
 | 
			
		||||
      $crc = $crc[1];
 | 
			
		||||
    } else {
 | 
			
		||||
      die("unknown large_file_method: $meth_str");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # send file header
 | 
			
		||||
    $this->add_file_header($name, $opt, $meth, $crc, $zlen, $len);
 | 
			
		||||
 | 
			
		||||
    # open input file
 | 
			
		||||
    $fh = fopen($path, 'rb');
 | 
			
		||||
 | 
			
		||||
    # send file blocks
 | 
			
		||||
    while ($data = fgets($fh, $block_size)) {
 | 
			
		||||
      if ($meth_str == 'deflate') 
 | 
			
		||||
        $data = gzdeflate($data);
 | 
			
		||||
 | 
			
		||||
      # send data
 | 
			
		||||
      $this->send($data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # close input file
 | 
			
		||||
    fclose($fh);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Is this file larger than large_file_size?
 | 
			
		||||
  #
 | 
			
		||||
  function is_large_file($path) {
 | 
			
		||||
    $st = stat($path);
 | 
			
		||||
    return ($this->opt['large_file_size'] > 0) && 
 | 
			
		||||
           ($st['size'] > $this->opt['large_file_size']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Save file attributes for trailing CDR record.
 | 
			
		||||
  #
 | 
			
		||||
  private function add_to_cdr($name, $opt, $meth, $crc, $zlen, $len, $rec_len) {
 | 
			
		||||
    $this->files[] = array($name, $opt, $meth, $crc, $zlen, $len, $this->ofs);
 | 
			
		||||
    $this->ofs += $rec_len;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Send CDR record for specified file.
 | 
			
		||||
  #
 | 
			
		||||
  private function add_cdr_file($args) {
 | 
			
		||||
    list ($name, $opt, $meth, $crc, $zlen, $len, $ofs) = $args;
 | 
			
		||||
 | 
			
		||||
    # get attributes
 | 
			
		||||
    $comment = isset($opt['comment']) ? $opt['comment'] : '';
 | 
			
		||||
 | 
			
		||||
    # get dos timestamp
 | 
			
		||||
    $dts = $this->dostime($opt['time']);
 | 
			
		||||
 | 
			
		||||
    $fields = array(                  # (from V,F of APPNOTE.TXT)
 | 
			
		||||
      array('V', 0x02014b50),           # central file header signature
 | 
			
		||||
      array('v', (6 << 8) + 3),         # version made by
 | 
			
		||||
      array('v', (6 << 8) + 3),         # version needed to extract
 | 
			
		||||
      array('v', 0x00),                 # general purpose bit flag
 | 
			
		||||
      array('v', $meth),                # compresion method (deflate or store)
 | 
			
		||||
      array('V', $dts),                 # dos timestamp
 | 
			
		||||
      array('V', $crc),                 # crc32 of data
 | 
			
		||||
      array('V', $zlen),                # compressed data length
 | 
			
		||||
      array('V', $len),                 # uncompressed data length
 | 
			
		||||
      array('v', strlen($name)),        # filename length
 | 
			
		||||
      array('v', 0),                    # extra data len
 | 
			
		||||
      array('v', strlen($comment)),     # file comment length
 | 
			
		||||
      array('v', 0),                    # disk number start
 | 
			
		||||
      array('v', 0),                    # internal file attributes
 | 
			
		||||
      array('V', 32),                   # external file attributes
 | 
			
		||||
      array('V', $ofs),                 # relative offset of local header
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    # pack fields, then append name and comment
 | 
			
		||||
    $ret = $this->pack_fields($fields) . $name . $comment;
 | 
			
		||||
 | 
			
		||||
    $this->send($ret);
 | 
			
		||||
 | 
			
		||||
    # increment cdr offset
 | 
			
		||||
    $this->cdr_ofs += strlen($ret);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Send CDR EOF (Central Directory Record End-of-File) record.
 | 
			
		||||
  #
 | 
			
		||||
  private function add_cdr_eof($opt = null) {
 | 
			
		||||
    $num = count($this->files);
 | 
			
		||||
    $cdr_len = $this->cdr_ofs;
 | 
			
		||||
    $cdr_ofs = $this->ofs;
 | 
			
		||||
 | 
			
		||||
    # grab comment (if specified)
 | 
			
		||||
    $comment = '';
 | 
			
		||||
    if ($opt && isset($opt['comment']))
 | 
			
		||||
      $comment = $opt['comment'];
 | 
			
		||||
 | 
			
		||||
    $fields = array(                # (from V,F of APPNOTE.TXT)
 | 
			
		||||
      array('V', 0x06054b50),         # end of central file header signature
 | 
			
		||||
      array('v', 0x00),               # this disk number
 | 
			
		||||
      array('v', 0x00),               # number of disk with cdr
 | 
			
		||||
      array('v', $num),               # number of entries in the cdr on this disk
 | 
			
		||||
      array('v', $num),               # number of entries in the cdr
 | 
			
		||||
      array('V', $cdr_len),           # cdr size
 | 
			
		||||
      array('V', $cdr_ofs),           # cdr ofs
 | 
			
		||||
      array('v', strlen($comment)),   # zip file comment length
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $ret = $this->pack_fields($fields) . $comment;
 | 
			
		||||
    $this->send($ret);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Add CDR (Central Directory Record) footer.
 | 
			
		||||
  #
 | 
			
		||||
  private function add_cdr($opt = null) {
 | 
			
		||||
    foreach ($this->files as $file)
 | 
			
		||||
      $this->add_cdr_file($file);
 | 
			
		||||
    $this->add_cdr_eof($opt);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Clear all internal variables.  Note that the stream object is not
 | 
			
		||||
  # usable after this.
 | 
			
		||||
  #
 | 
			
		||||
  function clear() {
 | 
			
		||||
    $this->files = array();
 | 
			
		||||
    $this->ofs = 0;
 | 
			
		||||
    $this->cdr_ofs = 0;
 | 
			
		||||
    $this->opt = array();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ###########################
 | 
			
		||||
  # PRIVATE UTILITY METHODS #
 | 
			
		||||
  ###########################
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Send HTTP headers for this stream.
 | 
			
		||||
  #
 | 
			
		||||
  private function send_http_headers() {
 | 
			
		||||
    # grab options
 | 
			
		||||
    $opt = $this->opt;
 | 
			
		||||
    
 | 
			
		||||
    # grab content type from options
 | 
			
		||||
    $content_type = 'application/x-zip';
 | 
			
		||||
    if (isset($opt['content_type']))
 | 
			
		||||
      $content_type = $this->opt['content_type'];
 | 
			
		||||
 | 
			
		||||
    # grab content disposition 
 | 
			
		||||
    $disposition = 'attachment';
 | 
			
		||||
    if (isset($opt['content_disposition']))
 | 
			
		||||
      $disposition = $opt['content_disposition'];
 | 
			
		||||
 | 
			
		||||
    if ($this->output_name) 
 | 
			
		||||
      $disposition .= "; filename=\"{$this->output_name}\"";
 | 
			
		||||
 | 
			
		||||
    $headers = array(
 | 
			
		||||
      'Content-Type'              => $content_type,
 | 
			
		||||
      'Content-Disposition'       => $disposition,
 | 
			
		||||
      'Pragma'                    => 'public',
 | 
			
		||||
      'Cache-Control'             => 'public, must-revalidate',
 | 
			
		||||
      'Content-Transfer-Encoding' => 'binary',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    foreach ($headers as $key => $val)
 | 
			
		||||
      header("$key: $val");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Send string, sending HTTP headers if necessary.
 | 
			
		||||
  #
 | 
			
		||||
  private function send($str) {
 | 
			
		||||
    if ($this->need_headers)
 | 
			
		||||
      $this->send_http_headers();
 | 
			
		||||
    $this->need_headers = false;
 | 
			
		||||
 | 
			
		||||
    echo $str;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Convert a UNIX timestamp to a DOS timestamp.
 | 
			
		||||
  #
 | 
			
		||||
  function dostime($when = 0) {
 | 
			
		||||
    # get date array for timestamp
 | 
			
		||||
    $d = getdate($when);
 | 
			
		||||
 | 
			
		||||
    # set lower-bound on dates
 | 
			
		||||
    if ($d['year'] < 1980) {
 | 
			
		||||
      $d = array('year' => 1980, 'mon' => 1, 'mday' => 1, 
 | 
			
		||||
                 'hours' => 0, 'minutes' => 0, 'seconds' => 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # remove extra years from 1980
 | 
			
		||||
    $d['year'] -= 1980;
 | 
			
		||||
 | 
			
		||||
    # return date string
 | 
			
		||||
    return ($d['year'] << 25) | ($d['mon'] << 21) | ($d['mday'] << 16) |
 | 
			
		||||
           ($d['hours'] << 11) | ($d['minutes'] << 5) | ($d['seconds'] >> 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Create a format string and argument list for pack(), then call
 | 
			
		||||
  # pack() and return the result.
 | 
			
		||||
  #
 | 
			
		||||
  function pack_fields($fields) {
 | 
			
		||||
    list ($fmt, $args) = array('', array());
 | 
			
		||||
 | 
			
		||||
    # populate format string and argument list
 | 
			
		||||
    foreach ($fields as $field) {
 | 
			
		||||
      $fmt .= $field[0];
 | 
			
		||||
      $args[] = $field[1];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # prepend format string to argument list
 | 
			
		||||
    array_unshift($args, $fmt);
 | 
			
		||||
 | 
			
		||||
    # build output string from header and compressed data
 | 
			
		||||
    return call_user_func_array('pack', $args);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
@@ -70,6 +70,13 @@ folder. Here is a configuration example:
 | 
			
		||||
    $cfg['idf_plugin_syncgit_path_gitserve'] = '/home/www/indefero/scripts/gitserve.py'; # yes .py
 | 
			
		||||
    $cfg['idf_plugin_syncgit_path_authorized_keys'] = '/home/git/.ssh/authorized_keys';
 | 
			
		||||
    $cfg['idf_plugin_syncgit_sync_file'] = '/tmp/SYNC-GIT';
 | 
			
		||||
    # Remove the git repositories which do not have a corresponding project
 | 
			
		||||
    # This is run at cron time
 | 
			
		||||
    $cfg['idf_plugin_syncgit_remove_orphans'] = false;
 | 
			
		||||
    # git account home dir
 | 
			
		||||
    $cfg['idf_plugin_syncgit_git_home_dir'] = '/home/git'; 
 | 
			
		||||
    # where are going to be the git repositories
 | 
			
		||||
    $cfg['idf_plugin_syncgit_base_repositories'] = '/home/git/repositories'; 
 | 
			
		||||
 | 
			
		||||
When someone will change his SSH key or add a new one, the
 | 
			
		||||
`/tmp/SYNC-GIT` file will be created. The cron job
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,14 @@ You also need to provide the base definition of the hgrc file. For example:
 | 
			
		||||
               'extensions' => array(),
 | 
			
		||||
                                                  );
 | 
			
		||||
 | 
			
		||||
If you are note using Apache but Nginx, you may need to create the
 | 
			
		||||
passwords as plain text passwords (see ticket 391). You can configure
 | 
			
		||||
the password storage with the format you want. The default is `sha`
 | 
			
		||||
you can set it to `plain` for nginx.
 | 
			
		||||
 | 
			
		||||
    $cfg['idf_plugin_syncmercurial_passwd_mode'] = 'sha';
 | 
			
		||||
 | 
			
		||||
See the [`FILE_PASSWD_*` constants](http://euk1.php.net/package/File_Passwd/docs/latest/File_Passwd/_File_Passwd-1.1.7---Passwd.php.html) for more choices.
 | 
			
		||||
 | 
			
		||||
## Cron configuration
 | 
			
		||||
 | 
			
		||||
@@ -76,3 +84,13 @@ Each time this file is modified, a temporary file is created.
 | 
			
		||||
    */5 * * * * /bin/sh /home/indefero/src/scripts/SyncMercurial.sh
 | 
			
		||||
 | 
			
		||||
Edit this script and add correct values to `private_notify` and `reload_cmd`.
 | 
			
		||||
 | 
			
		||||
## Hook configuratin
 | 
			
		||||
 | 
			
		||||
To get notifications sent directly when pushing in your repositories,
 | 
			
		||||
you need to add the following in your `.hgrc` file. The script will be
 | 
			
		||||
called onec per push and will automatically send the notifications and
 | 
			
		||||
sync the timeline.
 | 
			
		||||
 | 
			
		||||
    [hooks]
 | 
			
		||||
    changegroup = /home/indefero/src/scripts/hgchangegroup.php
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										272
									
								
								doc/syncmonotone.mdtext
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								doc/syncmonotone.mdtext
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,272 @@
 | 
			
		||||
# Plugin SyncMonotone by Thomas Keller (me@thomaskeller.biz)
 | 
			
		||||
 | 
			
		||||
The SyncMonotone plugin allow the direct creation and synchronisation of
 | 
			
		||||
monotone repositories with the InDefero database. It has been built to
 | 
			
		||||
work together with monotone's "super server" usher, which is used to control
 | 
			
		||||
several repositories at once, acts as proxy and single entrance.
 | 
			
		||||
 | 
			
		||||
## Prerequisites
 | 
			
		||||
 | 
			
		||||
* a unixoid operating system
 | 
			
		||||
* monotone >= 0.99.1
 | 
			
		||||
* for a proxy setup with usher:
 | 
			
		||||
    * boost headers (for usher compilation)
 | 
			
		||||
    * a current version of usher
 | 
			
		||||
    * a daemonizer, like supervise
 | 
			
		||||
 | 
			
		||||
## Installation of monotone
 | 
			
		||||
 | 
			
		||||
If you install monotone from a distribution package, ensure you do not
 | 
			
		||||
install and / or activate the server component. We just need a plain
 | 
			
		||||
client installation which usually consists only of the `mtn` binary and
 | 
			
		||||
a few docs.
 | 
			
		||||
 | 
			
		||||
If you install monotone from source (<http://monotone.ca/downloads.php>),
 | 
			
		||||
please follow the `INSTALL` document which comes with the software.
 | 
			
		||||
It contains detailed instructions, including all needed dependencies.
 | 
			
		||||
 | 
			
		||||
## Choose your indefero setup
 | 
			
		||||
 | 
			
		||||
The monotone plugin can be used in several different ways:
 | 
			
		||||
 | 
			
		||||
1. One database for everything. This is the easiest setup and of possible
 | 
			
		||||
   use in case you do not want indefero to manage the access to your project.
 | 
			
		||||
   Your `idf.php` should look like this:
 | 
			
		||||
 | 
			
		||||
            $ cat idf.php
 | 
			
		||||
            ...
 | 
			
		||||
            $cfg['mtn_path'] = 'mtn';
 | 
			
		||||
            $cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles');
 | 
			
		||||
            $cfg['mtn_repositories'] = '/home/monotone/all_projects.mtn';
 | 
			
		||||
            $cfg['mtn_remote_url'] = 'ssh://monotone@my.server.com:~all_projects.mtn';
 | 
			
		||||
            $cfg['mtn_db_access'] = 'local';
 | 
			
		||||
            ...
 | 
			
		||||
 | 
			
		||||
   Pro:
 | 
			
		||||
      * easy to setup and to manage
 | 
			
		||||
 | 
			
		||||
   Con:
 | 
			
		||||
      * you need to give committers SSH access to your machine
 | 
			
		||||
      * database lock problem: the database from which
 | 
			
		||||
        indefero reads its data might be locked in case a user
 | 
			
		||||
        syncs at the very moment via SSH
 | 
			
		||||
 | 
			
		||||
2. One database for every project. Similar to the above setup, but this
 | 
			
		||||
   time you use the '%s' placeholder which is replaced with the short name
 | 
			
		||||
   of the indefero project:
 | 
			
		||||
 | 
			
		||||
            $ cat idf.php
 | 
			
		||||
            ...
 | 
			
		||||
            $cfg['mtn_path'] = 'mtn';
 | 
			
		||||
            $cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles');
 | 
			
		||||
            $cfg['mtn_repositories'] = '/home/monotone/%s.mtn';
 | 
			
		||||
            $cfg['mtn_remote_url'] = 'ssh://monotone@my.server.com:~%s.mtn';
 | 
			
		||||
            $cfg['mtn_db_access'] = 'local';
 | 
			
		||||
            ...
 | 
			
		||||
 | 
			
		||||
   The same pro's and con's apply. Additionally you have to be careful about
 | 
			
		||||
   not giving people physical read/write access of another project's database.
 | 
			
		||||
 | 
			
		||||
   Furthermore, if you do not want to use `ssh`, but `netsync` transport,
 | 
			
		||||
   each project's database must be served over a separate port.
 | 
			
		||||
 | 
			
		||||
3. One database for every project, all managed with usher. This is the
 | 
			
		||||
   recommended setup for a mid-size forge setup. The remaining part of this
 | 
			
		||||
   document will describe the process to set this up in detail.
 | 
			
		||||
 | 
			
		||||
   Pro:
 | 
			
		||||
      * access rights can be granted per project and are automatically
 | 
			
		||||
        managed by indefero, just like the user's public monotone keys
 | 
			
		||||
      * no database locking issues
 | 
			
		||||
      * one public server running on the one well-known port
 | 
			
		||||
 | 
			
		||||
   Con:
 | 
			
		||||
      * harder to setup
 | 
			
		||||
 | 
			
		||||
## Installation and configuration of usher
 | 
			
		||||
 | 
			
		||||
1. Clone usher's monotone repository:
 | 
			
		||||
 | 
			
		||||
            $ mtn clone "mtn://monotone.ca?net.venge.monotone.contrib.usher"
 | 
			
		||||
 | 
			
		||||
2. Compile usher:
 | 
			
		||||
 | 
			
		||||
            $ autoreconf -i
 | 
			
		||||
            $ ./configure && make
 | 
			
		||||
            $ sudo make install
 | 
			
		||||
 | 
			
		||||
   This installs the usher binary in $prefix/bin.
 | 
			
		||||
 | 
			
		||||
3. Create a new usher user:
 | 
			
		||||
 | 
			
		||||
            $ adduser --system --disabled-login --home /var/lib/usher usher
 | 
			
		||||
 | 
			
		||||
4. Create the basic usher setup:
 | 
			
		||||
 | 
			
		||||
            $ cd /var/lib/usher
 | 
			
		||||
            $ mkdir projects logs
 | 
			
		||||
            $ cat > usher.conf
 | 
			
		||||
             userpass "admin" "<secret-password>"
 | 
			
		||||
            adminaddr "127.0.0.1:12345"
 | 
			
		||||
               logdir "log"
 | 
			
		||||
            ^D
 | 
			
		||||
            $ chmod 600 usher.conf
 | 
			
		||||
 | 
			
		||||
    Your indefero www user needs later write access to `usher.conf` and
 | 
			
		||||
    `projects/`. There are two ways of setting this up:
 | 
			
		||||
 | 
			
		||||
    * Make the usher user the web user, for example via Apache's `suexec`
 | 
			
		||||
    * Use acls, like this:
 | 
			
		||||
 | 
			
		||||
            $ setfacl -m u:www:rw usher.conf
 | 
			
		||||
            $ setfacl -m d:u:www:rwx projects/
 | 
			
		||||
 | 
			
		||||
5. Wrap a daemonizer around usher, for example supervise from daemontools
 | 
			
		||||
   (<http://cr.yp.to/damontools.html>):
 | 
			
		||||
 | 
			
		||||
            $ cat > run
 | 
			
		||||
            #!/bin/sh
 | 
			
		||||
            cd /var/lib/usher
 | 
			
		||||
            exec 2>&1
 | 
			
		||||
            exec \
 | 
			
		||||
                setuidgid usher \
 | 
			
		||||
                usher usher.conf
 | 
			
		||||
            ^D
 | 
			
		||||
 | 
			
		||||
   The service can now be started through supervise:
 | 
			
		||||
 | 
			
		||||
            $ supervise /var/lib/usher
 | 
			
		||||
 | 
			
		||||
## Configuration of indefero
 | 
			
		||||
 | 
			
		||||
Based on the above setup, the configuration in `src/IDF/conf/idf.php` should
 | 
			
		||||
look like this:
 | 
			
		||||
 | 
			
		||||
        $ cat idf.php
 | 
			
		||||
        ...
 | 
			
		||||
        $cfg['mtn_path'] = 'mtn';
 | 
			
		||||
        $cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles');
 | 
			
		||||
        $cfg['mtn_repositories'] = '/var/lib/usher/projects/%s/';
 | 
			
		||||
        $cfg['mtn_remote_url'] = 'mtn://my.server.com/%s';
 | 
			
		||||
        $cfg['mtn_db_access'] = 'remote';
 | 
			
		||||
        $cfg['mtn_usher_conf'] = '/var/lib/usher/usher.conf';
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
The `%s` placeholders are automatically replaced by the name of the
 | 
			
		||||
indefero project. The plugin assumes that every project is separated
 | 
			
		||||
by a distinct server name in the monotone URL (hence the use of `/%s`),
 | 
			
		||||
so if a user calls
 | 
			
		||||
 | 
			
		||||
        $ mtn sync mtn://my.server.com/project1
 | 
			
		||||
 | 
			
		||||
then the database / repository of the indefero `project1` is used.
 | 
			
		||||
Note that 'mtn_remote_url' is also used as internal URI to query the data
 | 
			
		||||
for indefero's source view, so it *must* be a valid host!
 | 
			
		||||
 | 
			
		||||
Usher also allows the identification of a project repository by hostname,
 | 
			
		||||
which would allow an URL template like `mtn://%s.my.server.com`, however
 | 
			
		||||
the plugin does not write out the configuration which is needed for this
 | 
			
		||||
yet.
 | 
			
		||||
 | 
			
		||||
For even more advanced setups, usher can also be used to forward sync
 | 
			
		||||
requests to other remote servers for load balancing, please consult the
 | 
			
		||||
README file for more information.
 | 
			
		||||
 | 
			
		||||
## Security and remote access
 | 
			
		||||
 | 
			
		||||
Indefero distinguishs between public and private projects and so does
 | 
			
		||||
the monotone plugin.
 | 
			
		||||
 | 
			
		||||
Public projects can be pulled by everybody and pushed by team members
 | 
			
		||||
or additional invited people. Remote command execution is enabled, but
 | 
			
		||||
only for read-only commands.
 | 
			
		||||
 | 
			
		||||
Remote commands can be helpful for a user or a 3rd party tool (like
 | 
			
		||||
[mtn-browse](http://mtn-browse.sourceforge.net) or
 | 
			
		||||
[guitone](http://guitone.thomaskeller.biz)) to browse the database
 | 
			
		||||
contents remotely without having to pull everything in first instance.
 | 
			
		||||
 | 
			
		||||
Private projects on the other hand can only be synced by team members
 | 
			
		||||
or additional invited people. Remote command execution is disabled
 | 
			
		||||
by default. If you want to enable that, simply put the keys of the users
 | 
			
		||||
you want to give access to in your project's `remote-automate-permissions`
 | 
			
		||||
file. In the future this plugin might handle this file just as it handles
 | 
			
		||||
`read-permissions` and `write-permissions`.
 | 
			
		||||
 | 
			
		||||
## Notifications
 | 
			
		||||
 | 
			
		||||
If you have successfully set up your monotone instance, you probably want
 | 
			
		||||
to notify 3rd party systems for incoming changes or simply mirror them
 | 
			
		||||
somewhere else for backup purposes.  The monotone source tree already comes
 | 
			
		||||
with [many example scripts and hooks](http://code.monotone.ca/p/monotone/source/tree/h:net.venge.monotone/contrib)
 | 
			
		||||
which serve these purposes, after only little additional configuration.
 | 
			
		||||
 | 
			
		||||
The usher/indefero-controlled setup automatically looks for *.lua files
 | 
			
		||||
in a directory called `hooks.d` right under the project's base directory
 | 
			
		||||
(configured via $cfg['mtn_repositories']) and this is the ideal place to
 | 
			
		||||
put or link these additional lua sources.
 | 
			
		||||
 | 
			
		||||
## Custom project configurations and templates
 | 
			
		||||
 | 
			
		||||
If a new project is created in IDF, the SyncMonotone plugin creates a new
 | 
			
		||||
configuration tree for the project into the project's configuration directory,
 | 
			
		||||
determined by `$cfg['mtn_repositories']`. IDF ships with the minimum set of
 | 
			
		||||
files for this configuration tree and sets up everything automatically for you.
 | 
			
		||||
 | 
			
		||||
Even more, most of the configuration files from the newly created tree are only
 | 
			
		||||
symlinked to the original configuration directory which is configurable via
 | 
			
		||||
`$cfg['mtn_confdir']` and defaults to `src/IDF/Plugin/SyncMonotone/`. This has
 | 
			
		||||
the advantage that your standard IDF setup automatically receives updates to
 | 
			
		||||
existing (symlinked) configuration files as soon as you update to a newer
 | 
			
		||||
version.
 | 
			
		||||
 | 
			
		||||
You could, however, also choose to place the directory tree somewhere else
 | 
			
		||||
and adapt the contents of the individual files yourself, so these changes get
 | 
			
		||||
automatically applied to all new projects you create. You could even go so far
 | 
			
		||||
and add new files to the tree and let them be processed automatically just
 | 
			
		||||
as the basic files! All you need to do is to copy your files and / or directories
 | 
			
		||||
underknees your `$cfg['mtn_confdir']` and add their relative paths to
 | 
			
		||||
`$cfg['mtn_confdir_extra']`.
 | 
			
		||||
 | 
			
		||||
By convention, all entries which end with a slash are considered directories,
 | 
			
		||||
so mkdir(1) is issued for these entries, all files which do not end up with
 | 
			
		||||
".in" are considered to be static script files which are just symlinked from
 | 
			
		||||
the basic configuration dir and all entries ending on ".in" are considered
 | 
			
		||||
configuration files or templates, which are copied over to the project's
 | 
			
		||||
configuration tree and which get some basic project-specific values replaced.
 | 
			
		||||
 | 
			
		||||
The following placeholders are currently recognized and replaced for these files:
 | 
			
		||||
 | 
			
		||||
 * %%PROJECT%% - the name of the created project
 | 
			
		||||
 * %%MTNPOSTPUSH%% - the absolute path to the `mtn-post-push` script
 | 
			
		||||
 * %%MTNCLIENTKEY%% - the public key hash of the key which is used by IDF
 | 
			
		||||
   to authenticate remote stdio access
 | 
			
		||||
 | 
			
		||||
Thats it - I hope you find it useful :)
 | 
			
		||||
 | 
			
		||||
## Q&A
 | 
			
		||||
 | 
			
		||||
### After I created a new project, IDF throws an exception and tells me that it couldn't save the membership data with a cryptic error message. Whats wrong?
 | 
			
		||||
 | 
			
		||||
Multiple issues could cause that. If you've set up usher, make sure the usher
 | 
			
		||||
can fork your database at all and look out for specific errors in the log file
 | 
			
		||||
of your project. If you stumble upon permission issues, ensure that the user
 | 
			
		||||
who runs the usher can access all files in your project's configuration directory,
 | 
			
		||||
including symlinked files.
 | 
			
		||||
 | 
			
		||||
### I pushed a branch to my server, but it does not show up in IDF. Whats wrong?
 | 
			
		||||
 | 
			
		||||
Check if the heads of your branch are not suspended, i.e. do not carry a
 | 
			
		||||
`suspend` certificate.  This usually hides the branch and all of its history
 | 
			
		||||
from monotone's eyes and therefor also from indefero.  You can either choose
 | 
			
		||||
to "unsuspend" the branch simply by committing and pushing another head or
 | 
			
		||||
by letting monotone ignore all suspend certs.  For the latter, its usually
 | 
			
		||||
enough to add `--ignore-suspend-certs` to the list of options in `$cfg['mtn_opts']`.
 | 
			
		||||
 | 
			
		||||
### I want to display another default branch when I click the "Source" tab. How can I do that?
 | 
			
		||||
 | 
			
		||||
Let the forge admin know the new master branch for your project.  He is able
 | 
			
		||||
to change that quickly.  Depending on the backend / server setup this might
 | 
			
		||||
also require some changes in the usher configuration, but only if usher
 | 
			
		||||
recognizes and proxies your database on a branch name level.
 | 
			
		||||
 | 
			
		||||
@@ -66,6 +66,8 @@ need to put the following in your configuration file:
 | 
			
		||||
    $cfg['idf_plugin_syncsvn_authz_file'] = '/home/svn/dav_svn.authz';
 | 
			
		||||
    $cfg['idf_plugin_syncsvn_passwd_file'] = '/home/svn/dav_svn.passwd';
 | 
			
		||||
    $cfg['idf_plugin_syncsvn_svn_path'] = '/home/svn/repositories'; 
 | 
			
		||||
    // Delete the corresponding repository when deleting the project
 | 
			
		||||
    $cfg['idf_plugin_syncsvn_remove_orphans'] = false;
 | 
			
		||||
 | 
			
		||||
You can have more control over the permissions given to the owners,
 | 
			
		||||
members, extra authorized users and anonymous users if you want with
 | 
			
		||||
@@ -77,3 +79,12 @@ the following configuration variables:
 | 
			
		||||
* **idf_plugin_syncsvn_access_public ('r')**: Anonymous access.
 | 
			
		||||
* **idf_plugin_syncsvn_access_private ('')**: Anonymous access in the case of a private project.
 | 
			
		||||
 | 
			
		||||
## svn: Can't open file '/root/.subversion/servers': Permission denied error
 | 
			
		||||
 | 
			
		||||
If you get the error:
 | 
			
		||||
 | 
			
		||||
    svn: Can't open file '/root/.subversion/servers': Permission denied
 | 
			
		||||
 | 
			
		||||
Check the [fix available](http://projects.ceondo.com/p/indefero/issues/458/)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										86
									
								
								logo/indefero-logo-lite.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								logo/indefero-logo-lite.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
 | 
			
		||||
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   width="16"
 | 
			
		||||
   height="16"
 | 
			
		||||
   id="svg2"
 | 
			
		||||
   sodipodi:version="0.32"
 | 
			
		||||
   inkscape:version="0.48.0 r9654"
 | 
			
		||||
   version="1.0"
 | 
			
		||||
   sodipodi:docname="indefero-logo-lite.svg"
 | 
			
		||||
   inkscape:output_extension="org.inkscape.output.svg.inkscape">
 | 
			
		||||
  <defs
 | 
			
		||||
     id="defs4">
 | 
			
		||||
    <inkscape:perspective
 | 
			
		||||
       sodipodi:type="inkscape:persp3d"
 | 
			
		||||
       inkscape:vp_x="0 : 526.18109 : 1"
 | 
			
		||||
       inkscape:vp_y="0 : 1000 : 0"
 | 
			
		||||
       inkscape:vp_z="744.09448 : 526.18109 : 1"
 | 
			
		||||
       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
 | 
			
		||||
       id="perspective10" />
 | 
			
		||||
  </defs>
 | 
			
		||||
  <sodipodi:namedview
 | 
			
		||||
     id="base"
 | 
			
		||||
     pagecolor="#ffffff"
 | 
			
		||||
     bordercolor="#666666"
 | 
			
		||||
     borderopacity="1.0"
 | 
			
		||||
     gridtolerance="10000"
 | 
			
		||||
     guidetolerance="10"
 | 
			
		||||
     objecttolerance="10"
 | 
			
		||||
     inkscape:pageopacity="0.0"
 | 
			
		||||
     inkscape:pageshadow="2"
 | 
			
		||||
     inkscape:zoom="11.313708"
 | 
			
		||||
     inkscape:cx="31.568929"
 | 
			
		||||
     inkscape:cy="-0.35578703"
 | 
			
		||||
     inkscape:document-units="px"
 | 
			
		||||
     inkscape:current-layer="layer1"
 | 
			
		||||
     showgrid="false"
 | 
			
		||||
     inkscape:window-width="1280"
 | 
			
		||||
     inkscape:window-height="723"
 | 
			
		||||
     inkscape:window-x="0"
 | 
			
		||||
     inkscape:window-y="24"
 | 
			
		||||
     inkscape:window-maximized="1" />
 | 
			
		||||
  <metadata
 | 
			
		||||
     id="metadata7">
 | 
			
		||||
    <rdf:RDF>
 | 
			
		||||
      <cc:Work
 | 
			
		||||
         rdf:about="">
 | 
			
		||||
        <dc:format>image/svg+xml</dc:format>
 | 
			
		||||
        <dc:type
 | 
			
		||||
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
			
		||||
      </cc:Work>
 | 
			
		||||
    </rdf:RDF>
 | 
			
		||||
  </metadata>
 | 
			
		||||
  <g
 | 
			
		||||
     inkscape:label="Layer 1"
 | 
			
		||||
     inkscape:groupmode="layer"
 | 
			
		||||
     id="layer1"
 | 
			
		||||
     transform="translate(0,-728.09451)">
 | 
			
		||||
    <g
 | 
			
		||||
       id="g2401"
 | 
			
		||||
       transform="matrix(0.13580542,0,0,0.13580542,-47.580342,708.10521)"
 | 
			
		||||
       style="fill:#8ae234;stroke:#4e9a06;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none"
 | 
			
		||||
       inkscape:export-filename="/home/loa/Projects/indefero/logo/powered-by-indefero.png"
 | 
			
		||||
       inkscape:export-xdpi="12.330909"
 | 
			
		||||
       inkscape:export-ydpi="12.330909">
 | 
			
		||||
      <path
 | 
			
		||||
         id="path2383"
 | 
			
		||||
         d="m 396.19089,173.14471 c -7.67621,0.80661 -14.40195,5.39406 -19.58101,10.89131 -7.23597,7.88004 -11.69742,18.07908 -13.32198,28.60362 -1.7236,11.28173 -0.25925,23.20635 5.07686,33.37271 3.78607,7.24384 9.53161,13.92339 17.29701,16.96772 3.86478,1.53937 8.98362,1.03284 11.67912,-2.41036 2.64357,-3.5671 2.69463,-8.234 2.85756,-12.48867 0.045,-7.61054 -0.54749,-15.25544 0.45618,-22.83193 0.87131,-9.50623 4.03944,-18.56751 6.71612,-27.66851 1.16242,-4.44333 2.25094,-9.02808 1.97499,-13.64988 -0.48817,-4.62476 -3.58059,-9.31042 -8.2964,-10.4067 -1.57489,-0.44882 -3.23412,-0.48948 -4.85845,-0.37931 z"
 | 
			
		||||
         style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#4e9a06;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
 | 
			
		||||
         inkscape:connector-curvature="0" />
 | 
			
		||||
      <path
 | 
			
		||||
         id="path2391"
 | 
			
		||||
         d="m 433.14691,149.28687 c 7.2059,2.76589 12.51512,8.93778 16.09494,15.58815 4.94991,9.48434 6.61962,20.49058 5.46486,31.07695 -1.25505,11.34342 -5.75582,22.48271 -13.54134,30.92159 -5.53192,6.01709 -12.81048,10.98198 -21.09918,11.91276 -4.13154,0.4866 -8.94486,-1.32748 -10.65734,-5.35104 -1.63027,-4.12976 -0.4717,-8.65084 0.47212,-12.80269 1.92628,-7.36287 4.47721,-14.59393 5.4687,-22.17201 1.61875,-9.40784 0.90381,-18.98034 0.67386,-28.46402 0.0272,-4.59278 0.1624,-9.30303 1.62515,-13.69592 1.66851,-4.34082 5.86829,-8.06645 10.70716,-7.90484 1.63738,-0.0259 3.25061,0.36424 4.79107,0.89107 z"
 | 
			
		||||
         style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#4e9a06;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
 | 
			
		||||
         inkscape:connector-curvature="0" />
 | 
			
		||||
    </g>
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 4.1 KiB  | 
							
								
								
									
										248
									
								
								logo/indefero-logo5.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								logo/indefero-logo5.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,248 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:xlink="http://www.w3.org/1999/xlink"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   width="2500.0901"
 | 
			
		||||
   height="1052.3622"
 | 
			
		||||
   id="svg2"
 | 
			
		||||
   sodipodi:version="0.32"
 | 
			
		||||
   inkscape:version="0.46"
 | 
			
		||||
   sodipodi:docname="indefero-logo5.svg"
 | 
			
		||||
   inkscape:output_extension="org.inkscape.output.svg.inkscape"
 | 
			
		||||
   version="1.0">
 | 
			
		||||
  <defs
 | 
			
		||||
     id="defs4">
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       inkscape:collect="always"
 | 
			
		||||
       id="linearGradient3535">
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#8ae234;stop-opacity:1;"
 | 
			
		||||
         offset="0"
 | 
			
		||||
         id="stop3537" />
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#8ae234;stop-opacity:0;"
 | 
			
		||||
         offset="1"
 | 
			
		||||
         id="stop3539" />
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       inkscape:collect="always"
 | 
			
		||||
       id="linearGradient3527">
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#9f5e21;stop-opacity:1;"
 | 
			
		||||
         offset="0"
 | 
			
		||||
         id="stop3529" />
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#9f5e21;stop-opacity:0;"
 | 
			
		||||
         offset="1"
 | 
			
		||||
         id="stop3531" />
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       id="linearGradient3309">
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#73d213;stop-opacity:1;"
 | 
			
		||||
         offset="0"
 | 
			
		||||
         id="stop3311" />
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#4e9a06;stop-opacity:1;"
 | 
			
		||||
         offset="1"
 | 
			
		||||
         id="stop3313" />
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       id="linearGradient3300">
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#a56223;stop-opacity:1;"
 | 
			
		||||
         offset="0"
 | 
			
		||||
         id="stop3302" />
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#492902;stop-opacity:1;"
 | 
			
		||||
         offset="1"
 | 
			
		||||
         id="stop3304" />
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       id="linearGradient3214">
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#c17d11;stop-opacity:1;"
 | 
			
		||||
         offset="0"
 | 
			
		||||
         id="stop3216" />
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#c17d11;stop-opacity:0;"
 | 
			
		||||
         offset="1"
 | 
			
		||||
         id="stop3218" />
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       id="linearGradient3200">
 | 
			
		||||
      <stop
 | 
			
		||||
         id="stop3204"
 | 
			
		||||
         offset="1"
 | 
			
		||||
         style="stop-color:#feaf36;stop-opacity:1;" />
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       id="linearGradient3178">
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#8f5902;stop-opacity:1;"
 | 
			
		||||
         offset="0"
 | 
			
		||||
         id="stop3180" />
 | 
			
		||||
      <stop
 | 
			
		||||
         style="stop-color:#8f5902;stop-opacity:0;"
 | 
			
		||||
         offset="1"
 | 
			
		||||
         id="stop3182" />
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
    <inkscape:perspective
 | 
			
		||||
       sodipodi:type="inkscape:persp3d"
 | 
			
		||||
       inkscape:vp_x="0 : 526.18109 : 1"
 | 
			
		||||
       inkscape:vp_y="0 : 1000 : 0"
 | 
			
		||||
       inkscape:vp_z="744.09448 : 526.18109 : 1"
 | 
			
		||||
       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
 | 
			
		||||
       id="perspective10" />
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       inkscape:collect="always"
 | 
			
		||||
       xlink:href="#linearGradient3300"
 | 
			
		||||
       id="linearGradient3502"
 | 
			
		||||
       gradientUnits="userSpaceOnUse"
 | 
			
		||||
       x1="255.2104"
 | 
			
		||||
       y1="743.95398"
 | 
			
		||||
       x2="517.18134"
 | 
			
		||||
       y2="334.32626" />
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       inkscape:collect="always"
 | 
			
		||||
       xlink:href="#linearGradient3309"
 | 
			
		||||
       id="linearGradient3505"
 | 
			
		||||
       gradientUnits="userSpaceOnUse"
 | 
			
		||||
       gradientTransform="matrix(0.90055,0,0,0.9256685,8.702072,50.919096)"
 | 
			
		||||
       x1="314.81146"
 | 
			
		||||
       y1="549.55981"
 | 
			
		||||
       x2="603.79364"
 | 
			
		||||
       y2="130.80347" />
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       inkscape:collect="always"
 | 
			
		||||
       xlink:href="#linearGradient3527"
 | 
			
		||||
       id="linearGradient3533"
 | 
			
		||||
       x1="344.48004"
 | 
			
		||||
       y1="526.93384"
 | 
			
		||||
       x2="279.66428"
 | 
			
		||||
       y2="343.77176"
 | 
			
		||||
       gradientUnits="userSpaceOnUse" />
 | 
			
		||||
    <linearGradient
 | 
			
		||||
       inkscape:collect="always"
 | 
			
		||||
       xlink:href="#linearGradient3535"
 | 
			
		||||
       id="linearGradient3541"
 | 
			
		||||
       x1="189.38306"
 | 
			
		||||
       y1="542.79291"
 | 
			
		||||
       x2="213.16689"
 | 
			
		||||
       y2="515.03833"
 | 
			
		||||
       gradientUnits="userSpaceOnUse" />
 | 
			
		||||
  </defs>
 | 
			
		||||
  <sodipodi:namedview
 | 
			
		||||
     id="base"
 | 
			
		||||
     pagecolor="#ffffff"
 | 
			
		||||
     bordercolor="#666666"
 | 
			
		||||
     borderopacity="1.0"
 | 
			
		||||
     gridtolerance="10000"
 | 
			
		||||
     guidetolerance="10"
 | 
			
		||||
     objecttolerance="10"
 | 
			
		||||
     inkscape:pageopacity="0.0"
 | 
			
		||||
     inkscape:pageshadow="2"
 | 
			
		||||
     inkscape:zoom="0.26121807"
 | 
			
		||||
     inkscape:cx="54.404882"
 | 
			
		||||
     inkscape:cy="468.48648"
 | 
			
		||||
     inkscape:document-units="px"
 | 
			
		||||
     inkscape:current-layer="layer1"
 | 
			
		||||
     showgrid="false"
 | 
			
		||||
     inkscape:window-width="1279"
 | 
			
		||||
     inkscape:window-height="951"
 | 
			
		||||
     inkscape:window-x="0"
 | 
			
		||||
     inkscape:window-y="25" />
 | 
			
		||||
  <metadata
 | 
			
		||||
     id="metadata7">
 | 
			
		||||
    <rdf:RDF>
 | 
			
		||||
      <cc:Work
 | 
			
		||||
         rdf:about="">
 | 
			
		||||
        <dc:format>image/svg+xml</dc:format>
 | 
			
		||||
        <dc:type
 | 
			
		||||
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
			
		||||
      </cc:Work>
 | 
			
		||||
    </rdf:RDF>
 | 
			
		||||
  </metadata>
 | 
			
		||||
  <g
 | 
			
		||||
     inkscape:label="Layer 1"
 | 
			
		||||
     inkscape:groupmode="layer"
 | 
			
		||||
     id="layer1">
 | 
			
		||||
    <rect
 | 
			
		||||
       style="opacity:1;fill:#2e1c09;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
 | 
			
		||||
       id="rect3479"
 | 
			
		||||
       width="2327.0989"
 | 
			
		||||
       height="738.08856"
 | 
			
		||||
       x="28.861307"
 | 
			
		||||
       y="47.247032"
 | 
			
		||||
       ry="0"
 | 
			
		||||
       inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
 | 
			
		||||
       inkscape:export-xdpi="10.974293"
 | 
			
		||||
       inkscape:export-ydpi="10.974293" />
 | 
			
		||||
    <path
 | 
			
		||||
       style="fill:url(#linearGradient3505);fill-opacity:1;fill-rule:nonzero;stroke:#4e9a06;stroke-width:8.6737175;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
 | 
			
		||||
       d="M 380.09452,80.164434 C 325.61878,80.164434 277.73831,109.63179 250.44346,154.01542 C 173.08362,161.38346 112.57489,224.77008 112.57489,301.77525 C 112.57489,328.86745 120.06848,354.27485 133.14683,376.14693 C 107.0966,405.29318 91.243109,443.82739 91.243109,486.07006 C 91.243109,576.90189 164.55915,650.60763 254.88993,650.60763 C 271.09473,650.60763 286.73713,648.22781 301.52154,643.80975 C 339.34139,666.58702 385.02955,679.88189 434.18381,679.88189 C 564.58392,679.88189 670.40931,586.17696 670.40933,470.70975 C 670.40933,424.81133 653.66603,382.36023 625.32555,347.85618 C 628.73221,333.4146 630.53185,318.20844 630.53185,302.49843 C 630.53185,212.12462 570.29216,138.17535 494.43623,133.1011 C 466.29611,100.61828 425.47491,80.164434 380.09452,80.164434 z"
 | 
			
		||||
       id="path2397"
 | 
			
		||||
       inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
 | 
			
		||||
       inkscape:export-xdpi="10.974293"
 | 
			
		||||
       inkscape:export-ydpi="10.974293" />
 | 
			
		||||
    <path
 | 
			
		||||
       style="fill:url(#linearGradient3502);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.40000010000000019;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
 | 
			
		||||
       d="M 480.78826,286.70465 C 471.74217,303.14801 466.59919,316.91871 462.21007,324.32034 C 458.4305,332.41806 437.64848,381.27668 422.04792,410.85567 C 411.43569,432.35724 400.69212,454.31445 384.28125,472.09375 C 378.04049,477.37818 367.78727,475.23418 363.26933,468.71428 C 353.17919,458.89555 343.45836,448.70244 333.24758,439.0075 C 317.77144,422.84753 305.56098,403.5827 297.62915,382.65768 C 289.88676,365.17614 282.06423,347.03016 268.29833,333.33399 C 266.87079,332.00892 257.87723,342.72971 251.17151,350.13535 C 257.91944,385.02379 261.45712,393.83832 272.16171,424.59112 C 277.97988,444.06591 287.52802,462.10601 295.01596,480.9416 C 301.95377,497.73482 307.73336,515.11749 310.44798,533.13227 C 316.97458,571.06863 313.78852,610.29702 303.54294,647.28446 C 295.53018,678.50408 283.87873,708.83248 268.40625,737.125 C 267.48586,741.82958 273.28765,745.0451 277.32903,743.60868 C 316.10441,740.6132 354.85515,736.72668 393.76368,736.01019 C 432.08809,735.62752 470.30781,739.07963 508.44819,742.41755 C 512.60098,742.7876 516.75356,743.16004 520.90625,743.53125 C 522.73666,738.32781 518.93119,733.37885 515.5,729.84375 C 505.37922,718.85052 495.85001,707.33025 486.04792,696.05659 C 472.12065,679.28224 459.49275,660.74518 453.48949,639.55179 C 445.74909,613.54378 446.01072,585.89039 449.3871,559.18722 C 454.13621,520.65521 457.70434,476.90793 468.12716,439.5747 C 475.03595,414.82825 499.26944,325.16045 505.28557,302.9463 C 506.6387,297.45482 507.70151,291.88947 508.4375,286.28125 C 508.3006,286.02979 501.31023,285.50092 480.78826,286.70465 z"
 | 
			
		||||
       id="rect2383"
 | 
			
		||||
       sodipodi:nodetypes="ccscscscccccccccccccccsccc"
 | 
			
		||||
       inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
 | 
			
		||||
       inkscape:export-xdpi="10.974293"
 | 
			
		||||
       inkscape:export-ydpi="10.974293" />
 | 
			
		||||
    <path
 | 
			
		||||
       style="fill:url(#linearGradient3533);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25000000000000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 | 
			
		||||
       d="M 261.61445,343.77177 C 258.29796,347.16111 256.78029,348.5143 253.80525,350.94005 C 257.34812,367.72635 271.24491,410.33512 274.92704,420.91638 C 283.98781,446.95407 298.27703,470.44926 305.96388,496.81755 C 312.6285,519.6793 316.95796,541.99153 318.18734,565.63445 C 319.43684,589.66412 317.35056,617.16231 311.24725,640.25675 C 304.22544,666.82669 289.67219,709.81256 278.71119,735.38821 C 291.85032,736.74081 303.9092,734.30172 317.58611,733.28738 C 330.8032,732.91089 346.51711,728.41602 360.28427,728.58961 C 361.70248,692.98386 362.28914,639.87401 361.91883,606.08799 C 361.53577,571.13943 356.10739,550.49134 343.0934,518.4162 C 332.76842,492.96857 313.62977,457.39972 298.73254,430.34797 C 283.83531,403.29622 270.0195,371.02282 261.61445,343.77177 z"
 | 
			
		||||
       id="path3450"
 | 
			
		||||
       sodipodi:nodetypes="ccsssscccsssc"
 | 
			
		||||
       inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
 | 
			
		||||
       inkscape:export-xdpi="10.974293"
 | 
			
		||||
       inkscape:export-ydpi="10.974293" />
 | 
			
		||||
    <path
 | 
			
		||||
       style="fill:url(#linearGradient3541);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25000000000000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 | 
			
		||||
       d="M 113.16529,429.39362 C 113.16529,429.39362 104.64326,454.1652 103.03794,468.69107 C 101.44344,483.11903 102.46825,506.23771 105.4804,520.43755 C 108.98801,536.97308 121.29761,558.93795 130.281,573.25668 C 138.61935,586.54728 158.09648,604.51753 171.10612,613.28778 C 185.25725,622.82754 205.57955,633.4095 222.19789,637.29436 C 239.00231,641.22271 255.43702,641.52676 272.63495,640.09436 C 278.21565,639.62955 302.26007,633.26385 302.26007,633.26385 L 308.87233,606.09973 L 113.16529,429.39362 z"
 | 
			
		||||
       id="path3461"
 | 
			
		||||
       sodipodi:nodetypes="cssssssccc"
 | 
			
		||||
       inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
 | 
			
		||||
       inkscape:export-xdpi="10.974293"
 | 
			
		||||
       inkscape:export-ydpi="10.974293" />
 | 
			
		||||
    <text
 | 
			
		||||
       xml:space="preserve"
 | 
			
		||||
       style="font-size:500px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;writing-mode:lr-tb;text-anchor:start;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:FromageCondOblique;-inkscape-font-specification:FromageCondOblique"
 | 
			
		||||
       x="739.95435"
 | 
			
		||||
       y="534.80164"
 | 
			
		||||
       id="text3481"
 | 
			
		||||
       inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
 | 
			
		||||
       inkscape:export-xdpi="10.974293"
 | 
			
		||||
       inkscape:export-ydpi="10.974293"><tspan
 | 
			
		||||
         sodipodi:role="line"
 | 
			
		||||
         id="tspan3483"
 | 
			
		||||
         x="739.95435"
 | 
			
		||||
         y="534.80164">indefero</tspan></text>
 | 
			
		||||
    <text
 | 
			
		||||
       xml:space="preserve"
 | 
			
		||||
       style="font-size:150px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:FromageCondOblique;-inkscape-font-specification:FromageCondOblique"
 | 
			
		||||
       x="744.05432"
 | 
			
		||||
       y="711.55402"
 | 
			
		||||
       id="text3485"
 | 
			
		||||
       sodipodi:linespacing="125%"
 | 
			
		||||
       inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
 | 
			
		||||
       inkscape:export-xdpi="10.974293"
 | 
			
		||||
       inkscape:export-ydpi="10.974293"><tspan
 | 
			
		||||
         sodipodi:role="line"
 | 
			
		||||
         id="tspan3487"
 | 
			
		||||
         x="744.05432"
 | 
			
		||||
         y="711.55402"
 | 
			
		||||
         style="letter-spacing:5.01085806">Better code management</tspan></text>
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 12 KiB  | 
							
								
								
									
										173
									
								
								logo/no_logo.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								logo/no_logo.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
 | 
			
		||||
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   sodipodi:docname="no_logo.svg"
 | 
			
		||||
   inkscape:version="0.47 r22583"
 | 
			
		||||
   id="svg2985"
 | 
			
		||||
   height="32"
 | 
			
		||||
   width="32"
 | 
			
		||||
   version="1.1"
 | 
			
		||||
   inkscape:export-filename="/Users/tommyd/Entwicklung/indefero/www/media/idf/img/no_logo.png"
 | 
			
		||||
   inkscape:export-xdpi="89.989998"
 | 
			
		||||
   inkscape:export-ydpi="89.989998">
 | 
			
		||||
  <sodipodi:namedview
 | 
			
		||||
     pagecolor="#ffffff"
 | 
			
		||||
     bordercolor="#666666"
 | 
			
		||||
     borderopacity="1"
 | 
			
		||||
     objecttolerance="10"
 | 
			
		||||
     gridtolerance="10"
 | 
			
		||||
     guidetolerance="10"
 | 
			
		||||
     inkscape:pageopacity="0"
 | 
			
		||||
     inkscape:pageshadow="2"
 | 
			
		||||
     inkscape:window-width="1440"
 | 
			
		||||
     inkscape:window-height="852"
 | 
			
		||||
     id="namedview9"
 | 
			
		||||
     showgrid="false"
 | 
			
		||||
     inkscape:zoom="16.0625"
 | 
			
		||||
     inkscape:cx="8.5507561"
 | 
			
		||||
     inkscape:cy="16.122403"
 | 
			
		||||
     inkscape:window-x="0"
 | 
			
		||||
     inkscape:window-y="0"
 | 
			
		||||
     inkscape:window-maximized="0"
 | 
			
		||||
     inkscape:current-layer="g2847"
 | 
			
		||||
     showguides="true"
 | 
			
		||||
     inkscape:guide-bbox="true">
 | 
			
		||||
    <sodipodi:guide
 | 
			
		||||
       orientation="1,0"
 | 
			
		||||
       position="16,25.836575"
 | 
			
		||||
       id="guide3752" />
 | 
			
		||||
    <sodipodi:guide
 | 
			
		||||
       orientation="0,1"
 | 
			
		||||
       position="-18.677042,16"
 | 
			
		||||
       id="guide3754" />
 | 
			
		||||
  </sodipodi:namedview>
 | 
			
		||||
  <defs
 | 
			
		||||
     id="defs2987">
 | 
			
		||||
    <inkscape:perspective
 | 
			
		||||
       sodipodi:type="inkscape:persp3d"
 | 
			
		||||
       inkscape:vp_x="0 : 16 : 1"
 | 
			
		||||
       inkscape:vp_y="0 : 1000 : 0"
 | 
			
		||||
       inkscape:vp_z="32 : 16 : 1"
 | 
			
		||||
       inkscape:persp3d-origin="16 : 10.666667 : 1"
 | 
			
		||||
       id="perspective13" />
 | 
			
		||||
    <inkscape:perspective
 | 
			
		||||
       id="perspective2863"
 | 
			
		||||
       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
 | 
			
		||||
       inkscape:vp_z="1 : 0.5 : 1"
 | 
			
		||||
       inkscape:vp_y="0 : 1000 : 0"
 | 
			
		||||
       inkscape:vp_x="0 : 0.5 : 1"
 | 
			
		||||
       sodipodi:type="inkscape:persp3d" />
 | 
			
		||||
    <inkscape:perspective
 | 
			
		||||
       id="perspective3676"
 | 
			
		||||
       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
 | 
			
		||||
       inkscape:vp_z="1 : 0.5 : 1"
 | 
			
		||||
       inkscape:vp_y="0 : 1000 : 0"
 | 
			
		||||
       inkscape:vp_x="0 : 0.5 : 1"
 | 
			
		||||
       sodipodi:type="inkscape:persp3d" />
 | 
			
		||||
    <inkscape:perspective
 | 
			
		||||
       id="perspective3717"
 | 
			
		||||
       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
 | 
			
		||||
       inkscape:vp_z="1 : 0.5 : 1"
 | 
			
		||||
       inkscape:vp_y="0 : 1000 : 0"
 | 
			
		||||
       inkscape:vp_x="0 : 0.5 : 1"
 | 
			
		||||
       sodipodi:type="inkscape:persp3d" />
 | 
			
		||||
    <filter
 | 
			
		||||
       inkscape:collect="always"
 | 
			
		||||
       id="filter3816"
 | 
			
		||||
       x="-0.14434362"
 | 
			
		||||
       width="1.2886872"
 | 
			
		||||
       y="-0.11562817"
 | 
			
		||||
       height="1.2312563">
 | 
			
		||||
      <feGaussianBlur
 | 
			
		||||
         inkscape:collect="always"
 | 
			
		||||
         stdDeviation="1.1799243"
 | 
			
		||||
         id="feGaussianBlur3818" />
 | 
			
		||||
    </filter>
 | 
			
		||||
  </defs>
 | 
			
		||||
  <metadata
 | 
			
		||||
     id="metadata2990">
 | 
			
		||||
    <rdf:RDF>
 | 
			
		||||
      <cc:Work
 | 
			
		||||
         rdf:about="">
 | 
			
		||||
        <dc:format>image/svg+xml</dc:format>
 | 
			
		||||
        <dc:type
 | 
			
		||||
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
			
		||||
        <dc:title></dc:title>
 | 
			
		||||
      </cc:Work>
 | 
			
		||||
    </rdf:RDF>
 | 
			
		||||
  </metadata>
 | 
			
		||||
  <g
 | 
			
		||||
     inkscape:groupmode="layer"
 | 
			
		||||
     id="layer2"
 | 
			
		||||
     inkscape:label="shadow"
 | 
			
		||||
     style="display:inline"
 | 
			
		||||
     sodipodi:insensitive="true">
 | 
			
		||||
    <g
 | 
			
		||||
       transform="translate(0.44042901,0.78704792)"
 | 
			
		||||
       id="g2847-8"
 | 
			
		||||
       style="opacity:0.79710143;fill:#000000;stroke:#000000;stroke-opacity:1;filter:url(#filter3816)">
 | 
			
		||||
      <g
 | 
			
		||||
         id="g3838-0"
 | 
			
		||||
         style="fill:#000000;stroke:#000000;stroke-opacity:1" />
 | 
			
		||||
      <g
 | 
			
		||||
         id="g2401-2"
 | 
			
		||||
         transform="matrix(0.21219597,0,0,0.21219597,-70.751966,-27.73328)"
 | 
			
		||||
         style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2.4000001;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
 | 
			
		||||
         inkscape:export-filename="/home/loa/Projects/indefero/logo/powered-by-indefero.png"
 | 
			
		||||
         inkscape:export-xdpi="12.330909"
 | 
			
		||||
         inkscape:export-ydpi="12.330909">
 | 
			
		||||
        <path
 | 
			
		||||
           id="path2383-4"
 | 
			
		||||
           d="m 396.19089,173.14471 c -7.67621,0.80661 -14.40195,5.39406 -19.58101,10.89131 -7.23597,7.88004 -11.69742,18.07908 -13.32198,28.60362 -1.7236,11.28173 -0.25925,23.20635 5.07686,33.37271 3.78607,7.24384 9.53161,13.92339 17.29701,16.96772 3.86478,1.53937 8.98362,1.03284 11.67912,-2.41036 2.64357,-3.5671 2.69463,-8.234 2.85756,-12.48867 0.045,-7.61054 -0.54749,-15.25544 0.45618,-22.83193 0.87131,-9.50623 4.03944,-18.56751 6.71612,-27.66851 1.16242,-4.44333 2.25094,-9.02808 1.97499,-13.64988 -0.48817,-4.62476 -3.58059,-9.31042 -8.2964,-10.4067 -1.57489,-0.44882 -3.23412,-0.48948 -4.85845,-0.37931 z"
 | 
			
		||||
           style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
 | 
			
		||||
           inkscape:connector-curvature="0" />
 | 
			
		||||
        <path
 | 
			
		||||
           id="path2391-8"
 | 
			
		||||
           d="m 433.14691,149.28687 c 7.2059,2.76589 12.51512,8.93778 16.09494,15.58815 4.94991,9.48434 6.61962,20.49058 5.46486,31.07695 -1.25505,11.34342 -5.75582,22.48271 -13.54134,30.92159 -5.53192,6.01709 -12.81048,10.98198 -21.09918,11.91276 -4.13154,0.4866 -8.94486,-1.32748 -10.65734,-5.35104 -1.63027,-4.12976 -0.4717,-8.65084 0.47212,-12.80269 1.92628,-7.36287 4.47721,-14.59393 5.4687,-22.17201 1.61875,-9.40784 0.90381,-18.98034 0.67386,-28.46402 0.0272,-4.59278 0.1624,-9.30303 1.62515,-13.69592 1.66851,-4.34082 5.86829,-8.06645 10.70716,-7.90484 1.63738,-0.0259 3.25061,0.36424 4.79107,0.89107 z"
 | 
			
		||||
           style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
 | 
			
		||||
           inkscape:connector-curvature="0" />
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
  </g>
 | 
			
		||||
  <g
 | 
			
		||||
     inkscape:groupmode="layer"
 | 
			
		||||
     id="layer3"
 | 
			
		||||
     inkscape:label="logo"
 | 
			
		||||
     style="display:inline">
 | 
			
		||||
    <g
 | 
			
		||||
       id="layer1"
 | 
			
		||||
       transform="translate(-0.06540759,0.09444087)">
 | 
			
		||||
      <g
 | 
			
		||||
         id="g2847">
 | 
			
		||||
        <g
 | 
			
		||||
           id="g3838" />
 | 
			
		||||
        <g
 | 
			
		||||
           id="g2401"
 | 
			
		||||
           transform="matrix(0.21219597,0,0,0.21219597,-70.751966,-27.73328)"
 | 
			
		||||
           style="fill:#e6e6e6;fill-opacity:1;stroke:#a0a0a0;stroke-width:2.4000001;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
 | 
			
		||||
           inkscape:export-filename="/home/loa/Projects/indefero/logo/powered-by-indefero.png"
 | 
			
		||||
           inkscape:export-xdpi="12.330909"
 | 
			
		||||
           inkscape:export-ydpi="12.330909">
 | 
			
		||||
          <path
 | 
			
		||||
             id="path2383"
 | 
			
		||||
             d="m 396.19089,173.14471 c -7.67621,0.80661 -14.40195,5.39406 -19.58101,10.89131 -7.23597,7.88004 -11.69742,18.07908 -13.32198,28.60362 -1.7236,11.28173 -0.25925,23.20635 5.07686,33.37271 3.78607,7.24384 9.53161,13.92339 17.29701,16.96772 3.86478,1.53937 8.98362,1.03284 11.67912,-2.41036 2.64357,-3.5671 2.69463,-8.234 2.85756,-12.48867 0.045,-7.61054 -0.54749,-15.25544 0.45618,-22.83193 0.87131,-9.50623 4.03944,-18.56751 6.71612,-27.66851 1.16242,-4.44333 2.25094,-9.02808 1.97499,-13.64988 -0.48817,-4.62476 -3.58059,-9.31042 -8.2964,-10.4067 -1.57489,-0.44882 -3.23412,-0.48948 -4.85845,-0.37931 z"
 | 
			
		||||
             style="fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#a0a0a0;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
 | 
			
		||||
             inkscape:connector-curvature="0" />
 | 
			
		||||
          <path
 | 
			
		||||
             id="path2391"
 | 
			
		||||
             d="m 433.14691,149.28687 c 7.2059,2.76589 12.51512,8.93778 16.09494,15.58815 4.94991,9.48434 6.61962,20.49058 5.46486,31.07695 -1.25505,11.34342 -5.75582,22.48271 -13.54134,30.92159 -5.53192,6.01709 -12.81048,10.98198 -21.09918,11.91276 -4.13154,0.4866 -8.94486,-1.32748 -10.65734,-5.35104 -1.63027,-4.12976 -0.4717,-8.65084 0.47212,-12.80269 1.92628,-7.36287 4.47721,-14.59393 5.4687,-22.17201 1.61875,-9.40784 0.90381,-18.98034 0.67386,-28.46402 0.0272,-4.59278 0.1624,-9.30303 1.62515,-13.69592 1.66851,-4.34082 5.86829,-8.06645 10.70716,-7.90484 1.63738,-0.0259 3.25061,0.36424 4.79107,0.89107 z"
 | 
			
		||||
             style="fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#a0a0a0;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
 | 
			
		||||
             inkscape:connector-curvature="0" />
 | 
			
		||||
        </g>
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 8.4 KiB  | 
							
								
								
									
										27
									
								
								phpunit.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								phpunit.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
<phpunit backupGlobals="true"
 | 
			
		||||
         backupStaticAttributes="false"
 | 
			
		||||
         bootstrap="test/bootstrap.php"
 | 
			
		||||
         convertErrorsToExceptions="true"
 | 
			
		||||
         convertNoticesToExceptions="true"
 | 
			
		||||
         convertWarningsToExceptions="true"
 | 
			
		||||
         strict="false"
 | 
			
		||||
         verbose="true">
 | 
			
		||||
 | 
			
		||||
    <testsuites>
 | 
			
		||||
        <testsuite name="Everything">
 | 
			
		||||
            <directory>test/IDF/</directory>
 | 
			
		||||
        </testsuite>
 | 
			
		||||
    </testsuites>
 | 
			
		||||
 | 
			
		||||
    <filter>
 | 
			
		||||
        <whitelist>
 | 
			
		||||
            <directory suffix=".php">src/IDF</directory>
 | 
			
		||||
            <exclude>
 | 
			
		||||
                <directory suffix=".php">src/IDF/Tests</directory>
 | 
			
		||||
                <directory suffix=".php">src/IDF/conf</directory>
 | 
			
		||||
                <file>src/IDF/version.php</file>
 | 
			
		||||
            </exclude>
 | 
			
		||||
        </whitelist>
 | 
			
		||||
    </filter>
 | 
			
		||||
</phpunit>
 | 
			
		||||
 | 
			
		||||
@@ -1,10 +1,13 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
last="$1"
 | 
			
		||||
new="$2"
 | 
			
		||||
PLUF_PATH=`php -r "require_once('./IDF/conf/path.php'); echo PLUF_PATH;"`
 | 
			
		||||
echo "php $PLUF_PATH/extracttemplates.php ./IDF/conf/idf.php ./IDF/gettexttemplates"
 | 
			
		||||
echo "xgettext -o idf.pot -p ./IDF/locale --force-po --from-code=UTF-8 --keyword --keyword=__ --keyword=_n:1,2 -L PHP ./IDF/*.php"
 | 
			
		||||
echo "find ./ -iname \"*.php\" -exec xgettext -o idf.pot -p ./IDF/locale/ --from-code=UTF-8 -j --keyword --keyword=__ --keyword=_n:1,2 -L PHP {} \;"
 | 
			
		||||
echo 'for pofile in `ls ./IDF/locale/*/idf.po`; do msgmerge -U $pofile ./IDF/locale/idf.pot; done'
 | 
			
		||||
echo "# git tag v$new"
 | 
			
		||||
echo "git archive --format=zip --prefix=indefero-$new/ v$new > indefero-$new.zip"
 | 
			
		||||
echo "git log --no-merges v$new ^v$last > ChangeLog-$new"
 | 
			
		||||
echo "git shortlog --no-merges v$new ^v$last > ShortLog"
 | 
			
		||||
echo "git diff --stat --summary -M v$last v$new > diffstat-$new"
 | 
			
		||||
echo "git diff --stat --summary -M v$last v$new > diffstat-$new"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								run-tests
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										14
									
								
								run-tests
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
#!/usr/bin/env php
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
$xmlfile = dirname(__FILE__) .'/test/report.xml';
 | 
			
		||||
passthru('phpunit --coverage-clover='.$xmlfile);
 | 
			
		||||
$xml = simplexml_load_string(file_get_contents($xmlfile));
 | 
			
		||||
unlink($xmlfile);
 | 
			
		||||
printf(
 | 
			
		||||
   '>>> code coverage %s/%s (%s%%)'."\n",
 | 
			
		||||
   $xml->project->metrics['coveredstatements'],
 | 
			
		||||
   $xml->project->metrics['statements'],
 | 
			
		||||
   round(($xml->project->metrics['coveredstatements']/(float)$xml->project->metrics['statements']) * 100.0, 2)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										0
									
								
								scripts/SyncMercurial.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								scripts/SyncMercurial.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
								
								
									
										24
									
								
								scripts/git-post-update
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										24
									
								
								scripts/git-post-update
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
#
 | 
			
		||||
# This hook does only one thing:
 | 
			
		||||
#
 | 
			
		||||
# 1. It calls the gitpostupdate.php script with the current $GIT_DIR 
 | 
			
		||||
#    as argument. The gitpostupdate.php script will then trigger
 | 
			
		||||
#    the 'gitpostupdate.php::run' event with the $GIT_DIR as argument
 | 
			
		||||
#    together with merged $_ENV and $_SERVER array.
 | 
			
		||||
#
 | 
			
		||||
# This hook is normally installed automatically at the creation of your
 | 
			
		||||
# repository if you have everything configured correctly. If you want
 | 
			
		||||
# to enable it later, you need to symlink it as "post-update" in your 
 | 
			
		||||
# $GIT_DIR/hooks folder.
 | 
			
		||||
#
 | 
			
		||||
# www$ chmod +x /home/www/indefero/scripts/git-post-update
 | 
			
		||||
# git$ cd /home/git/repositories/project.git/hooks
 | 
			
		||||
# git$ ln -s /home/www/indefero/scripts/git-post-update post-update
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
SCRIPTDIR=$(dirname $(readlink $0))
 | 
			
		||||
FULL_GIT_DIR=$(cd "$GIT_DIR" && /bin/pwd || "$GIT_DIR")
 | 
			
		||||
PHP_POST_UPDATE=$SCRIPTDIR/gitpostupdate.php
 | 
			
		||||
 | 
			
		||||
echo php $PHP_POST_UPDATE $FULL_GIT_DIR | at now > /dev/null 2>&1
 | 
			
		||||
@@ -22,18 +22,12 @@
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This script is used to control the access to the git repositories
 | 
			
		||||
 * using a restricted shell access.
 | 
			
		||||
 * This script is used to sync the SSH keys, mark the repositories for
 | 
			
		||||
 * export and prune the deleted repositories.
 | 
			
		||||
 *
 | 
			
		||||
 * The only argument must be the login of the user.
 | 
			
		||||
 */
 | 
			
		||||
// Set the include path to have Pluf and IDF in it.
 | 
			
		||||
$indefero_path = dirname(__FILE__).'/../src';
 | 
			
		||||
//$pluf_path = '/path/to/pluf/src';
 | 
			
		||||
set_include_path(get_include_path()
 | 
			
		||||
                 .PATH_SEPARATOR.$indefero_path
 | 
			
		||||
//                 .PATH_SEPARATOR.$pluf_path
 | 
			
		||||
                 );
 | 
			
		||||
 | 
			
		||||
require dirname(__FILE__).'/../src/IDF/conf/path.php';
 | 
			
		||||
require 'Pluf.php';
 | 
			
		||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
 | 
			
		||||
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								scripts/gitpostupdate.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								scripts/gitpostupdate.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008-2010 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This script will send the notifications after a push in your 
 | 
			
		||||
 * repository.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
require dirname(__FILE__).'/../src/IDF/conf/path.php';
 | 
			
		||||
require 'Pluf.php';
 | 
			
		||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
 | 
			
		||||
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * [signal]
 | 
			
		||||
 *
 | 
			
		||||
 * gitpostupdate.php::run
 | 
			
		||||
 *
 | 
			
		||||
 * [sender]
 | 
			
		||||
 *
 | 
			
		||||
 * gitpostupdate.php
 | 
			
		||||
 *
 | 
			
		||||
 * [description]
 | 
			
		||||
 *
 | 
			
		||||
 * This signal allows an application to perform a set of tasks on a
 | 
			
		||||
 * post update of a git repository.
 | 
			
		||||
 *
 | 
			
		||||
 * [parameters]
 | 
			
		||||
 *
 | 
			
		||||
 * array('git_dir' => '/path/to/git/repository.git',
 | 
			
		||||
 *       'env' => array_merge($_ENV, $_SERVER));
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
$params = array('git_dir' => $argv[1],
 | 
			
		||||
                'env' => array_merge($_ENV, $_SERVER));
 | 
			
		||||
Pluf_Log::event(array('gitpostupdate.php', 'Send run signal.', $params));
 | 
			
		||||
Pluf_Signal::send('gitpostupdate.php::run', 'gitpostupdate.php', $params);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -27,13 +27,8 @@
 | 
			
		||||
 *
 | 
			
		||||
 * The only argument must be the login of the user.
 | 
			
		||||
 */
 | 
			
		||||
// Set the include path to have Pluf and IDF in it.
 | 
			
		||||
$indefero_path = dirname(__FILE__).'/../src';
 | 
			
		||||
//$pluf_path = '/path/to/pluf/src';
 | 
			
		||||
set_include_path(get_include_path()
 | 
			
		||||
                 .PATH_SEPARATOR.$indefero_path
 | 
			
		||||
//                 .PATH_SEPARATOR.$pluf_path
 | 
			
		||||
                 );
 | 
			
		||||
 | 
			
		||||
require dirname(__FILE__).'/../src/IDF/conf/path.php';
 | 
			
		||||
require 'Pluf.php';
 | 
			
		||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
 | 
			
		||||
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
# 
 | 
			
		||||
#
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
@@ -23,14 +23,17 @@
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import commands
 | 
			
		||||
import traceback
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
SCRIPTDIR = os.path.abspath(__file__).rsplit(os.path.sep, 1)[0]
 | 
			
		||||
GITSERVEPHP = '%s/gitserve.php' % SCRIPTDIR
 | 
			
		||||
process = subprocess.Popen(['php', GITSERVEPHP, sys.argv[1]],
 | 
			
		||||
                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 | 
			
		||||
output = str.encode("\n").join(process.communicate()).strip()
 | 
			
		||||
status = process.wait()
 | 
			
		||||
 | 
			
		||||
n = len("/gitserve.py")
 | 
			
		||||
GITSERVEPHP = '%s/gitserve.php' % traceback.extract_stack(limit=1)[0][0][0:-n]
 | 
			
		||||
status, output = commands.getstatusoutput('php %s %s' % (GITSERVEPHP, sys.argv[1]))
 | 
			
		||||
if status == 0:
 | 
			
		||||
    os.execvp('git', ['git', 'shell', '-c', output.strip()])
 | 
			
		||||
else:
 | 
			
		||||
    sys.stderr.write("%s\n" % output)
 | 
			
		||||
    sys.stderr.write("%s\n" % output.strip())
 | 
			
		||||
sys.exit(1)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										61
									
								
								scripts/hgchangegroup.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										61
									
								
								scripts/hgchangegroup.php
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
#!/usr/bin/env php
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008-2010 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This script will send the notifications after a push in your 
 | 
			
		||||
 * repository.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
require dirname(__FILE__).'/../src/IDF/conf/path.php';
 | 
			
		||||
require 'Pluf.php';
 | 
			
		||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
 | 
			
		||||
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * [signal]
 | 
			
		||||
 *
 | 
			
		||||
 * hgchangegroup.php::run
 | 
			
		||||
 *
 | 
			
		||||
 * [sender]
 | 
			
		||||
 *
 | 
			
		||||
 * hgchangegroup.php
 | 
			
		||||
 *
 | 
			
		||||
 * [description]
 | 
			
		||||
 *
 | 
			
		||||
 * This signal allows an application to perform a set of tasks on a
 | 
			
		||||
 * group change hook of a Mercurial repository. The rel_dir is a 
 | 
			
		||||
 * relative path to the root of your hg repositories but starting with
 | 
			
		||||
 * a slash.
 | 
			
		||||
 *
 | 
			
		||||
 * [parameters]
 | 
			
		||||
 *
 | 
			
		||||
 * array('rel_dir' => '/relative/path/to/hg/repository',
 | 
			
		||||
 *       'env' => array_merge($_ENV, $_SERVER));
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
$params = array('rel_dir' => $_ENV['PATH_INFO'],
 | 
			
		||||
                'env' => array_merge($_ENV, $_SERVER));
 | 
			
		||||
Pluf_Signal::send('hgchangegroup.php::run', 'hgchangegroup.php', $params);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								scripts/mtn-post-push
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										22
									
								
								scripts/mtn-post-push
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
#
 | 
			
		||||
# This hook informs IDF that new revisions arrived in the database
 | 
			
		||||
# of the specified project.
 | 
			
		||||
#
 | 
			
		||||
# This hook is normally installed automatically at the creation of your
 | 
			
		||||
# repository if you have everything configured correctly. If you want
 | 
			
		||||
# to enable it later, you need to call it into your monotonerc file
 | 
			
		||||
# from the hook "note_netsync_end". (See chapter "Event Notifications
 | 
			
		||||
# and Triggers" on <http://monotone.ca/docs/Hooks.html#Hooks>.)
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
dir=$(dirname "$0")
 | 
			
		||||
res=$(cd "$dir" && /bin/pwd || "$dir")
 | 
			
		||||
SCRIPTDIR="$res/$(readlink $0)"
 | 
			
		||||
PHP_POST_PUSH=$SCRIPTDIR/mtnpostpush.php
 | 
			
		||||
 | 
			
		||||
TMPFILE=$(mktemp /tmp/mtn-post-push.XXXXXX) || exit 1
 | 
			
		||||
while read rev; do echo $rev >> $TMPFILE; done
 | 
			
		||||
 | 
			
		||||
echo php $PHP_POST_PUSH "$1" \< $TMPFILE \&\& rm -f $TMPFILE |\
 | 
			
		||||
    at now > /dev/null 2>&1
 | 
			
		||||
							
								
								
									
										63
									
								
								scripts/mtnpostpush.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								scripts/mtnpostpush.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008-2010 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This script will send the notifications after a push in your 
 | 
			
		||||
 * repository.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
require dirname(__FILE__).'/../src/IDF/conf/path.php';
 | 
			
		||||
require 'Pluf.php';
 | 
			
		||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
 | 
			
		||||
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * [signal]
 | 
			
		||||
 *
 | 
			
		||||
 * mtnpostpush.php::run
 | 
			
		||||
 *
 | 
			
		||||
 * [sender]
 | 
			
		||||
 *
 | 
			
		||||
 * mtnpostpush.php
 | 
			
		||||
 *
 | 
			
		||||
 * [description]
 | 
			
		||||
 *
 | 
			
		||||
 * This signal allows an application to perform a set of tasks
 | 
			
		||||
 * after a push to a monotone repository.
 | 
			
		||||
 *
 | 
			
		||||
 * [parameters]
 | 
			
		||||
 *
 | 
			
		||||
 * array('project' => 'name-of-the-project',
 | 
			
		||||
 *       'revisions' => array('123abc...', '456def...', ...));
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
fwrite(STDERR, "waiting for revisions on STDIN...\n");
 | 
			
		||||
$stdin = file_get_contents('php://stdin');
 | 
			
		||||
 | 
			
		||||
$params = array('project' => $argv[1],
 | 
			
		||||
                'revisions' => explode("\n", chop($stdin)));
 | 
			
		||||
Pluf_Signal::send('mtnpostpush.php::run', 'mtnpostpush.php', $params);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										69
									
								
								scripts/queuecron.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								scripts/queuecron.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This script process the queue of items.
 | 
			
		||||
 *
 | 
			
		||||
 * At the moment the queue is only used for the webhooks, but it would
 | 
			
		||||
 * be good in the future to use it for indexing and email
 | 
			
		||||
 * notifications.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
require dirname(__FILE__).'/../src/IDF/conf/path.php';
 | 
			
		||||
require 'Pluf.php';
 | 
			
		||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
 | 
			
		||||
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
 | 
			
		||||
 | 
			
		||||
#;*/ ::
 | 
			
		||||
$lock_file = Pluf::f('idf_queuecron_lock', 
 | 
			
		||||
                     Pluf::f('tmp_folder', '/tmp').'/queuecron.lock');
 | 
			
		||||
if (file_exists($lock_file)) {
 | 
			
		||||
    Pluf_Log::event(array('queuecron.php', 'skip'));
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
file_put_contents($lock_file, time(), LOCK_EX);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * [signal]
 | 
			
		||||
 *
 | 
			
		||||
 * queuecron.php::run
 | 
			
		||||
 *
 | 
			
		||||
 * [sender]
 | 
			
		||||
 *
 | 
			
		||||
 * queuecron.php
 | 
			
		||||
 *
 | 
			
		||||
 * [description]
 | 
			
		||||
 *
 | 
			
		||||
 * This signal allows an application to perform a set of tasks when
 | 
			
		||||
 * the queue cron job is run. This is done usually every 5 minutes.
 | 
			
		||||
 *
 | 
			
		||||
 * [parameters]
 | 
			
		||||
 *
 | 
			
		||||
 * array()
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
$params = array();
 | 
			
		||||
Pluf_Signal::send('queuecron.php::run', 'queuecron.php', $params);
 | 
			
		||||
 | 
			
		||||
unlink($lock_file);
 | 
			
		||||
							
								
								
									
										24
									
								
								scripts/svn-post-commit
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										24
									
								
								scripts/svn-post-commit
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
#
 | 
			
		||||
# This hook does only one thing:
 | 
			
		||||
#
 | 
			
		||||
# 1. It calls the svnpostcommit.php script with the current repository
 | 
			
		||||
#    and revision as argument. The svnpostcommit.php script will then 
 | 
			
		||||
#    trigger the 'svnpostcommit.php::run' event with the repository path
 | 
			
		||||
#    and revision as arguments together with merged $_ENV and $_SERVER 
 | 
			
		||||
#    array.
 | 
			
		||||
#
 | 
			
		||||
# This hook is normally installed automatically at the creation of your
 | 
			
		||||
# repository if you have everything configured correctly. If you want
 | 
			
		||||
# to enable it later, you need to symlink it as "post-commit" in your 
 | 
			
		||||
# $REPOSITORY/hooks folder.
 | 
			
		||||
#
 | 
			
		||||
# www$ chmod +x /home/www/indefero/scripts/svn-post-commit
 | 
			
		||||
# www$ cd /home/svn/repositories/project/hooks
 | 
			
		||||
# www$ ln -s /home/www/indefero/scripts/svn-post-commit post-commit
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
SCRIPTDIR=$(dirname $(readlink $0))
 | 
			
		||||
PHP_POST_COMMIT=$SCRIPTDIR/svnpostcommit.php
 | 
			
		||||
 | 
			
		||||
echo php $PHP_POST_COMMIT "$1" "$2" | at now > /dev/null 2>&1
 | 
			
		||||
							
								
								
									
										29
									
								
								scripts/svn-post-revprop-change
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										29
									
								
								scripts/svn-post-revprop-change
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
#
 | 
			
		||||
# This hook does only one thing:
 | 
			
		||||
#
 | 
			
		||||
# 1. It calls the svnpostrevpropchange.php script with the current repository
 | 
			
		||||
#    and revision as argument. The svnpostrevpropchange.php script will then 
 | 
			
		||||
#    trigger the 'svnpostrevpropchange.php::run' event with the repository 
 | 
			
		||||
#    path, revision, username, property name and action as arguments together
 | 
			
		||||
#    with merged $_ENV and $_SERVER array.
 | 
			
		||||
#
 | 
			
		||||
# This hook is normally installed automatically at the creation of your
 | 
			
		||||
# repository if you have everything configured correctly. If you want
 | 
			
		||||
# to enable it later, you need to symlink it as "post-revprop-change" in your 
 | 
			
		||||
# $REPOSITORY/hooks folder. It needs to be executable.
 | 
			
		||||
#
 | 
			
		||||
# www$ chmod +x /home/www/indefero/scripts/svn-post-revprop-change
 | 
			
		||||
# www$ cd /home/svn/repositories/project/hooks
 | 
			
		||||
# www$ ln -s /home/www/indefero/scripts/svn-post-revprop-change post-revprop-change
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
SCRIPTDIR=$(dirname $(readlink $0))
 | 
			
		||||
PHP_POST_REVPROP=$SCRIPTDIR/svnpostrevpropchange.php
 | 
			
		||||
 | 
			
		||||
echo php $PHP_POST_REVPROP "$1" "$2" "$3" "$4" "$5" | at now > /dev/null 2>&1
 | 
			
		||||
REPOS="$1"
 | 
			
		||||
REV="$2"
 | 
			
		||||
USER="$3"
 | 
			
		||||
PROPNAME="$4"
 | 
			
		||||
ACTION="$5"
 | 
			
		||||
							
								
								
									
										62
									
								
								scripts/svnpostcommit.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								scripts/svnpostcommit.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008-2010 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This script will send the notifications after a push in your 
 | 
			
		||||
 * repository.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
require dirname(__FILE__).'/../src/IDF/conf/path.php';
 | 
			
		||||
require 'Pluf.php';
 | 
			
		||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
 | 
			
		||||
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * [signal]
 | 
			
		||||
 *
 | 
			
		||||
 * svnpostcommit.php::run
 | 
			
		||||
 *
 | 
			
		||||
 * [sender]
 | 
			
		||||
 *
 | 
			
		||||
 * svnpostcommit.php
 | 
			
		||||
 *
 | 
			
		||||
 * [description]
 | 
			
		||||
 *
 | 
			
		||||
 * This signal allows an application to perform a set of tasks on a
 | 
			
		||||
 * post commit of a subversion repository.
 | 
			
		||||
 *
 | 
			
		||||
 * [parameters]
 | 
			
		||||
 *
 | 
			
		||||
 * array('repo_dir' => '/path/to/subversion/repository',
 | 
			
		||||
 *       'revision' => 1234,
 | 
			
		||||
 *       'env' => array_merge($_ENV, $_SERVER));
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
$params = array('repo_dir' => $argv[1],
 | 
			
		||||
                'revision' => $argv[2],
 | 
			
		||||
                'env' => array_merge($_ENV, $_SERVER));
 | 
			
		||||
Pluf_Log::event(array('svnpostcommit.php', 'Send run signal.', $params));
 | 
			
		||||
Pluf_Signal::send('svnpostcommit.php::run', 'svnpostcommit.php', $params);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										70
									
								
								scripts/svnpostrevpropchange.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								scripts/svnpostrevpropchange.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008-2010 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This script allows you to hook into the post-revprop-change action
 | 
			
		||||
 * of your subversion repository. I am using it to perform near real
 | 
			
		||||
 * time backup of the repositories on indefero.net.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
require dirname(__FILE__).'/../src/IDF/conf/path.php';
 | 
			
		||||
require 'Pluf.php';
 | 
			
		||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
 | 
			
		||||
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * [signal]
 | 
			
		||||
 *
 | 
			
		||||
 * svnpostrevpropchange.php::run
 | 
			
		||||
 *
 | 
			
		||||
 * [sender]
 | 
			
		||||
 *
 | 
			
		||||
 * svnpostrevpropchange.php
 | 
			
		||||
 *
 | 
			
		||||
 * [description]
 | 
			
		||||
 *
 | 
			
		||||
 * This signal allows an application to perform a set of tasks on a
 | 
			
		||||
 * post property revision change of a subversion repository.
 | 
			
		||||
 *
 | 
			
		||||
 * [parameters]
 | 
			
		||||
 *
 | 
			
		||||
 * array('repo_dir' => '/path/to/subversion/repository',
 | 
			
		||||
 *       'revision' => 1234,
 | 
			
		||||
 *       'user' => 'username',
 | 
			
		||||
 *       'propname' => 'changed-property',
 | 
			
		||||
 *       'action' => 'the action M, A or D',
 | 
			
		||||
 *       'env' => array_merge($_ENV, $_SERVER));
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
$params = array('repo_dir' => $argv[1],
 | 
			
		||||
                'revision' => $argv[2],
 | 
			
		||||
                'user' => $argv[3],
 | 
			
		||||
                'propname' => $argv[4],
 | 
			
		||||
                'action' => $argv[5],
 | 
			
		||||
                'env' => array_merge($_ENV, $_SERVER));
 | 
			
		||||
Pluf_Signal::send('svnpostrevpropchange.php::run', 'svnpostrevpropchange.php', 
 | 
			
		||||
                  $params);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
n# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
@@ -34,6 +34,7 @@ Pluf::loadFunction('Pluf_Template_dateAgo');
 | 
			
		||||
class IDF_Commit extends Pluf_Model
 | 
			
		||||
{
 | 
			
		||||
    public $_model = __CLASS__;
 | 
			
		||||
    public $extra = null; /**< Extra data as IDF_Gconf object */
 | 
			
		||||
 | 
			
		||||
    function init()
 | 
			
		||||
    {
 | 
			
		||||
@@ -44,9 +45,9 @@ class IDF_Commit extends Pluf_Model
 | 
			
		||||
                            'id' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  'blank' => true, 
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'project' => 
 | 
			
		||||
                            'project' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'IDF_Project',
 | 
			
		||||
@@ -54,7 +55,7 @@ class IDF_Commit extends Pluf_Model
 | 
			
		||||
                                  'verbose' => __('project'),
 | 
			
		||||
                                  'relate_name' => 'commits',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'author' => 
 | 
			
		||||
                            'author' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
@@ -118,7 +119,7 @@ class IDF_Commit extends Pluf_Model
 | 
			
		||||
    {
 | 
			
		||||
        IDF_Search::index($this);
 | 
			
		||||
        if ($create) {
 | 
			
		||||
            IDF_Timeline::insert($this, $this->get_project(), 
 | 
			
		||||
            IDF_Timeline::insert($this, $this->get_project(),
 | 
			
		||||
                                 $this->get_author(), $this->creation_dtime);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -127,6 +128,7 @@ class IDF_Commit extends Pluf_Model
 | 
			
		||||
    {
 | 
			
		||||
        IDF_Timeline::remove($this);
 | 
			
		||||
        IDF_Search::remove($this);
 | 
			
		||||
        IDF_Gconf::dropForModel($this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -142,51 +144,82 @@ class IDF_Commit extends Pluf_Model
 | 
			
		||||
                            array($project->id, $change->commit));
 | 
			
		||||
        $r = Pluf::factory('IDF_Commit')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
        if ($r->count() > 0) {
 | 
			
		||||
            $r[0]->extra = new IDF_Gconf();
 | 
			
		||||
            $r[0]->extra->serialize = true;
 | 
			
		||||
            $r[0]->extra->setModel($r[0]);
 | 
			
		||||
            $r[0]->extra->initCache();
 | 
			
		||||
            return $r[0];
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($change->full_message)) {
 | 
			
		||||
            $change->full_message = '';
 | 
			
		||||
        }
 | 
			
		||||
        $scm = IDF_Scm::get($project);
 | 
			
		||||
        $commit = new IDF_Commit();
 | 
			
		||||
        $commit->project = $project;
 | 
			
		||||
        $commit->scm_id = $change->commit;
 | 
			
		||||
        $commit->summary = $change->title;
 | 
			
		||||
        $commit->fullmessage = $change->full_message;
 | 
			
		||||
        $commit->summary = self::toUTF8($change->title);
 | 
			
		||||
        $commit->fullmessage = self::toUTF8($change->full_message);
 | 
			
		||||
        $commit->author = $scm->findAuthor($change->author);
 | 
			
		||||
        $commit->origauthor = $change->author;
 | 
			
		||||
        $commit->origauthor = self::toUTF8($change->author);
 | 
			
		||||
        $commit->creation_dtime = $change->date;
 | 
			
		||||
        $commit->create();
 | 
			
		||||
        // We notify the creation of the commit
 | 
			
		||||
        if ('' != $project->getConf()->getVal('source_notification_email', '')) {
 | 
			
		||||
            $context = new Pluf_Template_Context(
 | 
			
		||||
                       array(
 | 
			
		||||
                             'c' => $commit,
 | 
			
		||||
                             'project' => $project,
 | 
			
		||||
                             'url_base' => Pluf::f('url_base'),
 | 
			
		||||
                             )
 | 
			
		||||
                                                     );
 | 
			
		||||
            $tmpl = new Pluf_Template('idf/source/commit-created-email.txt');
 | 
			
		||||
            $text_email = $tmpl->render($context);
 | 
			
		||||
            $email = new Pluf_Mail(Pluf::f('from_email'), 
 | 
			
		||||
                       $project->getConf()->getVal('source_notification_email'),
 | 
			
		||||
                       sprintf(__('New Commit %s - %s (%s)'),
 | 
			
		||||
                               $commit->scm_id, $commit->summary, $project->shortname));
 | 
			
		||||
            $email->addTextMessage($text_email);
 | 
			
		||||
            $email->sendMail();
 | 
			
		||||
        $extra = $scm->getExtraProperties($change);
 | 
			
		||||
        $commit->extra = new IDF_Gconf();
 | 
			
		||||
        $commit->extra->serialize = true; // As we can store arrays
 | 
			
		||||
        $commit->extra->setModel($commit);
 | 
			
		||||
        foreach ($extra as $key => $val) {
 | 
			
		||||
            $commit->extra->setVal($key, $val);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $commit->notify($project->getConf());
 | 
			
		||||
        return $commit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convert encoding to UTF8.
 | 
			
		||||
     *
 | 
			
		||||
     * If an array is given, the encoding is detected only on the
 | 
			
		||||
     * first value and then used to convert all the strings.
 | 
			
		||||
     *
 | 
			
		||||
     * @param mixed String or array of string to be converted
 | 
			
		||||
     * @param bool Returns the encoding together with the converted text (false)
 | 
			
		||||
     * @return mixed String or array of string or array of res + encoding
 | 
			
		||||
     */
 | 
			
		||||
    public static function toUTF8($text, $get_encoding=False)
 | 
			
		||||
    {
 | 
			
		||||
        $enc = 'ASCII, UTF-8, ISO-8859-1, JIS, EUC-JP, SJIS';
 | 
			
		||||
        $ref = $text;
 | 
			
		||||
        if (is_array($text)) {
 | 
			
		||||
            $ref = $text[0];
 | 
			
		||||
        }
 | 
			
		||||
        if (Pluf_Text_UTF8::check($ref)) {
 | 
			
		||||
            return (!$get_encoding) ? $text : array($text, 'UTF-8');
 | 
			
		||||
        }
 | 
			
		||||
        $encoding = mb_detect_encoding($ref, $enc, true);
 | 
			
		||||
        if ($encoding == false) {
 | 
			
		||||
            $encoding = Pluf_Text_UTF8::detect_cyr_charset($ref);
 | 
			
		||||
        }
 | 
			
		||||
        if (is_array($text)) {
 | 
			
		||||
            foreach ($text as $t) {
 | 
			
		||||
                $res[] = mb_convert_encoding($t, 'UTF-8', $encoding);
 | 
			
		||||
            }
 | 
			
		||||
            return (!$get_encoding) ? $res : array($res, $encoding);
 | 
			
		||||
        } else {
 | 
			
		||||
            $res = mb_convert_encoding($text, 'UTF-8', $encoding);
 | 
			
		||||
            return (!$get_encoding) ? $res : array($res, $encoding);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the timeline fragment for the commit.
 | 
			
		||||
     *
 | 
			
		||||
     *
 | 
			
		||||
     * @param Pluf_HTTP_Request 
 | 
			
		||||
     * @param Pluf_HTTP_Request
 | 
			
		||||
     * @return Pluf_Template_SafeString
 | 
			
		||||
     */
 | 
			
		||||
    public function timelineFragment($request)
 | 
			
		||||
    {
 | 
			
		||||
        $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit', 
 | 
			
		||||
                                        array($request->project->shortname, 
 | 
			
		||||
        $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit',
 | 
			
		||||
                                        array($request->project->shortname,
 | 
			
		||||
                                              $this->scm_id));
 | 
			
		||||
        $out = '<tr class="log"><td><a href="'.$url.'">'.
 | 
			
		||||
            Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
 | 
			
		||||
@@ -202,39 +235,103 @@ class IDF_Commit extends Pluf_Model
 | 
			
		||||
</tr>
 | 
			
		||||
<tr class="extra">
 | 
			
		||||
<td colspan="2">
 | 
			
		||||
<div class="helptext right">'.__('Commit').' <a href="'.$url.'" class="mono">'.$this->scm_id.'</a>, '.__('by').' '.$user.'</div></td></tr>'; 
 | 
			
		||||
<div class="helptext right">'.sprintf(__('Commit %s, by %s'), '<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>', $user).'</div></td></tr>';
 | 
			
		||||
        return Pluf_Template::markSafe($out);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the feed fragment for the commit.
 | 
			
		||||
     *
 | 
			
		||||
     * @param Pluf_HTTP_Request 
 | 
			
		||||
     * @param Pluf_HTTP_Request
 | 
			
		||||
     * @return Pluf_Template_SafeString
 | 
			
		||||
     */
 | 
			
		||||
    public function feedFragment($request)
 | 
			
		||||
    {
 | 
			
		||||
        $url = Pluf::f('url_base')
 | 
			
		||||
            .Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit', 
 | 
			
		||||
                                      array($request->project->shortname, 
 | 
			
		||||
            .Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit',
 | 
			
		||||
                                      array($request->project->shortname,
 | 
			
		||||
                                            $this->scm_id));
 | 
			
		||||
        $tag = new IDF_Template_IssueComment();
 | 
			
		||||
        $summary = '<content type="xhtml">'."\n"
 | 
			
		||||
            .'<div xmlns="http://www.w3.org/1999/xhtml">'
 | 
			
		||||
            .$tag->start($this->summary, $request, false, false, false);
 | 
			
		||||
        if ($this->fullmessage) {
 | 
			
		||||
            $summary .= '<br /><br />'
 | 
			
		||||
                .$tag->start($this->fullmessage, $request, false, false, false);
 | 
			
		||||
        }
 | 
			
		||||
        $date = Pluf_Date::gmDateToGmString($this->creation_dtime);
 | 
			
		||||
        $summary .= '</div></content>';
 | 
			
		||||
        $out = '<entry>
 | 
			
		||||
   <title>'.Pluf_esc($request->project->name).': '.__('Commit').' '.$this->scm_id.'</title>
 | 
			
		||||
   <link href="'.$url.'"/>
 | 
			
		||||
   <id>'.$url.'</id>
 | 
			
		||||
   <updated>'.$date.'</updated>'.$summary.'
 | 
			
		||||
 </entry>
 | 
			
		||||
';
 | 
			
		||||
        return $out;
 | 
			
		||||
        $author = ($this->get_author()) ?
 | 
			
		||||
            $this->get_author() : $this->origauthor;
 | 
			
		||||
        $cproject = $this->get_project();
 | 
			
		||||
        $context = new Pluf_Template_Context_Request(
 | 
			
		||||
                       $request,
 | 
			
		||||
                       array(
 | 
			
		||||
                             'c' => $this,
 | 
			
		||||
                             'cproject' => $cproject,
 | 
			
		||||
                             'url' => $url,
 | 
			
		||||
                             'date' => $date,
 | 
			
		||||
                             'author' => $author,
 | 
			
		||||
                             )
 | 
			
		||||
                                             );
 | 
			
		||||
        $tmpl = new Pluf_Template('idf/source/feedfragment.xml');
 | 
			
		||||
        return $tmpl->render($context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notification of change of the object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param IDF_Conf Current configuration
 | 
			
		||||
     * @param bool Creation (true)
 | 
			
		||||
     */
 | 
			
		||||
    public function notify($conf, $create=true)
 | 
			
		||||
    {
 | 
			
		||||
        // Now we add to the queue, soon we will push everything in
 | 
			
		||||
        // the queue, including email notifications and indexing.
 | 
			
		||||
        // Even if the url is empty, we add to the queue as some
 | 
			
		||||
        // plugins may want to do something with this information in
 | 
			
		||||
        // an asynchronous way.
 | 
			
		||||
        $project = $this->get_project();
 | 
			
		||||
        $scm = $project->getConf()->getVal('scm', 'git');
 | 
			
		||||
        $url = str_replace(array('%p', '%r'),
 | 
			
		||||
                           array($project->shortname, $this->scm_id),
 | 
			
		||||
                           $conf->getVal('webhook_url', ''));
 | 
			
		||||
        $payload = array('to_send' => array(
 | 
			
		||||
                                            'project' => $project->shortname,
 | 
			
		||||
                                            'rev' => $this->scm_id,
 | 
			
		||||
                                            'scm' => $scm,
 | 
			
		||||
                                            'summary' => $this->summary,
 | 
			
		||||
                                            'fullmessage' => $this->fullmessage,
 | 
			
		||||
                                            'author' => $this->origauthor,
 | 
			
		||||
                                            'creation_date' => $this->creation_dtime,
 | 
			
		||||
                                            ),
 | 
			
		||||
                         'project_id' => $project->id,
 | 
			
		||||
                         'authkey' => $project->getPostCommitHookKey(),
 | 
			
		||||
                         'url' => $url,
 | 
			
		||||
                         );
 | 
			
		||||
        $item = new IDF_Queue();
 | 
			
		||||
        $item->type = 'new_commit';
 | 
			
		||||
        $item->payload = $payload;
 | 
			
		||||
        $item->create();
 | 
			
		||||
 | 
			
		||||
        if ('' == $conf->getVal('source_notification_email', '')) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $current_locale = Pluf_Translation::getLocale();
 | 
			
		||||
        $langs = Pluf::f('languages', array('en'));
 | 
			
		||||
        Pluf_Translation::loadSetLocale($langs[0]);
 | 
			
		||||
 | 
			
		||||
        $context = new Pluf_Template_Context(
 | 
			
		||||
                       array(
 | 
			
		||||
                             'c' => $this,
 | 
			
		||||
                             'project' => $this->get_project(),
 | 
			
		||||
                             'url_base' => Pluf::f('url_base'),
 | 
			
		||||
                             )
 | 
			
		||||
                                             );
 | 
			
		||||
        $tmpl = new Pluf_Template('idf/source/commit-created-email.txt');
 | 
			
		||||
        $text_email = $tmpl->render($context);
 | 
			
		||||
        $addresses = explode(',', $conf->getVal('source_notification_email'));
 | 
			
		||||
        foreach ($addresses as $address) {
 | 
			
		||||
            $email = new Pluf_Mail(Pluf::f('from_email'),
 | 
			
		||||
                                   $address,
 | 
			
		||||
                                   sprintf(__('New Commit %s - %s (%s)'),
 | 
			
		||||
                                           $this->scm_id, $this->summary,
 | 
			
		||||
                                           $this->get_project()->shortname));
 | 
			
		||||
            $email->addTextMessage($text_email);
 | 
			
		||||
            $email->sendMail();
 | 
			
		||||
        }
 | 
			
		||||
        Pluf_Translation::loadSetLocale($current_locale);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										197
									
								
								src/IDF/Diff.php
									
									
									
									
									
								
							
							
						
						
									
										197
									
								
								src/IDF/Diff.php
									
									
									
									
									
								
							@@ -27,17 +27,17 @@
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Diff
 | 
			
		||||
{
 | 
			
		||||
    public $repo = '';
 | 
			
		||||
    public $diff = '';
 | 
			
		||||
    public $path_strip_level = 0;
 | 
			
		||||
    protected $lines = array();
 | 
			
		||||
 | 
			
		||||
    public $files = array();
 | 
			
		||||
 | 
			
		||||
    public function __construct($diff, $repo='')
 | 
			
		||||
    public function __construct($diff, $path_strip_level = 0)
 | 
			
		||||
    {
 | 
			
		||||
        $this->repo = $repo;
 | 
			
		||||
        $this->diff = $diff;
 | 
			
		||||
        $this->lines = preg_split("/\015\012|\015|\012/", $diff);
 | 
			
		||||
        $this->path_strip_level = $path_strip_level;
 | 
			
		||||
        // this works because in unified diff format even empty lines are
 | 
			
		||||
        // either prefixed with a '+', '-' or ' '
 | 
			
		||||
        $this->lines = preg_split("/\015\012|\015|\012/", $diff, -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function parse()
 | 
			
		||||
@@ -49,90 +49,95 @@ class IDF_Diff
 | 
			
		||||
        $files = array();
 | 
			
		||||
        $indiff = false; // Used to skip the headers in the git patches
 | 
			
		||||
        $i = 0; // Used to skip the end of a git patch with --\nversion number
 | 
			
		||||
        foreach ($this->lines as $line) {
 | 
			
		||||
            $i++;
 | 
			
		||||
            if (0 === strpos($line, '--') and isset($this->lines[$i]) 
 | 
			
		||||
                and preg_match('/^\d+\.\d+\.\d+\.\d+$/', $this->lines[$i])) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            if (0 === strpos($line, 'diff --git a')) {
 | 
			
		||||
                $current_file = self::getFile($line);
 | 
			
		||||
                $files[$current_file] = array();
 | 
			
		||||
                $files[$current_file]['chunks'] = array();
 | 
			
		||||
                $files[$current_file]['chunks_def'] = array();
 | 
			
		||||
                $current_chunk = 0;
 | 
			
		||||
                $indiff = true;
 | 
			
		||||
                continue;
 | 
			
		||||
            } else if (preg_match('#^diff -r [^\s]+ -r [^\s]+ (.+)$#', $line, $matches)) {
 | 
			
		||||
                $current_file = $matches[1];
 | 
			
		||||
                $files[$current_file] = array();
 | 
			
		||||
                $files[$current_file]['chunks'] = array();
 | 
			
		||||
                $files[$current_file]['chunks_def'] = array();
 | 
			
		||||
                $current_chunk = 0;
 | 
			
		||||
                $indiff = true;
 | 
			
		||||
                continue;
 | 
			
		||||
            } else if (0 === strpos($line, 'Index: ')) {
 | 
			
		||||
                $current_file = self::getSvnFile($line);
 | 
			
		||||
                $files[$current_file] = array();
 | 
			
		||||
                $files[$current_file]['chunks'] = array();
 | 
			
		||||
                $files[$current_file]['chunks_def'] = array();
 | 
			
		||||
                $current_chunk = 0;
 | 
			
		||||
                $indiff = true;
 | 
			
		||||
        $diffsize = count($this->lines);
 | 
			
		||||
        while ($i < $diffsize) {
 | 
			
		||||
            // look for the potential beginning of a diff
 | 
			
		||||
            if (substr($this->lines[$i], 0, 4) !== '--- ') {
 | 
			
		||||
                $i++;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (!$indiff) {
 | 
			
		||||
 | 
			
		||||
            // we're inside a diff candiate
 | 
			
		||||
            $oldfileline = $this->lines[$i++];
 | 
			
		||||
            $newfileline = $this->lines[$i++];
 | 
			
		||||
            if (substr($newfileline, 0, 4) !== '+++ ') {
 | 
			
		||||
                // not a valid diff here, move on
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (0 === strpos($line, '@@ ')) {
 | 
			
		||||
                $files[$current_file]['chunks_def'][] = self::getChunk($line);
 | 
			
		||||
 | 
			
		||||
            // use new file name by default
 | 
			
		||||
            preg_match("/^\+\+\+ ([^\t]+)/", $newfileline, $m);
 | 
			
		||||
            $current_file = $m[1];
 | 
			
		||||
            if ($current_file === '/dev/null') {
 | 
			
		||||
                // except if it's /dev/null, use the old one instead
 | 
			
		||||
                // eg. mtn 0.48 and newer
 | 
			
		||||
                preg_match("/^--- ([^\t]+)/", $oldfileline, $m);
 | 
			
		||||
                $current_file = $m[1];
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->path_strip_level > 0) {
 | 
			
		||||
                $fileparts = explode('/', $current_file, $this->path_strip_level+1);
 | 
			
		||||
                $current_file = array_pop($fileparts);
 | 
			
		||||
            }
 | 
			
		||||
            $current_chunk = 0;
 | 
			
		||||
            $files[$current_file] = array();
 | 
			
		||||
            $files[$current_file]['chunks'] = array();
 | 
			
		||||
            $files[$current_file]['chunks_def'] = array();
 | 
			
		||||
 | 
			
		||||
            while ($i < $diffsize && substr($this->lines[$i], 0, 3) === '@@ ') {
 | 
			
		||||
                $elems = preg_match('/@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@.*/',
 | 
			
		||||
                                    $this->lines[$i++], $results);
 | 
			
		||||
                if ($elems != 1) {
 | 
			
		||||
                    // hunk is badly formatted
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                $delstart = $results[1];
 | 
			
		||||
                $dellines = $results[2] === '' ? 1 : $results[2];
 | 
			
		||||
                $addstart = $results[3];
 | 
			
		||||
                $addlines = $results[4] === '' ? 1 : $results[4];
 | 
			
		||||
 | 
			
		||||
                $files[$current_file]['chunks_def'][] = array(
 | 
			
		||||
                    array($delstart, $dellines), array($addstart, $addlines)
 | 
			
		||||
                );
 | 
			
		||||
                $files[$current_file]['chunks'][] = array();
 | 
			
		||||
 | 
			
		||||
                while ($i < $diffsize && ($addlines >= 0 || $dellines >= 0)) {
 | 
			
		||||
                    $linetype = $this->lines[$i] != '' ? $this->lines[$i][0] : ' ';
 | 
			
		||||
                    switch ($linetype) {
 | 
			
		||||
                        case ' ':
 | 
			
		||||
                            $files[$current_file]['chunks'][$current_chunk][] =
 | 
			
		||||
                                array($delstart, $addstart, substr($this->lines[$i++], 1));
 | 
			
		||||
                            $dellines--;
 | 
			
		||||
                            $addlines--;
 | 
			
		||||
                            $delstart++;
 | 
			
		||||
                            $addstart++;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case '+':
 | 
			
		||||
                            $files[$current_file]['chunks'][$current_chunk][] =
 | 
			
		||||
                                array('', $addstart, substr($this->lines[$i++], 1));
 | 
			
		||||
                            $addlines--;
 | 
			
		||||
                            $addstart++;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case '-':
 | 
			
		||||
                            $files[$current_file]['chunks'][$current_chunk][] =
 | 
			
		||||
                                array($delstart, '', substr($this->lines[$i++], 1));
 | 
			
		||||
                            $dellines--;
 | 
			
		||||
                            $delstart++;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case '\\':
 | 
			
		||||
                            // ignore newline handling for now, see issue 636
 | 
			
		||||
                            $i++;
 | 
			
		||||
                            continue;
 | 
			
		||||
                        default:
 | 
			
		||||
                            break 2;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                $current_chunk++;
 | 
			
		||||
                $lline = $files[$current_file]['chunks_def'][$current_chunk-1][0][0];
 | 
			
		||||
                $rline = $files[$current_file]['chunks_def'][$current_chunk-1][1][0];
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (0 === strpos($line, '---') or 0 === strpos($line, '+++')) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (0 === strpos($line, '-')) {
 | 
			
		||||
                $files[$current_file]['chunks'][$current_chunk-1][] = array($lline, '', substr($line, 1));
 | 
			
		||||
                $lline++;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (0 === strpos($line, '+')) {
 | 
			
		||||
                $files[$current_file]['chunks'][$current_chunk-1][] = array('', $rline, substr($line, 1));
 | 
			
		||||
                $rline++;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (0 === strpos($line, ' ')) {
 | 
			
		||||
                $files[$current_file]['chunks'][$current_chunk-1][] = array($lline, $rline, substr($line, 1));
 | 
			
		||||
                $rline++;
 | 
			
		||||
                $lline++;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if ($line == '') {
 | 
			
		||||
                $files[$current_file]['chunks'][$current_chunk-1][] = array($lline, $rline, $line);
 | 
			
		||||
                $rline++;
 | 
			
		||||
                $lline++;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $this->files = $files;
 | 
			
		||||
        return $files;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function getFile($line)
 | 
			
		||||
    {
 | 
			
		||||
        $line = substr(trim($line), 10);
 | 
			
		||||
        $n = (int) strlen($line)/2;
 | 
			
		||||
        return trim(substr($line, 3, $n-3));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function getSvnFile($line)
 | 
			
		||||
    {
 | 
			
		||||
        return substr(trim($line), 7);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return the html version of a parsed diff.
 | 
			
		||||
     */
 | 
			
		||||
@@ -141,8 +146,8 @@ class IDF_Diff
 | 
			
		||||
        $out = '';
 | 
			
		||||
        foreach ($this->files as $filename=>$file) {
 | 
			
		||||
            $pretty = '';
 | 
			
		||||
            $fileinfo = IDF_Views_Source::getMimeType($filename);
 | 
			
		||||
            if (IDF_Views_Source::isSupportedExtension($fileinfo[2])) {
 | 
			
		||||
            $fileinfo = IDF_FileUtil::getMimeType($filename);
 | 
			
		||||
            if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
 | 
			
		||||
                $pretty = ' prettyprint';
 | 
			
		||||
            }
 | 
			
		||||
            $out .= "\n".'<table class="diff" summary="">'."\n";
 | 
			
		||||
@@ -169,7 +174,6 @@ class IDF_Diff
 | 
			
		||||
        return Pluf_Template::markSafe($out);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static function padLine($line)
 | 
			
		||||
    {
 | 
			
		||||
        $line = str_replace("\t", '    ', $line);
 | 
			
		||||
@@ -182,19 +186,6 @@ class IDF_Diff
 | 
			
		||||
        return str_repeat(' ', $i).substr($line, $i);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array array(array(start, n), array(start, n))
 | 
			
		||||
     */
 | 
			
		||||
    public static function getChunk($line)
 | 
			
		||||
    {
 | 
			
		||||
        $elts = split(' ', $line);
 | 
			
		||||
        $res = array();
 | 
			
		||||
        for ($i=1;$i<3;$i++) {
 | 
			
		||||
            $res[] = split(',', trim(substr($elts[$i], 1)));
 | 
			
		||||
        }
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Review patch.
 | 
			
		||||
     *
 | 
			
		||||
@@ -215,14 +206,14 @@ class IDF_Diff
 | 
			
		||||
     * @param int Number of lines before/after the chunk to be displayed (10)
 | 
			
		||||
     * @return Pluf_Template_SafeString The table body
 | 
			
		||||
     */
 | 
			
		||||
    public function fileCompare($orig, $chunks, $filename, $context=10) 
 | 
			
		||||
    public function fileCompare($orig, $chunks, $filename, $context=10)
 | 
			
		||||
    {
 | 
			
		||||
        $orig_lines = preg_split("/\015\012|\015|\012/", $orig);
 | 
			
		||||
        $new_chunks = $this->mergeChunks($orig_lines, $chunks, $context);
 | 
			
		||||
        return $this->renderCompared($new_chunks, $filename);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function mergeChunks($orig_lines, $chunks, $context=10) 
 | 
			
		||||
    public function mergeChunks($orig_lines, $chunks, $context=10)
 | 
			
		||||
    {
 | 
			
		||||
        $spans = array();
 | 
			
		||||
        $new_chunks = array();
 | 
			
		||||
@@ -250,7 +241,7 @@ class IDF_Diff
 | 
			
		||||
                for ($lc=$spans[$i][0];$lc<$chunk[0][0];$lc++) {
 | 
			
		||||
                    $exists = false;
 | 
			
		||||
                    foreach ($chunk_lines as $line) {
 | 
			
		||||
                        if ($lc == $line[0] 
 | 
			
		||||
                        if ($lc == $line[0]
 | 
			
		||||
                            or ($chunk[0][1]-$chunk[0][0]+$lc) == $line[1]) {
 | 
			
		||||
                            $exists = true;
 | 
			
		||||
                            break;
 | 
			
		||||
@@ -259,7 +250,7 @@ class IDF_Diff
 | 
			
		||||
                    if (!$exists) {
 | 
			
		||||
                        $orig = isset($orig_lines[$lc-1]) ? $orig_lines[$lc-1] : '';
 | 
			
		||||
                        $n_chunk[] = array(
 | 
			
		||||
                                           $lc, 
 | 
			
		||||
                                           $lc,
 | 
			
		||||
                                           $chunk[0][1]-$chunk[0][0]+$lc,
 | 
			
		||||
                                           $orig
 | 
			
		||||
                                           );
 | 
			
		||||
@@ -283,7 +274,7 @@ class IDF_Diff
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!$exists) {
 | 
			
		||||
                        $n_chunk[] = array(
 | 
			
		||||
                                           $lc, 
 | 
			
		||||
                                           $lc,
 | 
			
		||||
                                           $lline[1]-$lline[0]+$lc,
 | 
			
		||||
                                           $orig_lines[$lc-1]
 | 
			
		||||
                                           );
 | 
			
		||||
@@ -305,7 +296,7 @@ class IDF_Diff
 | 
			
		||||
                    foreach ($chunk as $line) {
 | 
			
		||||
                        if ($line[0] > $lline[0] or empty($line[0])) {
 | 
			
		||||
                            $nnew_chunks[$i-1][] = $line;
 | 
			
		||||
                        } 
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    $nnew_chunks[] = $chunk;
 | 
			
		||||
@@ -319,12 +310,11 @@ class IDF_Diff
 | 
			
		||||
        return $nnew_chunks;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function renderCompared($chunks, $filename)
 | 
			
		||||
    {
 | 
			
		||||
        $fileinfo = IDF_Views_Source::getMimeType($filename);
 | 
			
		||||
        $fileinfo = IDF_FileUtil::getMimeType($filename);
 | 
			
		||||
        $pretty = '';
 | 
			
		||||
        if (IDF_Views_Source::isSupportedExtension($fileinfo[2])) {
 | 
			
		||||
        if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
 | 
			
		||||
            $pretty = ' prettyprint';
 | 
			
		||||
        }
 | 
			
		||||
        $out = '';
 | 
			
		||||
@@ -353,6 +343,5 @@ class IDF_Diff
 | 
			
		||||
            $i++;
 | 
			
		||||
        }
 | 
			
		||||
        return Pluf_Template::markSafe($out);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										96
									
								
								src/IDF/EmailAddress.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/IDF/EmailAddress.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Storage of Email addresses
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class IDF_EmailAddress extends Pluf_Model
 | 
			
		||||
{
 | 
			
		||||
    public $_model = __CLASS__;
 | 
			
		||||
 | 
			
		||||
    function init()
 | 
			
		||||
    {
 | 
			
		||||
        $this->_a['table'] = 'idf_emailaddresses';
 | 
			
		||||
        $this->_a['model'] = __CLASS__;
 | 
			
		||||
        $this->_a['cols'] = array(
 | 
			
		||||
                             // It is mandatory to have an "id" column.
 | 
			
		||||
                            'id' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  //It is automatically added.
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'user' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('user'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'address' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Email',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('email'),
 | 
			
		||||
                                  'unique' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
        // WARNING: Not using getSqlTable on the Pluf_User object to
 | 
			
		||||
        // avoid recursion.
 | 
			
		||||
        $t_users = $this->_con->pfx.'users';
 | 
			
		||||
        $this->_a['views'] = array(
 | 
			
		||||
                              'join_user' =>
 | 
			
		||||
                              array(
 | 
			
		||||
                                    'join' => 'LEFT JOIN '.$t_users
 | 
			
		||||
                                    .' ON '.$t_users.'.id='.$this->_con->qn('user'),
 | 
			
		||||
                                    'select' => $this->getSelect().', '
 | 
			
		||||
                                    .$t_users.'.login AS login',
 | 
			
		||||
                                    'props' => array('login' => 'login'),
 | 
			
		||||
                                    )
 | 
			
		||||
                                   );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function get_email_addresses_for_user($user)
 | 
			
		||||
    {
 | 
			
		||||
        $addr = $user->get_idf_emailaddress_list();
 | 
			
		||||
        $addr[] = (object)array("address" => $user->email, "id" => -1, "user" => $user);
 | 
			
		||||
        return $addr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function get_user_for_email_address($email)
 | 
			
		||||
    {
 | 
			
		||||
        $sql = new Pluf_SQL('email=%s', array($email));
 | 
			
		||||
        $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
        if ($users->count() > 0) {
 | 
			
		||||
            return $users[0];
 | 
			
		||||
        }
 | 
			
		||||
        $sql = new Pluf_SQL('address=%s', array($email));
 | 
			
		||||
        $matches = Pluf::factory('IDF_EmailAddress')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
        if ($matches->count() > 0) {
 | 
			
		||||
            return new Pluf_User($matches[0]->user);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										165
									
								
								src/IDF/FileUtil.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								src/IDF/FileUtil.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,165 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2010 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * File utilities.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class IDF_FileUtil
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Extension supported by the syntax highlighter.
 | 
			
		||||
     */
 | 
			
		||||
    public static $supportedExtenstions = array(
 | 
			
		||||
              'ascx', 'ashx', 'asmx', 'aspx', 'browser', 'bsh', 'c', 'cl', 'cc',
 | 
			
		||||
              'config', 'cpp', 'cs', 'csh', 'csproj', 'css', 'cv', 'cyc', 'el', 'fs',
 | 
			
		||||
              'h', 'hh', 'hpp', 'hs', 'html', 'html', 'java', 'js', 'lisp', 'master',
 | 
			
		||||
              'pas', 'perl', 'php', 'pl', 'pm', 'py', 'rb', 'scm', 'sh', 'sitemap',
 | 
			
		||||
              'skin', 'sln', 'svc', 'vala', 'vb', 'vbproj', 'vbs', 'wsdl', 'xhtml',
 | 
			
		||||
              'xml', 'xsd', 'xsl', 'xslt');
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Test if an extension is supported by the syntax highlighter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string The extension to test
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public static function isSupportedExtension($extension)
 | 
			
		||||
    {
 | 
			
		||||
        return in_array($extension, self::$supportedExtenstions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a HTML snippet with a line-by-line pre-rendered table
 | 
			
		||||
     * for the given source content
 | 
			
		||||
     *
 | 
			
		||||
     * @param array file information as returned by getMimeType or getMimeTypeFromContent
 | 
			
		||||
     * @param string the content of the file
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public static function highLight($fileinfo, $content)
 | 
			
		||||
    {
 | 
			
		||||
        $pretty = '';
 | 
			
		||||
        if (self::isSupportedExtension($fileinfo[2])) {
 | 
			
		||||
            $pretty = ' prettyprint';
 | 
			
		||||
        }
 | 
			
		||||
        $table = array();
 | 
			
		||||
        $i = 1;
 | 
			
		||||
        foreach (preg_split("/\015\012|\015|\012/", $content) as $line) {
 | 
			
		||||
            $table[] = '<tr class="c-line"><td class="code-lc" id="L'.$i.'"><a href="#L'.$i.'">'.$i.'</a></td>'
 | 
			
		||||
                .'<td class="code mono'.$pretty.'">'.IDF_Diff::padLine(Pluf_esc($line)).'</td></tr>';
 | 
			
		||||
            $i++;
 | 
			
		||||
        }
 | 
			
		||||
        return Pluf_Template::markSafe(implode("\n", $table));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find the mime type of a file.
 | 
			
		||||
     *
 | 
			
		||||
     * Use /etc/mime.types to find the type.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string Filename/Filepath
 | 
			
		||||
     * @param array  Mime type found or 'application/octet-stream', basename, extension
 | 
			
		||||
     */
 | 
			
		||||
    public static function getMimeType($file)
 | 
			
		||||
    {
 | 
			
		||||
        static $mimes = null;
 | 
			
		||||
        if ($mimes == null) {
 | 
			
		||||
            $mimes = array();
 | 
			
		||||
            $src = Pluf::f('idf_mimetypes_db', '/etc/mime.types');
 | 
			
		||||
            $filecontent = @file_get_contents($src);
 | 
			
		||||
            if ($filecontent !== false) {
 | 
			
		||||
                $mimes = preg_split("/\015\012|\015|\012/", $filecontent);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $info = pathinfo($file);
 | 
			
		||||
        if (isset($info['extension'])) {
 | 
			
		||||
            foreach ($mimes as $mime) {
 | 
			
		||||
                if ('#' != substr($mime, 0, 1)) {
 | 
			
		||||
                    $elts = preg_split('/ |\t/', $mime, -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
                    if (in_array($info['extension'], $elts)) {
 | 
			
		||||
                        return array($elts[0], $info['basename'], $info['extension']);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // we consider that if no extension and base name is all
 | 
			
		||||
            // uppercase, then we have a text file.
 | 
			
		||||
            if ($info['basename'] == strtoupper($info['basename'])) {
 | 
			
		||||
                return array('text/plain', $info['basename'], 'txt');
 | 
			
		||||
            }
 | 
			
		||||
            $info['extension'] = 'bin';
 | 
			
		||||
        }
 | 
			
		||||
        return array('application/octet-stream', $info['basename'], $info['extension']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find the mime type of a file using the fileinfo class.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string Filename/Filepath
 | 
			
		||||
     * @param string File content
 | 
			
		||||
     * @return array Mime type found or 'application/octet-stream', basename, extension
 | 
			
		||||
     */
 | 
			
		||||
    public static function getMimeTypeFromContent($file, $filedata)
 | 
			
		||||
    {
 | 
			
		||||
        $info = pathinfo($file);
 | 
			
		||||
        $res = array('application/octet-stream',
 | 
			
		||||
                     $info['basename'],
 | 
			
		||||
                     isset($info['extension']) ? $info['extension'] : 'bin');
 | 
			
		||||
        if (function_exists('finfo_open')) {
 | 
			
		||||
            $finfo = finfo_open(FILEINFO_MIME);
 | 
			
		||||
            $mime = finfo_buffer($finfo, $filedata);
 | 
			
		||||
            finfo_close($finfo);
 | 
			
		||||
            if ($mime) {
 | 
			
		||||
                $res[0] = $mime;
 | 
			
		||||
            }
 | 
			
		||||
            if (!isset($info['extension']) && $mime) {
 | 
			
		||||
                $res[2] = (0 === strpos($mime, 'text/')) ? 'txt' : 'bin';
 | 
			
		||||
            } elseif (!isset($info['extension'])) {
 | 
			
		||||
                $res[2] = 'bin';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find if a given mime type is a text file.
 | 
			
		||||
     * This uses the output of the self::getMimeType function.
 | 
			
		||||
     *
 | 
			
		||||
     * @param array (Mime type, file name, extension)
 | 
			
		||||
     * @return bool Is text
 | 
			
		||||
     */
 | 
			
		||||
    public static function isText($fileinfo)
 | 
			
		||||
    {
 | 
			
		||||
        if (0 === strpos($fileinfo[0], 'text/')) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        $ext = 'mdtext php-dist h gitignore diff patch';
 | 
			
		||||
        $extra_ext = trim(Pluf::f('idf_extra_text_ext', ''));
 | 
			
		||||
        if (!empty($extra_ext))
 | 
			
		||||
            $ext .= ' ' . $extra_ext;
 | 
			
		||||
        $ext = array_merge(self::$supportedExtenstions, explode(' ' , $ext));
 | 
			
		||||
        return (in_array($fileinfo[2], $ext));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -38,6 +38,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
 | 
			
		||||
                         'git' => __('git'),
 | 
			
		||||
                         'svn' => __('Subversion'),
 | 
			
		||||
                         'mercurial' => __('mercurial'),
 | 
			
		||||
                         'mtn' => __('monotone'),
 | 
			
		||||
                         );
 | 
			
		||||
        foreach (Pluf::f('allowed_scm', array()) as $key => $class) {
 | 
			
		||||
            $choices[$options[$key]] = $key;
 | 
			
		||||
@@ -63,6 +64,14 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
 | 
			
		||||
                                            'help_text' => __('It must be unique for each project and composed only of letters, digits and dash (-) like "my-project".'),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['shortdesc'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Short description'),
 | 
			
		||||
                                            'help_text' => __('A one line description of the project.'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget_attrs' => array('size' => '35'),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['scm'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                    array('required' => true,
 | 
			
		||||
                          'label' => __('Repository type'),
 | 
			
		||||
@@ -92,6 +101,14 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
 | 
			
		||||
                          'widget' => 'Pluf_Form_Widget_PasswordInput',
 | 
			
		||||
                          ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['mtn_master_branch'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                    array('required' => false,
 | 
			
		||||
                          'label' => __('Master branch'),
 | 
			
		||||
                          'initial' => '',
 | 
			
		||||
                          'widget_attrs' => array('size' => '35'),
 | 
			
		||||
                          'help_text' => __('This should be a world-wide unique identifier for your project. A reverse DNS notation like "com.my-domain.my-project" is a good idea.'),
 | 
			
		||||
                          ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['owners'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Project owners'),
 | 
			
		||||
@@ -109,6 +126,20 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
 | 
			
		||||
                                                                    'cols' => 40),
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $projects = array('--' => '--');
 | 
			
		||||
        foreach (Pluf::factory('IDF_Project')->getList(array('order' => 'name ASC')) as $proj) {
 | 
			
		||||
            $projects[$proj->name] = $proj->shortname;
 | 
			
		||||
        }
 | 
			
		||||
        $this->fields['template'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Project template'),
 | 
			
		||||
                                            'initial' => '--',
 | 
			
		||||
                                            'help_text' => __('Use the given project to initialize the new project. Access rights and general configuration will be taken from the template project.'),
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_SelectInput',
 | 
			
		||||
                                            'widget_attrs' => array('choices' => $projects),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
@@ -133,6 +164,16 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
 | 
			
		||||
                          'IDF_Form_Admin_ProjectCreate', $params);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_owners()
 | 
			
		||||
    {
 | 
			
		||||
        return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['owners']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_members()
 | 
			
		||||
    {
 | 
			
		||||
        return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['members']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_svn_remote_url()
 | 
			
		||||
    {
 | 
			
		||||
        $this->cleaned_data['svn_remote_url'] = (!empty($this->cleaned_data['svn_remote_url'])) ? $this->cleaned_data['svn_remote_url'] : '';
 | 
			
		||||
@@ -141,14 +182,42 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
 | 
			
		||||
        // we accept only starting with http(s):// to avoid people
 | 
			
		||||
        // trying to access the local filesystem.
 | 
			
		||||
        if (!preg_match('#^(http|https)://#', $url)) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('Only a remote repository available throught http or https are allowed. For example "http://somewhere.com/svn/trunk".'));
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('Only a remote repository available through HTTP or HTTPS is allowed. For example "http://somewhere.com/svn/trunk".'));
 | 
			
		||||
        }
 | 
			
		||||
        return $url;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_mtn_master_branch()
 | 
			
		||||
    {
 | 
			
		||||
        // do not validate, but empty the field if a different
 | 
			
		||||
        // SCM should be used
 | 
			
		||||
        if ($this->cleaned_data['scm'] != 'mtn')
 | 
			
		||||
            return '';
 | 
			
		||||
 | 
			
		||||
        $mtn_master_branch = mb_strtolower($this->cleaned_data['mtn_master_branch']);
 | 
			
		||||
        if (!preg_match('/^([\w\d]+([-][\w\d]+)*)(\.[\w\d]+([-][\w\d]+)*)*$/',
 | 
			
		||||
                        $mtn_master_branch)) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__(
 | 
			
		||||
                'The master branch is empty or contains illegal characters, '.
 | 
			
		||||
                'please use only letters, digits, dashs and dots as separators.'
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $sql = new Pluf_SQL('vkey=%s AND vdesc=%s',
 | 
			
		||||
                            array('mtn_master_branch', $mtn_master_branch));
 | 
			
		||||
        $l = Pluf::factory('IDF_Conf')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
        if ($l->count() > 0) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__(
 | 
			
		||||
                'This master branch is already used. Please select another one.'
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $mtn_master_branch;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_shortname()
 | 
			
		||||
    {
 | 
			
		||||
        $shortname = $this->cleaned_data['shortname'];
 | 
			
		||||
        $shortname = mb_strtolower($this->cleaned_data['shortname']);
 | 
			
		||||
        if (preg_match('/[^\-A-Za-z0-9]/', $shortname)) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('This shortname contains illegal characters, please use only letters, digits and dash (-).'));
 | 
			
		||||
        }
 | 
			
		||||
@@ -174,6 +243,11 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
 | 
			
		||||
                $this->cleaned_data[$key] = '';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->cleaned_data['scm'] != 'mtn') {
 | 
			
		||||
            $this->cleaned_data['mtn_master_branch'] = '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
@@ -207,24 +281,88 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
 | 
			
		||||
        $project = new IDF_Project();
 | 
			
		||||
        $project->name = $this->cleaned_data['name'];
 | 
			
		||||
        $project->shortname = $this->cleaned_data['shortname'];
 | 
			
		||||
        $project->private = $this->cleaned_data['private_project'];
 | 
			
		||||
        $project->description = __('Click on the Administer tab to set the description of your project.');
 | 
			
		||||
        $project->shortdesc = $this->cleaned_data['shortdesc'];
 | 
			
		||||
 | 
			
		||||
        if ($this->cleaned_data['template'] != '--') {
 | 
			
		||||
            // Find the template project
 | 
			
		||||
            $sql = new Pluf_SQL('shortname=%s',
 | 
			
		||||
                                array($this->cleaned_data['template']));
 | 
			
		||||
            $tmpl = Pluf::factory('IDF_Project')->getOne(array('filter' => $sql->gen()));
 | 
			
		||||
            $project->private = $tmpl->private;
 | 
			
		||||
            $project->description = $tmpl->description;
 | 
			
		||||
        } else {
 | 
			
		||||
            $project->private = $this->cleaned_data['private_project'];
 | 
			
		||||
            $project->description = __('Click on the Project Management tab to set the description of your project.');
 | 
			
		||||
        }
 | 
			
		||||
        $project->create();
 | 
			
		||||
        $conf = new IDF_Conf();
 | 
			
		||||
        $conf->setProject($project);
 | 
			
		||||
        $keys = array('scm', 'svn_remote_url', 
 | 
			
		||||
                      'svn_username', 'svn_password');
 | 
			
		||||
        $keys = array('scm', 'svn_remote_url', 'svn_username',
 | 
			
		||||
                      'svn_password', 'mtn_master_branch');
 | 
			
		||||
        foreach ($keys as $key) {
 | 
			
		||||
            $this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ? 
 | 
			
		||||
            $this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ?
 | 
			
		||||
                $this->cleaned_data[$key] : '';
 | 
			
		||||
            $conf->setVal($key, $this->cleaned_data[$key]);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->cleaned_data['template'] != '--') {
 | 
			
		||||
            $tmplconf = new IDF_Conf();
 | 
			
		||||
            $tmplconf->setProject($tmpl);
 | 
			
		||||
            // We need to get all the configuration variables we want from
 | 
			
		||||
            // the old project and put them into the new project.
 | 
			
		||||
            $props = array(
 | 
			
		||||
                           'labels_download_predefined' => IDF_Form_UploadConf::init_predefined,
 | 
			
		||||
                           'labels_download_one_max' => IDF_Form_UploadConf::init_one_max,
 | 
			
		||||
                           'labels_wiki_predefined' => IDF_Form_WikiConf::init_predefined,
 | 
			
		||||
                           'labels_wiki_one_max' => IDF_Form_WikiConf::init_one_max,
 | 
			
		||||
                           'labels_issue_template' => IDF_Form_IssueTrackingConf::init_template,
 | 
			
		||||
                           'labels_issue_open' => IDF_Form_IssueTrackingConf::init_open,
 | 
			
		||||
                           'labels_issue_closed' => IDF_Form_IssueTrackingConf::init_closed,
 | 
			
		||||
                           'labels_issue_predefined' =>  IDF_Form_IssueTrackingConf::init_predefined,
 | 
			
		||||
                           'labels_issue_one_max' => IDF_Form_IssueTrackingConf::init_one_max,
 | 
			
		||||
                           'webhook_url' => '',
 | 
			
		||||
                           'downloads_access_rights' => 'all',
 | 
			
		||||
                           'review_access_rights' => 'all',
 | 
			
		||||
                           'wiki_access_rights' => 'all',
 | 
			
		||||
                           'source_access_rights' => 'all',
 | 
			
		||||
                           'issues_access_rights' => 'all',
 | 
			
		||||
                           'downloads_notification_email' => '',
 | 
			
		||||
                           'review_notification_email' => '',
 | 
			
		||||
                           'wiki_notification_email' => '',
 | 
			
		||||
                           'source_notification_email' => '',
 | 
			
		||||
                           'issues_notification_email' => '',
 | 
			
		||||
                           );
 | 
			
		||||
            foreach ($props as $prop => $def) {
 | 
			
		||||
                $conf->setVal($prop, $tmplconf->getVal($prop, $def));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $project->created();
 | 
			
		||||
        IDF_Form_MembersConf::updateMemberships($project, 
 | 
			
		||||
                                                $this->cleaned_data);
 | 
			
		||||
 | 
			
		||||
        if ($this->cleaned_data['template'] == '--') {
 | 
			
		||||
            IDF_Form_MembersConf::updateMemberships($project,
 | 
			
		||||
                                                    $this->cleaned_data);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Get the membership of the template $tmpl
 | 
			
		||||
            IDF_Form_MembersConf::updateMemberships($project,
 | 
			
		||||
                                                    $tmpl->getMembershipData('string'));
 | 
			
		||||
        }
 | 
			
		||||
        $project->membershipsUpdated();
 | 
			
		||||
        return $project;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check that the template project exists.
 | 
			
		||||
     */
 | 
			
		||||
    public function clean_template()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->cleaned_data['template'] == '--') {
 | 
			
		||||
            return $this->cleaned_data['template'];
 | 
			
		||||
        }
 | 
			
		||||
        $sql = new Pluf_SQL('shortname=%s', array($this->cleaned_data['template']));
 | 
			
		||||
        if (Pluf::factory('IDF_Project')->getOne(array('filter' => $sql->gen())) == null) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('This project is not available.'));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data['template'];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,12 +37,32 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
 | 
			
		||||
    {
 | 
			
		||||
        $this->project = $extra['project'];
 | 
			
		||||
        $members = $this->project->getMembershipData('string');
 | 
			
		||||
        $conf = $this->project->getConf();
 | 
			
		||||
 | 
			
		||||
        $this->fields['name'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Name'),
 | 
			
		||||
                                            'initial' => $this->project->name,
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['shortdesc'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Short description'),
 | 
			
		||||
                                            'help_text' => __('A one line description of the project.'),
 | 
			
		||||
                                            'initial' => $this->project->shortdesc,
 | 
			
		||||
                                            'widget_attrs' => array('size' => '35'),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        if ($this->project->getConf()->getVal('scm') == 'mtn') {
 | 
			
		||||
            $this->fields['mtn_master_branch'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                          array('required' => false,
 | 
			
		||||
                                                'label' => __('Master branch'),
 | 
			
		||||
                                                'initial' => $conf->getVal('mtn_master_branch'),
 | 
			
		||||
                                                'widget_attrs' => array('size' => '35'),
 | 
			
		||||
                                                'help_text' => __('This should be a world-wide unique identifier for your project. A reverse DNS notation like "com.my-domain.my-project" is a good idea.'),
 | 
			
		||||
                                                ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->fields['owners'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Project owners'),
 | 
			
		||||
@@ -61,16 +81,58 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
 | 
			
		||||
                                            ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_mtn_master_branch()
 | 
			
		||||
    {
 | 
			
		||||
        $mtn_master_branch = mb_strtolower($this->cleaned_data['mtn_master_branch']);
 | 
			
		||||
        if (!preg_match('/^([\w\d]+([-][\w\d]+)*)(\.[\w\d]+([-][\w\d]+)*)*$/',
 | 
			
		||||
                        $mtn_master_branch)) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__(
 | 
			
		||||
                'The master branch is empty or contains illegal characters, '.
 | 
			
		||||
                'please use only letters, digits, dashes and dots as separators.'
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $sql = new Pluf_SQL('vkey=%s AND vdesc=%s AND project!=%s',
 | 
			
		||||
                            array('mtn_master_branch', $mtn_master_branch,
 | 
			
		||||
                                  (string)$this->project->id));
 | 
			
		||||
        $l = Pluf::factory('IDF_Conf')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
        if ($l->count() > 0) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__(
 | 
			
		||||
                'This master branch is already used. Please select another one.'
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $mtn_master_branch;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_owners()
 | 
			
		||||
    {
 | 
			
		||||
        return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['owners']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_members()
 | 
			
		||||
    {
 | 
			
		||||
        return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['members']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function save($commit=true)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->isValid()) {
 | 
			
		||||
            throw new Exception(__('Cannot save the model from an invalid form.'));
 | 
			
		||||
        }
 | 
			
		||||
        IDF_Form_MembersConf::updateMemberships($this->project, 
 | 
			
		||||
        IDF_Form_MembersConf::updateMemberships($this->project,
 | 
			
		||||
                                                $this->cleaned_data);
 | 
			
		||||
        $this->project->membershipsUpdated();
 | 
			
		||||
        $this->project->name = $this->cleaned_data['name'];
 | 
			
		||||
        $this->project->shortdesc = $this->cleaned_data['shortdesc'];
 | 
			
		||||
        $this->project->update();
 | 
			
		||||
 | 
			
		||||
        $keys = array('mtn_master_branch');
 | 
			
		||||
        foreach ($keys as $key) {
 | 
			
		||||
            if (!empty($this->cleaned_data[$key])) {
 | 
			
		||||
                $this->project->getConf()->setVal($key, $this->cleaned_data[$key]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										215
									
								
								src/IDF/Form/Admin/UserCreate.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								src/IDF/Form/Admin/UserCreate.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,215 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Allow an admin to create a user.
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Form_Admin_UserCreate extends Pluf_Form
 | 
			
		||||
{
 | 
			
		||||
    public $request = null;
 | 
			
		||||
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
        $this->request = $extra['request'];
 | 
			
		||||
        $this->fields['first_name'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('First name'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
        $this->fields['last_name'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Last name'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 20,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['login'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Login'),
 | 
			
		||||
                                            'max_length' => 15,
 | 
			
		||||
                                            'min_length' => 3,
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'help_text' => __('The login must be between 3 and 15 characters long and contains only letters and digits.'),
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 15,
 | 
			
		||||
                                                       'size' => 10,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['email'] = new Pluf_Form_Field_Email(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Email'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'help_text' => __('Double check the email address as the password is sent directly to the user.'),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['language'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Language'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_SelectInput',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'choices' =>
 | 
			
		||||
                                                       Pluf_L10n::getInstalledLanguages()
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['public_key'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Add a public key'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget_attrs' => array('rows' => 3,
 | 
			
		||||
                                                                    'cols' => 40),
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            'help_text' => __('Paste a SSH or monotone public key. Be careful to not provide your private key here!')
 | 
			
		||||
                                            ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save the model in the database.
 | 
			
		||||
     *
 | 
			
		||||
     * @param bool Commit in the database or not. If not, the object
 | 
			
		||||
     *             is returned but not saved in the database.
 | 
			
		||||
     * @return Object Model with data set from the form.
 | 
			
		||||
     */
 | 
			
		||||
    function save($commit=true)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->isValid()) {
 | 
			
		||||
            throw new Exception(__('Cannot save the model from an invalid form.'));
 | 
			
		||||
        }
 | 
			
		||||
        $password = Pluf_Utils::getPassword();
 | 
			
		||||
        $user = new Pluf_User();
 | 
			
		||||
        $user->setFromFormData($this->cleaned_data);
 | 
			
		||||
        $user->active = true;
 | 
			
		||||
        $user->staff = false;
 | 
			
		||||
        $user->administrator = false;
 | 
			
		||||
        $user->setPassword($password);
 | 
			
		||||
        $user->create();
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
         * Pluf_User::passwordUpdated
 | 
			
		||||
         *
 | 
			
		||||
         * [sender]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Form_Admin_UserCreate
 | 
			
		||||
         *
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal is sent when a user is created
 | 
			
		||||
         * by the staff.
 | 
			
		||||
         *
 | 
			
		||||
         * [parameters]
 | 
			
		||||
         *
 | 
			
		||||
         * array('user' => $user)
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        $params = array('user' => $user);
 | 
			
		||||
        Pluf_Signal::send('Pluf_User::passwordUpdated',
 | 
			
		||||
                          'IDF_Form_Admin_UserCreate', $params);
 | 
			
		||||
        // Create the public key as needed
 | 
			
		||||
        if ('' !== $this->cleaned_data['public_key']) {
 | 
			
		||||
            $key = new IDF_Key();
 | 
			
		||||
            $key->user = $user;
 | 
			
		||||
            $key->content = $this->cleaned_data['public_key'];
 | 
			
		||||
            $key->create();
 | 
			
		||||
        }
 | 
			
		||||
        // Send an email to the user with the password
 | 
			
		||||
        Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
 | 
			
		||||
        $url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::login', array(), array(), false);
 | 
			
		||||
        $context = new Pluf_Template_Context(
 | 
			
		||||
                 array('password' => Pluf_Template::markSafe($password),
 | 
			
		||||
                       'user' => $user,
 | 
			
		||||
                       'url' => Pluf_Template::markSafe($url),
 | 
			
		||||
                       'admin' => $this->request->user,
 | 
			
		||||
                       ));
 | 
			
		||||
        $tmpl = new Pluf_Template('idf/gadmin/users/createuser-email.txt');
 | 
			
		||||
        $text_email = $tmpl->render($context);
 | 
			
		||||
        $email = new Pluf_Mail(Pluf::f('from_email'), $user->email,
 | 
			
		||||
                               __('Your details to access your forge.'));
 | 
			
		||||
        $email->addTextMessage($text_email);
 | 
			
		||||
        $email->sendMail();
 | 
			
		||||
        return $user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clean_last_name()
 | 
			
		||||
    {
 | 
			
		||||
        $last_name = trim($this->cleaned_data['last_name']);
 | 
			
		||||
        if ($last_name == mb_strtoupper($last_name)) {
 | 
			
		||||
            return mb_convert_case(mb_strtolower($last_name),
 | 
			
		||||
                                   MB_CASE_TITLE, 'UTF-8');
 | 
			
		||||
        }
 | 
			
		||||
        return $last_name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clean_first_name()
 | 
			
		||||
    {
 | 
			
		||||
        $first_name = trim($this->cleaned_data['first_name']);
 | 
			
		||||
        if ($first_name == mb_strtoupper($first_name)) {
 | 
			
		||||
            return mb_convert_case(mb_strtolower($first_name),
 | 
			
		||||
                                   MB_CASE_TITLE, 'UTF-8');
 | 
			
		||||
        }
 | 
			
		||||
        return $first_name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clean_email()
 | 
			
		||||
    {
 | 
			
		||||
        $this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email']));
 | 
			
		||||
        $guser = new Pluf_User();
 | 
			
		||||
        $sql = new Pluf_SQL('email=%s', array($this->cleaned_data['email']));
 | 
			
		||||
        if ($guser->getCount(array('filter' => $sql->gen())) > 0) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used.'), $this->cleaned_data['email']));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data['email'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_login()
 | 
			
		||||
    {
 | 
			
		||||
        $this->cleaned_data['login'] = mb_strtolower(trim($this->cleaned_data['login']));
 | 
			
		||||
        if (preg_match('/[^a-z0-9]/', $this->cleaned_data['login'])) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(sprintf(__('The login "%s" can only contain letters and digits.'), $this->cleaned_data['login']));
 | 
			
		||||
        }
 | 
			
		||||
        $guser = new Pluf_User();
 | 
			
		||||
        $sql = new Pluf_SQL('login=%s', $this->cleaned_data['login']);
 | 
			
		||||
        if ($guser->getCount(array('filter' => $sql->gen())) > 0) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(sprintf(__('The login "%s" is already used, please find another one.'), $this->cleaned_data['login']));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data['login'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_public_key()
 | 
			
		||||
    {
 | 
			
		||||
        $this->cleaned_data['public_key'] =
 | 
			
		||||
            IDF_Form_UserAccount::checkPublicKey($this->cleaned_data['public_key']);
 | 
			
		||||
 | 
			
		||||
        return $this->cleaned_data['public_key'];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -24,13 +24,15 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Update user's details.
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Form_Admin_UserUpdate  extends Pluf_Form
 | 
			
		||||
class IDF_Form_Admin_UserUpdate extends Pluf_Form
 | 
			
		||||
{
 | 
			
		||||
    public $user = null;
 | 
			
		||||
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
        $this->user = $extra['user'];
 | 
			
		||||
        $user_data = IDF_UserData::factory($this->user);
 | 
			
		||||
 | 
			
		||||
        $this->fields['first_name'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('First name'),
 | 
			
		||||
@@ -66,7 +68,7 @@ class IDF_Form_Admin_UserUpdate  extends Pluf_Form
 | 
			
		||||
                                            'initial' => $this->user->language,
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_SelectInput',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'choices' => 
 | 
			
		||||
                                                       'choices' =>
 | 
			
		||||
                                                       Pluf_L10n::getInstalledLanguages()
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
@@ -76,7 +78,7 @@ class IDF_Form_Admin_UserUpdate  extends Pluf_Form
 | 
			
		||||
                                            'label' => __('Password'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_PasswordInput',
 | 
			
		||||
                                            'help_text' => Pluf_Template::markSafe(__('Leave blank if you do not want to change the password.').'<br />'.__('The password must be hard for other people to find it, but easy for the user to remember.')),
 | 
			
		||||
                                            'help_text' => Pluf_Template::markSafe(__('Leave blank if you do not want to change the password.').'<br />'.__('The password must be hard for other people to guess, but easy for the user to remember.')),
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
@@ -93,13 +95,73 @@ class IDF_Form_Admin_UserUpdate  extends Pluf_Form
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['description'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Description'),
 | 
			
		||||
                                            'initial' => $user_data->description,
 | 
			
		||||
                                            'widget_attrs' => array('rows' => 3,
 | 
			
		||||
                                                                    'cols' => 40),
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['twitter'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Twitter username'),
 | 
			
		||||
                                            'initial' => $user_data->twitter,
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['public_email'] = new Pluf_Form_Field_Email(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Public email address'),
 | 
			
		||||
                                            'initial' => $user_data->public_email,
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['website'] = new Pluf_Form_Field_Url(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Website URL'),
 | 
			
		||||
                                            'initial' => $user_data->website,
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['custom_avatar'] = new Pluf_Form_Field_File(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Upload custom avatar'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'max_size' => Pluf::f('max_upload_size', 2097152),
 | 
			
		||||
                                            'move_function_params' => array('upload_path' => Pluf::f('upload_path').'/avatars',
 | 
			
		||||
                                                                            'upload_path_create' => true,
 | 
			
		||||
                                                                            'upload_overwrite' => true,
 | 
			
		||||
                                                                            'file_name' => 'user_'.$this->user->id.'_%s'),
 | 
			
		||||
                                            'help_text' => __('An image file with a width and height not larger than 60 pixels (bigger images are scaled down).'),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['remove_custom_avatar'] = new Pluf_Form_Field_Boolean(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Remove custom avatar'),
 | 
			
		||||
                                            'initial' => false,
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_CheckboxInput',
 | 
			
		||||
                                            'widget_attrs' => array(),
 | 
			
		||||
                                            'help_text' => __('Tick this to delete the custom avatar.'),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        if ($extra['request']->user->administrator) {
 | 
			
		||||
            $this->fields['staff'] = new Pluf_Form_Field_Boolean(
 | 
			
		||||
                    array('required' => false,
 | 
			
		||||
                          'label' => __('Staff'),
 | 
			
		||||
                          'initial' => $this->user->staff,
 | 
			
		||||
                          'widget' => 'Pluf_Form_Widget_CheckboxInput',
 | 
			
		||||
                          'help_text' => __('If you give staff rights to a user, you really need to trust him.'),
 | 
			
		||||
                          'help_text' => __('If you give staff rights to a user, you really need to trust them.'),
 | 
			
		||||
                          ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -111,7 +173,7 @@ class IDF_Form_Admin_UserUpdate  extends Pluf_Form
 | 
			
		||||
                          'initial' => $this->user->active,
 | 
			
		||||
                          'widget' => 'Pluf_Form_Widget_CheckboxInput',
 | 
			
		||||
                          'widget_attrs' => $attrs,
 | 
			
		||||
                          'help_text' => __('If the user is not getting the confirmation email or is abusing the system, you can directly enable or disable his account here.'),
 | 
			
		||||
                          'help_text' => __('If the user is not getting the confirmation email or is abusing the system, you can directly enable or disable their account here.'),
 | 
			
		||||
                          ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -136,8 +198,37 @@ class IDF_Form_Admin_UserUpdate  extends Pluf_Form
 | 
			
		||||
            $update_pass = true;
 | 
			
		||||
        }
 | 
			
		||||
        $this->user->setFromFormData($this->cleaned_data);
 | 
			
		||||
 | 
			
		||||
        if ($commit) {
 | 
			
		||||
            $this->user->update();
 | 
			
		||||
 | 
			
		||||
            // FIXME: go the extra mile and check the input lengths for
 | 
			
		||||
            // all fields here!
 | 
			
		||||
            // FIXME: this is all doubled in UserAccount!
 | 
			
		||||
 | 
			
		||||
            $user_data = IDF_UserData::factory($this->user);
 | 
			
		||||
 | 
			
		||||
            // Add or remove avatar - we need to do this here because every
 | 
			
		||||
            // single setter directly leads to a save in the database
 | 
			
		||||
            if ($user_data->avatar != '' &&
 | 
			
		||||
                    ($this->cleaned_data['remove_custom_avatar'] == 1 ||
 | 
			
		||||
                     $this->cleaned_data['custom_avatar'] != '')) {
 | 
			
		||||
                $avatar_path = Pluf::f('upload_path').'/avatars/'.basename($user_data->avatar);
 | 
			
		||||
                if (basename($avatar_path) != '' && is_file($avatar_path)) {
 | 
			
		||||
                    unlink($avatar_path);
 | 
			
		||||
                }
 | 
			
		||||
                $user_data->avatar = '';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ($this->cleaned_data['custom_avatar'] != '') {
 | 
			
		||||
                $user_data->avatar = $this->cleaned_data['custom_avatar'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $user_data->description  = $this->cleaned_data['description'];
 | 
			
		||||
            $user_data->twitter      = $this->cleaned_data['twitter'];
 | 
			
		||||
            $user_data->public_email = $this->cleaned_data['public_email'];
 | 
			
		||||
            $user_data->website      = $this->cleaned_data['website'];
 | 
			
		||||
 | 
			
		||||
            if ($update_pass) {
 | 
			
		||||
                /**
 | 
			
		||||
                 * [signal]
 | 
			
		||||
@@ -170,7 +261,7 @@ class IDF_Form_Admin_UserUpdate  extends Pluf_Form
 | 
			
		||||
    {
 | 
			
		||||
        $last_name = trim($this->cleaned_data['last_name']);
 | 
			
		||||
        if ($last_name == mb_strtoupper($last_name)) {
 | 
			
		||||
            return mb_convert_case(mb_strtolower($last_name), 
 | 
			
		||||
            return mb_convert_case(mb_strtolower($last_name),
 | 
			
		||||
                                   MB_CASE_TITLE, 'UTF-8');
 | 
			
		||||
        }
 | 
			
		||||
        return $last_name;
 | 
			
		||||
@@ -179,9 +270,12 @@ class IDF_Form_Admin_UserUpdate  extends Pluf_Form
 | 
			
		||||
    function clean_first_name()
 | 
			
		||||
    {
 | 
			
		||||
        $first_name = trim($this->cleaned_data['first_name']);
 | 
			
		||||
        if ($first_name == '---') {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('--- is not a valid first name.'));
 | 
			
		||||
        }
 | 
			
		||||
        if ($first_name == mb_strtoupper($first_name)) {
 | 
			
		||||
            return mb_convert_case(mb_strtolower($first_name), 
 | 
			
		||||
                                   MB_CASE_TITLE, 'UTF-8');
 | 
			
		||||
            $first_name = mb_convert_case(mb_strtolower($first_name),
 | 
			
		||||
                                          MB_CASE_TITLE, 'UTF-8');
 | 
			
		||||
        }
 | 
			
		||||
        return $first_name;
 | 
			
		||||
    }
 | 
			
		||||
@@ -198,12 +292,23 @@ class IDF_Form_Admin_UserUpdate  extends Pluf_Form
 | 
			
		||||
        return $email;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clean_custom_avatar()
 | 
			
		||||
    {
 | 
			
		||||
        // Just png, jpeg/jpg or gif
 | 
			
		||||
        if (!preg_match('/\.(png|jpg|jpeg|gif)$/i', $this->cleaned_data['custom_avatar']) &&
 | 
			
		||||
            $this->cleaned_data['custom_avatar'] != '') {
 | 
			
		||||
            @unlink(Pluf::f('upload_path').'/avatars/'.$this->cleaned_data['custom_avatar']);
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('For security reason, you cannot upload a file with this extension.'));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data['custom_avatar'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check to see if the 2 passwords are the same.
 | 
			
		||||
     * Check to see if the two passwords are the same.
 | 
			
		||||
     */
 | 
			
		||||
    public function clean()
 | 
			
		||||
    {
 | 
			
		||||
        if (!isset($this->errors['password']) 
 | 
			
		||||
        if (!isset($this->errors['password'])
 | 
			
		||||
            && !isset($this->errors['password2'])) {
 | 
			
		||||
            $password1 = $this->cleaned_data['password'];
 | 
			
		||||
            $password2 = $this->cleaned_data['password2'];
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								src/IDF/Form/Field/EmailList.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/IDF/Form/Field/EmailList.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2010 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# Plume Framework is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2.1 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# Plume Framework is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU Lesser General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Similar to Pluf_Form_Field_Email, this form field validates one or more
 | 
			
		||||
 * email addresses separated by a comma
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Form_Field_EmailList extends Pluf_Form_Field
 | 
			
		||||
{
 | 
			
		||||
    public $widget = 'Pluf_Form_Widget_TextInput';
 | 
			
		||||
 | 
			
		||||
    public function clean($value)
 | 
			
		||||
    {
 | 
			
		||||
        parent::clean($value);
 | 
			
		||||
        if (in_array($value, $this->empty_values)) {
 | 
			
		||||
            $value = '';
 | 
			
		||||
        }
 | 
			
		||||
        if ($value == '') {
 | 
			
		||||
            return $value;
 | 
			
		||||
        }
 | 
			
		||||
        $emails = preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
        foreach ($emails as $email) {
 | 
			
		||||
            if (!Pluf_Utils::isValidEmail($email)) {
 | 
			
		||||
                throw new Pluf_Form_Invalid(__(
 | 
			
		||||
                    'Please enter one or more valid email addresses.'
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return implode(',', $emails);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -45,6 +45,9 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
            or $this->user->hasPerm('IDF.project-member', $this->project)) {
 | 
			
		||||
            $this->show_full = true;
 | 
			
		||||
        }
 | 
			
		||||
        $contentTemplate = $this->project->getConf()->getVal(
 | 
			
		||||
            'labels_issue_template', IDF_Form_IssueTrackingConf::init_template
 | 
			
		||||
        );
 | 
			
		||||
        $this->fields['summary'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Summary'),
 | 
			
		||||
@@ -57,7 +60,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
        $this->fields['content'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Description'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'initial' => $contentTemplate,
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'cols' => 58,
 | 
			
		||||
@@ -105,14 +108,41 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
            /*
 | 
			
		||||
             * get predefined tags for issues from current project
 | 
			
		||||
             *
 | 
			
		||||
             * first Type:<...> and Priority:<...> will be used
 | 
			
		||||
             *
 | 
			
		||||
             */
 | 
			
		||||
            $predefined = preg_split("/[\r\n]+/", $extra['project']->getConf()->getVal(
 | 
			
		||||
              'labels_issue_predefined'
 | 
			
		||||
            ));
 | 
			
		||||
            $predefined_type = 'Type:Defect';
 | 
			
		||||
            foreach ($predefined as $tag) {
 | 
			
		||||
                if (strpos($tag, 'Type:') === 0) {
 | 
			
		||||
                    $predefined_type = explode('=', $tag, 2);
 | 
			
		||||
                    $predefined_type = trim($predefined_type[0]);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $predefined_priority = 'Priority:Medium';
 | 
			
		||||
            foreach ($predefined as $tag) {
 | 
			
		||||
                if (strpos($tag, 'Priority:') === 0) {
 | 
			
		||||
                    $predefined_priority = explode('=', $tag, 2);
 | 
			
		||||
                    $predefined_priority = trim($predefined_priority[0]);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for ($i=1;$i<7;$i++) {
 | 
			
		||||
                $initial = '';
 | 
			
		||||
                switch ($i) {
 | 
			
		||||
                case 1:
 | 
			
		||||
                    $initial = 'Type:Defect';
 | 
			
		||||
                    $initial = $predefined_type;
 | 
			
		||||
                    break;
 | 
			
		||||
                case 2:
 | 
			
		||||
                    $initial = 'Priority:Medium';
 | 
			
		||||
                    $initial = $predefined_priority;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                $this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
@@ -141,7 +171,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
        $conf = new IDF_Conf();
 | 
			
		||||
        $conf->setProject($this->project);
 | 
			
		||||
        $onemax = array();
 | 
			
		||||
        foreach (split(',', $conf->getVal('labels_issue_one_max', IDF_Form_IssueTrackingConf::init_one_max)) as $class) {
 | 
			
		||||
        foreach (explode(',', $conf->getVal('labels_issue_one_max', IDF_Form_IssueTrackingConf::init_one_max)) as $class) {
 | 
			
		||||
            if (trim($class) != '') {
 | 
			
		||||
                $onemax[] = mb_strtolower(trim($class));
 | 
			
		||||
            }
 | 
			
		||||
@@ -276,6 +306,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
        $comment->create();
 | 
			
		||||
        // If we have a file, create the IDF_IssueFile and attach
 | 
			
		||||
        // it to the comment.
 | 
			
		||||
        $created_files = array();
 | 
			
		||||
        for ($i=1;$i<4;$i++) {
 | 
			
		||||
            if ($this->cleaned_data['attachment'.$i]) {
 | 
			
		||||
                $file = new IDF_IssueFile();
 | 
			
		||||
@@ -283,8 +314,36 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
                $file->submitter = $this->user;
 | 
			
		||||
                $file->comment = $comment;
 | 
			
		||||
                $file->create();
 | 
			
		||||
                $created_files[] = $file;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Issue::create
 | 
			
		||||
         *
 | 
			
		||||
         * [sender]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Form_IssueCreate
 | 
			
		||||
         *
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal allows an application to perform a set of tasks
 | 
			
		||||
         * just after the creation of an issue. The comment contains
 | 
			
		||||
         * the description of the issue.
 | 
			
		||||
         *
 | 
			
		||||
         * [parameters]
 | 
			
		||||
         *
 | 
			
		||||
         * array('issue' => $issue,
 | 
			
		||||
         *       'comment' => $comment,
 | 
			
		||||
         *       'files' => $attached_files);
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        $params = array('issue' => $issue,
 | 
			
		||||
                        'comment' => $comment,
 | 
			
		||||
                        'files' => $created_files);
 | 
			
		||||
        Pluf_Signal::send('IDF_Issue::create', 'IDF_Form_IssueCreate',
 | 
			
		||||
                          $params);
 | 
			
		||||
        return $issue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,15 @@ class IDF_Form_IssueTrackingConf extends Pluf_Form
 | 
			
		||||
     * Defined as constants to easily access the value in the
 | 
			
		||||
     * IssueUpdate/Create form in the case nothing is in the db yet.
 | 
			
		||||
     */
 | 
			
		||||
    const init_template = 'Steps to reproduce the problem:
 | 
			
		||||
1.
 | 
			
		||||
2.
 | 
			
		||||
3.
 | 
			
		||||
 | 
			
		||||
Expected result:
 | 
			
		||||
 | 
			
		||||
Actual result:
 | 
			
		||||
';
 | 
			
		||||
    const init_open = 'New                 = Issue has not had initial review yet
 | 
			
		||||
Accepted            = Problem reproduced / Need acknowledged
 | 
			
		||||
Started             = Work on this issue has begun';
 | 
			
		||||
@@ -66,6 +75,15 @@ Maintainability      = Hinders future changes';
 | 
			
		||||
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
        $this->fields['labels_issue_template'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Define an issue template to hint to the reporter to provide certain information'),
 | 
			
		||||
                                            'initial' => self::init_template,
 | 
			
		||||
                                            'widget_attrs' => array('rows' => 7,
 | 
			
		||||
                                                                    'cols' => 75),
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['labels_issue_open'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Open issue status values'),
 | 
			
		||||
@@ -87,6 +105,7 @@ Maintainability      = Hinders future changes';
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Predefined issue labels'),
 | 
			
		||||
                                            'initial' => self::init_predefined,
 | 
			
		||||
                                            'help_text' => __('The first "Type:" and "Priority:" entries found in this list are automatically chosen as defaults for new issues.'),
 | 
			
		||||
                                            'widget_attrs' => array('rows' => 7,
 | 
			
		||||
                                                                    'cols' => 75),
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
@@ -99,8 +118,6 @@ Maintainability      = Hinders future changes';
 | 
			
		||||
                                            'widget_attrs' => array('size' => 60),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -305,6 +305,7 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate
 | 
			
		||||
            $this->issue->submitter != $this->user->id) {
 | 
			
		||||
            $this->issue->setAssoc($this->user); // interested user.
 | 
			
		||||
        }
 | 
			
		||||
        $attached_files = array();
 | 
			
		||||
        for ($i=1;$i<4;$i++) {
 | 
			
		||||
            if ($this->cleaned_data['attachment'.$i]) {
 | 
			
		||||
                $file = new IDF_IssueFile();
 | 
			
		||||
@@ -312,8 +313,36 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate
 | 
			
		||||
                $file->submitter = $this->user;
 | 
			
		||||
                $file->comment = $comment;
 | 
			
		||||
                $file->create();
 | 
			
		||||
                $attached_files[] = $file;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Issue::update
 | 
			
		||||
         *
 | 
			
		||||
         * [sender]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Form_IssueUpdate
 | 
			
		||||
         *
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal allows an application to perform a set of tasks
 | 
			
		||||
         * just after the update of an issue.
 | 
			
		||||
         *
 | 
			
		||||
         * [parameters]
 | 
			
		||||
         *
 | 
			
		||||
         * array('issue' => $issue,
 | 
			
		||||
         *       'comment' => $comment,
 | 
			
		||||
         *       'files' => $attached_files);
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        $params = array('issue' => $this->issue,
 | 
			
		||||
                        'comment' => $comment,
 | 
			
		||||
                        'files' => $attached_files);
 | 
			
		||||
        Pluf_Signal::send('IDF_Issue::update', 'IDF_Form_IssueUpdate',
 | 
			
		||||
                          $params);
 | 
			
		||||
 | 
			
		||||
        return $this->issue;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,45 @@ class IDF_Form_MembersConf extends Pluf_Form
 | 
			
		||||
        $this->project->membershipsUpdated();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_owners()
 | 
			
		||||
    {
 | 
			
		||||
        return self::checkBadLogins($this->cleaned_data['owners']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_members()
 | 
			
		||||
    {
 | 
			
		||||
        return self::checkBadLogins($this->cleaned_data['members']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * From the input, find the bad logins.
 | 
			
		||||
     *
 | 
			
		||||
     * @throws Pluf_Form_Invalid exception when bad logins are found
 | 
			
		||||
     * @param string Comma, new line delimited list of logins
 | 
			
		||||
     * @return string Comma, new line delimited list of logins
 | 
			
		||||
     */
 | 
			
		||||
    public static function checkBadLogins($logins)
 | 
			
		||||
    {
 | 
			
		||||
        $bad = array();
 | 
			
		||||
        foreach (preg_split("/\015\012|\015|\012|\,/", $logins, -1, PREG_SPLIT_NO_EMPTY) as $login) {
 | 
			
		||||
            $sql = new Pluf_SQL('login=%s', array(trim($login)));
 | 
			
		||||
            try {
 | 
			
		||||
                $user = Pluf::factory('Pluf_User')->getOne(array('filter'=>$sql->gen()));
 | 
			
		||||
                if (null == $user) {
 | 
			
		||||
                    $bad[] = $login;
 | 
			
		||||
                }
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                $bad[] = $login;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $n = count($bad);
 | 
			
		||||
        if ($n) {
 | 
			
		||||
            $badlogins = Pluf_esc(implode(', ', $bad));
 | 
			
		||||
            throw new Pluf_Form_Invalid(sprintf(_n('The following login is invalid: %s.', 'The following logins are invalid: %s.', $n), $badlogins));
 | 
			
		||||
        }
 | 
			
		||||
        return $logins;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The update of the memberships is done in different places. This
 | 
			
		||||
     * avoids duplicating code.
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,21 @@ class IDF_Form_Password extends Pluf_Form
 | 
			
		||||
        if ($users->count() == 0) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('Sorry, we cannot find a user with this email address or login. Feel free to try again.'));
 | 
			
		||||
        }
 | 
			
		||||
        $ok = false;
 | 
			
		||||
        foreach ($users as $user) {
 | 
			
		||||
            if ($user->active) {
 | 
			
		||||
                $ok = true;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (!$user->active and $user->first_name == '---') {
 | 
			
		||||
                $ok = true;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $ok = false; // This ensures an all or nothing ok.
 | 
			
		||||
        }
 | 
			
		||||
        if (!$ok) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('Sorry, we cannot find a user with this email address or login. Feel free to try again.'));
 | 
			
		||||
        }
 | 
			
		||||
        return $account;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -64,23 +79,34 @@ class IDF_Form_Password extends Pluf_Form
 | 
			
		||||
        $sql = new Pluf_SQL('email=%s OR login=%s',
 | 
			
		||||
                            array($account, $account));
 | 
			
		||||
        $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
 | 
			
		||||
        $return_url = '';
 | 
			
		||||
        foreach ($users as $user) {
 | 
			
		||||
            $tmpl = new Pluf_Template('idf/user/passrecovery-email.txt');
 | 
			
		||||
            $cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
 | 
			
		||||
            $code = trim($cr->encrypt($user->email.':'.$user->id.':'.time()), 
 | 
			
		||||
                         '~');
 | 
			
		||||
            $code = substr(md5(Pluf::f('secret_key').$code), 0, 2).$code;
 | 
			
		||||
            $url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecovery', array($code), array(), false);
 | 
			
		||||
            $urlic = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryInputCode', array(), array(), false);
 | 
			
		||||
            $context = new Pluf_Template_Context(array('url' => Pluf_Template::markSafe($url),
 | 
			
		||||
                                                       'urlik' => Pluf_Template::markSafe($urlic),
 | 
			
		||||
                                                       'user' => Pluf_Template::markSafe($user),
 | 
			
		||||
                                                       'key' => Pluf_Template::markSafe($code)));
 | 
			
		||||
            $email = new Pluf_Mail(Pluf::f('from_email'), $user->email,
 | 
			
		||||
                                   __('Password Recovery - InDefero'));
 | 
			
		||||
            $email->setReturnPath(Pluf::f('bounce_email', Pluf::f('from_email')));
 | 
			
		||||
            $email->addTextMessage($tmpl->render($context));
 | 
			
		||||
            $email->sendMail();
 | 
			
		||||
            if ($user->active) {
 | 
			
		||||
                $return_url = Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryInputCode');
 | 
			
		||||
                $tmpl = new Pluf_Template('idf/user/passrecovery-email.txt');
 | 
			
		||||
                $cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
 | 
			
		||||
                $code = trim($cr->encrypt($user->email.':'.$user->id.':'.time().':primary'), 
 | 
			
		||||
                             '~');
 | 
			
		||||
                $code = substr(md5(Pluf::f('secret_key').$code), 0, 2).$code;
 | 
			
		||||
                $url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecovery', array($code), array(), false);
 | 
			
		||||
                $urlic = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryInputCode', array(), array(), false);
 | 
			
		||||
                $context = new Pluf_Template_Context(
 | 
			
		||||
                         array('url' => Pluf_Template::markSafe($url),
 | 
			
		||||
                               'urlik' => Pluf_Template::markSafe($urlic),
 | 
			
		||||
                               'user' => Pluf_Template::markSafe($user),
 | 
			
		||||
                               'key' => Pluf_Template::markSafe($code)));
 | 
			
		||||
                $email = new Pluf_Mail(Pluf::f('from_email'), $user->email,
 | 
			
		||||
                                       __('Password Recovery - InDefero'));
 | 
			
		||||
                $email->setReturnPath(Pluf::f('bounce_email', Pluf::f('from_email')));
 | 
			
		||||
                $email->addTextMessage($tmpl->render($context));
 | 
			
		||||
                $email->sendMail();
 | 
			
		||||
            }
 | 
			
		||||
            if (!$user->active and $user->first_name == '---') {
 | 
			
		||||
                $return_url = Pluf_HTTP_URL_urlForView('IDF_Views::registerInputKey');
 | 
			
		||||
                IDF_Form_Register::sendVerificationEmail($user);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $return_url;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -95,6 +95,10 @@ class IDF_Form_PasswordInputKey extends Pluf_Form
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        $cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
 | 
			
		||||
        return split(':', $cr->decrypt($encrypted), 3);
 | 
			
		||||
        $f = explode(':', $cr->decrypt($encrypted), 3);
 | 
			
		||||
        if (count($f) != 3) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return $f;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,9 @@ class IDF_Form_PasswordReset extends Pluf_Form
 | 
			
		||||
        if ($this->cleaned_data['password'] != $this->cleaned_data['password2']) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('The two passwords must be the same.'));
 | 
			
		||||
        }
 | 
			
		||||
        if (!$this->user->active) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('This account is not active. Please contact the forge administrator to activate it.'));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										140
									
								
								src/IDF/Form/ProjectConf.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/IDF/Form/ProjectConf.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright(C) 2008-2011 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
#(at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Configuration of the project.
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Form_ProjectConf extends Pluf_Form
 | 
			
		||||
{
 | 
			
		||||
    public $project = null;
 | 
			
		||||
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
        $this->project = $extra['project'];
 | 
			
		||||
 | 
			
		||||
        // Basic part
 | 
			
		||||
        $this->fields['name'] = new Pluf_Form_Field_Varchar(array('required' => true,
 | 
			
		||||
                                                                  'label' => __('Name'),
 | 
			
		||||
                                                                  'initial' => $this->project->name,
 | 
			
		||||
                                                                 ));
 | 
			
		||||
        $this->fields['shortdesc'] = new Pluf_Form_Field_Varchar(array('required' => true,
 | 
			
		||||
                                                                       'label' => __('Short Description'),
 | 
			
		||||
                                                                       'initial' => $this->project->shortdesc,
 | 
			
		||||
                                                                       'widget_attrs' => array('size' => '68'),
 | 
			
		||||
                                                                      ));
 | 
			
		||||
        $this->fields['description'] = new Pluf_Form_Field_Varchar(array('required' => true,
 | 
			
		||||
                                                                         'label' => __('Description'),
 | 
			
		||||
                                                                         'initial' => $this->project->description,
 | 
			
		||||
                                                                         'widget_attrs' => array('cols' => 68,
 | 
			
		||||
                                                                                                 'rows' => 26,
 | 
			
		||||
                                                                                                ),
 | 
			
		||||
                                                                         'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                                                        ));
 | 
			
		||||
                                                                        
 | 
			
		||||
        // Logo part
 | 
			
		||||
        $upload_path = Pluf::f('upload_path', false);
 | 
			
		||||
        if (false === $upload_path) {
 | 
			
		||||
            throw new Pluf_Exception_SettingError(__('The "upload_path" configuration variable was not set.'));
 | 
			
		||||
        }
 | 
			
		||||
        $upload_path .= '/' . $this->project->shortname;
 | 
			
		||||
        $filename = '/%s'; 
 | 
			
		||||
        $this->fields['logo'] = new Pluf_Form_Field_File(array('required' => false,
 | 
			
		||||
                                                         'label' => __('Update the logo'),
 | 
			
		||||
                                                         'initial' => '',
 | 
			
		||||
                                                         'help_text' => __('The logo must be a picture with a size of 32 by 32.'),
 | 
			
		||||
                                                         'max_size' => Pluf::f('max_upload_size', 5 * 1024),
 | 
			
		||||
                                                         'move_function_params' => 
 | 
			
		||||
                                                         array('upload_path' => $upload_path,
 | 
			
		||||
                                                               'upload_path_create' => true,
 | 
			
		||||
                                                               'file_name' => $filename,
 | 
			
		||||
                                                               )
 | 
			
		||||
                                                         ));
 | 
			
		||||
                                                         
 | 
			
		||||
        $this->fields['logo_remove'] = new Pluf_Form_Field_Boolean(array('required' => false,
 | 
			
		||||
                                                                         'label' => __('Remove the current logo'),
 | 
			
		||||
                                                                         'initial' => false,
 | 
			
		||||
                                                                         'widget' => 'Pluf_Form_Widget_CheckboxInput',
 | 
			
		||||
                                                                         ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * If we have uploaded a file, but the form failed remove it.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    function failed()
 | 
			
		||||
    {
 | 
			
		||||
        if (!empty($this->cleaned_data['logo']) 
 | 
			
		||||
            && file_exists(Pluf::f('upload_path').'/'.$this->cleaned_data['logo'])) {
 | 
			
		||||
            unlink(Pluf::f('upload_path').'/'.$this->cleaned_data['logo']);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function clean()
 | 
			
		||||
    {
 | 
			
		||||
        if (!isset($this->cleaned_data['logo_remove'])) {
 | 
			
		||||
            $this->cleaned_data['logo_remove'] = false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $this->cleaned_data;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function clean_logo()
 | 
			
		||||
    {
 | 
			
		||||
        if (empty($this->cleaned_data['logo'])) {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $meta = getimagesize(Pluf::f('upload_path') . '/' . $this->project->shortname . $this->cleaned_data['logo']);
 | 
			
		||||
        
 | 
			
		||||
        if ($meta === false) {
 | 
			
		||||
            throw new Pluf_Form_Invalid("Could not determine the size of the uploaded picture.");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($meta[0] !== 32 || $meta[1] !== 32) {
 | 
			
		||||
            throw new Pluf_Form_Invalid("The picture must have a size of 32 by 32.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->cleaned_data['logo'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function save($commit=true)
 | 
			
		||||
    {
 | 
			
		||||
        $conf = $this->project->getConf();
 | 
			
		||||
        
 | 
			
		||||
        // Basic part
 | 
			
		||||
        $this->project->name = $this->cleaned_data['name'];
 | 
			
		||||
        $this->project->shortdesc = $this->cleaned_data['shortdesc'];
 | 
			
		||||
        $this->project->description = $this->cleaned_data['description'];
 | 
			
		||||
        $this->project->update();
 | 
			
		||||
        
 | 
			
		||||
        // Logo part
 | 
			
		||||
        if ($this->cleaned_data['logo'] !== "") {
 | 
			
		||||
            $conf->setVal('logo', $this->cleaned_data['logo']);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->cleaned_data['logo_remove'] === true) {
 | 
			
		||||
            @unlink(Pluf::f('upload_path') . '/' . $this->project->shortname . $conf->getVal('logo'));
 | 
			
		||||
            $conf->delVal('logo');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -42,7 +42,7 @@ class IDF_Form_Register extends Pluf_Form
 | 
			
		||||
                                            'max_length' => 15,
 | 
			
		||||
                                            'min_length' => 3,
 | 
			
		||||
                                            'initial' => $login,
 | 
			
		||||
                                            'help_text' => __('The login must be between 3 and 15 characters long and contains only letters and digits.'),
 | 
			
		||||
                                            'help_text' => __('The login must be between 3 and 15 characters long and contain only letters and digits.'),
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 15,
 | 
			
		||||
                                                       'size' => 10,
 | 
			
		||||
@@ -52,7 +52,7 @@ class IDF_Form_Register extends Pluf_Form
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Your email'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'help_text' => __('We will never send you any unsolicited emails. We hate spams too!'),
 | 
			
		||||
                                            'help_text' => __('We will never send you any unsolicited emails. We hate spam too!'),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['terms'] = new Pluf_Form_Field_Boolean(
 | 
			
		||||
@@ -93,10 +93,8 @@ class IDF_Form_Register extends Pluf_Form
 | 
			
		||||
    function clean_email()
 | 
			
		||||
    {
 | 
			
		||||
        $this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email']));
 | 
			
		||||
        $guser = new Pluf_User();
 | 
			
		||||
        $sql = new Pluf_SQL('email=%s', $this->cleaned_data['email']);
 | 
			
		||||
        if ($guser->getCount(array('filter' => $sql->gen())) > 0) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used. If you need, click on the help link to recover your password.'), $this->cleaned_data['email']));
 | 
			
		||||
        if (Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($this->cleaned_data['email']) != null) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used. If you need to, click on the help link to recover your password.'), $this->cleaned_data['email']));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data['email'];
 | 
			
		||||
    }
 | 
			
		||||
@@ -124,8 +122,14 @@ class IDF_Form_Register extends Pluf_Form
 | 
			
		||||
        $user->language = $this->request->language_code;
 | 
			
		||||
        $user->active = false;
 | 
			
		||||
        $user->create();
 | 
			
		||||
        $from_email = Pluf::f('from_email');
 | 
			
		||||
        self::sendVerificationEmail($user);
 | 
			
		||||
        return $user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function sendVerificationEmail($user)
 | 
			
		||||
    {
 | 
			
		||||
        Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
 | 
			
		||||
        $from_email = Pluf::f('from_email');
 | 
			
		||||
        $cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
 | 
			
		||||
        $encrypted = trim($cr->encrypt($user->email.':'.$user->id), '~');
 | 
			
		||||
        $key = substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted;
 | 
			
		||||
@@ -144,6 +148,5 @@ class IDF_Form_Register extends Pluf_Form
 | 
			
		||||
                               __('Confirm the creation of your account.'));
 | 
			
		||||
        $email->addTextMessage($text_email);
 | 
			
		||||
        $email->sendMail();
 | 
			
		||||
        return $user;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ class IDF_Form_RegisterConfirmation extends Pluf_Form
 | 
			
		||||
                                            'label' => __('Your password'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_PasswordInput',
 | 
			
		||||
                                            'help_text' => __('Your password must be hard for other people to find it, but easy for you to remember.'),
 | 
			
		||||
                                            'help_text' => __('Your password must be hard for other people to guess, but easy for you to remember.'),
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
 
 | 
			
		||||
@@ -91,6 +91,6 @@ class IDF_Form_RegisterInputKey extends Pluf_Form
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        $cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
 | 
			
		||||
        return split(':', $cr->decrypt($encrypted), 2);
 | 
			
		||||
        return explode(':', $cr->decrypt($encrypted), 2);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,203 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Create a new documentation page.
 | 
			
		||||
 *
 | 
			
		||||
 * This create a new page and the corresponding revision.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Form_WikiCreate extends Pluf_Form
 | 
			
		||||
{
 | 
			
		||||
    public $user = null;
 | 
			
		||||
    public $project = null;
 | 
			
		||||
    public $show_full = false;
 | 
			
		||||
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
        $initial = __('# Introduction
 | 
			
		||||
 | 
			
		||||
Add your content here.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Details
 | 
			
		||||
 | 
			
		||||
Add your content here. Format your content with:
 | 
			
		||||
 | 
			
		||||
* Text in **bold** or *italic*.
 | 
			
		||||
* Headings, paragraphs, and lists.
 | 
			
		||||
* Links to other [[WikiPage]].
 | 
			
		||||
');
 | 
			
		||||
        $this->user = $extra['user'];
 | 
			
		||||
        $this->project = $extra['project'];
 | 
			
		||||
        if ($this->user->hasPerm('IDF.project-owner', $this->project)
 | 
			
		||||
            or $this->user->hasPerm('IDF.project-member', $this->project)) {
 | 
			
		||||
            $this->show_full = true;
 | 
			
		||||
        }
 | 
			
		||||
        $this->fields['title'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Page title'),
 | 
			
		||||
                                            'initial' => __('PageName'),
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 200,
 | 
			
		||||
                                                       'size' => 67,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            'help_text' => __('The page name must contains only letters, digits and the dash (-) character.'),
 | 
			
		||||
                                            ));
 | 
			
		||||
        $this->fields['summary'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Description'),
 | 
			
		||||
                                            'help_text' => __('This one line description is displayed in the list of pages.'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 200,
 | 
			
		||||
                                                       'size' => 67,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
        $this->fields['content'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Content'),
 | 
			
		||||
                                            'initial' => $initial,
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'cols' => 58,
 | 
			
		||||
                                                       'rows' => 26,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        if ($this->show_full) {
 | 
			
		||||
            for ($i=1;$i<4;$i++) {
 | 
			
		||||
                $this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                            array('required' => false,
 | 
			
		||||
                                                  'label' => __('Labels'),
 | 
			
		||||
                                                  'initial' => '',
 | 
			
		||||
                                                  'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 20,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                                  ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_title()
 | 
			
		||||
    {
 | 
			
		||||
        $title = $this->cleaned_data['title'];
 | 
			
		||||
        if (preg_match('/[^a-zA-Z0-9\-]/', $title)) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('The title contains invalid characters.'));
 | 
			
		||||
        }
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND title=%s', 
 | 
			
		||||
                            array($this->project->id, $title));
 | 
			
		||||
        $pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
        if ($pages->count() > 0) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('A page with this title already exists.'));
 | 
			
		||||
        }
 | 
			
		||||
        return $title;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate the interconnection in the form.
 | 
			
		||||
     */
 | 
			
		||||
    public function clean()
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->show_full) {
 | 
			
		||||
            return $this->cleaned_data;
 | 
			
		||||
        }
 | 
			
		||||
        $conf = new IDF_Conf();
 | 
			
		||||
        $conf->setProject($this->project);
 | 
			
		||||
        $onemax = array();
 | 
			
		||||
        foreach (split(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
 | 
			
		||||
            if (trim($class) != '') {
 | 
			
		||||
                $onemax[] = mb_strtolower(trim($class));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $count = array();
 | 
			
		||||
        for ($i=1;$i<4;$i++) {
 | 
			
		||||
            $this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
 | 
			
		||||
            if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
 | 
			
		||||
                list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
 | 
			
		||||
                list($class, $name) = array(mb_strtolower(trim($class)), 
 | 
			
		||||
                                            trim($name));
 | 
			
		||||
            } else {
 | 
			
		||||
                $class = 'other';
 | 
			
		||||
                $name = $this->cleaned_data['label'.$i];
 | 
			
		||||
            }
 | 
			
		||||
            if (!isset($count[$class])) $count[$class] = 1;
 | 
			
		||||
            else $count[$class] += 1;
 | 
			
		||||
            if (in_array($class, $onemax) and $count[$class] > 1) {
 | 
			
		||||
                if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
 | 
			
		||||
                $this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to a page.'), $class);
 | 
			
		||||
                throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save the model in the database.
 | 
			
		||||
     *
 | 
			
		||||
     * @param bool Commit in the database or not. If not, the object
 | 
			
		||||
     *             is returned but not saved in the database.
 | 
			
		||||
     * @return Object Model with data set from the form.
 | 
			
		||||
     */
 | 
			
		||||
    function save($commit=true)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->isValid()) {
 | 
			
		||||
            throw new Exception(__('Cannot save the model from an invalid form.'));
 | 
			
		||||
        }
 | 
			
		||||
        // Add a tag for each label
 | 
			
		||||
        $tags = array();
 | 
			
		||||
        if ($this->show_full) {
 | 
			
		||||
            for ($i=1;$i<4;$i++) {
 | 
			
		||||
                if (strlen($this->cleaned_data['label'.$i]) > 0) {
 | 
			
		||||
                    if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
 | 
			
		||||
                        list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
 | 
			
		||||
                        list($class, $name) = array(trim($class), trim($name));
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $class = 'Other';
 | 
			
		||||
                        $name = trim($this->cleaned_data['label'.$i]);
 | 
			
		||||
                    }
 | 
			
		||||
                    $tags[] = IDF_Tag::add($name, $this->project, $class);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } 
 | 
			
		||||
        // Create the page
 | 
			
		||||
        $page = new IDF_WikiPage();
 | 
			
		||||
        $page->project = $this->project;
 | 
			
		||||
        $page->submitter = $this->user;
 | 
			
		||||
        $page->summary = trim($this->cleaned_data['summary']);
 | 
			
		||||
        $page->title = trim($this->cleaned_data['title']);
 | 
			
		||||
        $page->create();
 | 
			
		||||
        foreach ($tags as $tag) {
 | 
			
		||||
            $page->setAssoc($tag);
 | 
			
		||||
        }
 | 
			
		||||
        // add the first revision
 | 
			
		||||
        $rev = new IDF_WikiRevision();
 | 
			
		||||
        $rev->wikipage = $page;
 | 
			
		||||
        $rev->content = $this->cleaned_data['content'];
 | 
			
		||||
        $rev->submitter = $this->user;
 | 
			
		||||
        $rev->summary = __('Initial page creation');
 | 
			
		||||
        $rev->create();
 | 
			
		||||
        return $page;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -204,26 +204,31 @@ class IDF_Form_ReviewCreate extends Pluf_Form
 | 
			
		||||
        $patch->commit = self::findCommit($this->cleaned_data['commit']);
 | 
			
		||||
        $patch->patch = $this->cleaned_data['patch'];
 | 
			
		||||
        $patch->create();
 | 
			
		||||
        // Send create notification
 | 
			
		||||
        if ('' != $this->project->getConf()->getVal('review_notification_email', '')) {
 | 
			
		||||
            $context = new Pluf_Template_Context(
 | 
			
		||||
                       array(
 | 
			
		||||
                             'review' => $review,
 | 
			
		||||
                             'patch' => $patch,
 | 
			
		||||
                             'comments' => array(),
 | 
			
		||||
                             'project' => $this->project,
 | 
			
		||||
                             'url_base' => Pluf::f('url_base'),
 | 
			
		||||
                             )
 | 
			
		||||
                                                     );
 | 
			
		||||
            $tmpl = new Pluf_Template('idf/review/review-created-email.txt');
 | 
			
		||||
            $text_email = $tmpl->render($context);
 | 
			
		||||
            $email = new Pluf_Mail(Pluf::f('from_email'), 
 | 
			
		||||
                       $this->project->getConf()->getVal('review_notification_email'),
 | 
			
		||||
                       sprintf(__('New Code Review %s - %s (%s)'),
 | 
			
		||||
                               $review->id, $review->summary, $this->project->shortname));
 | 
			
		||||
            $email->addTextMessage($text_email);
 | 
			
		||||
            $email->sendMail();
 | 
			
		||||
        }
 | 
			
		||||
        $patch->notify($this->project->getConf());
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Review::create
 | 
			
		||||
         *
 | 
			
		||||
         * [sender]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Form_ReviewCreate
 | 
			
		||||
         *
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal allows an application to perform a set of tasks
 | 
			
		||||
         * just after the creation of a review and the notification.
 | 
			
		||||
         *
 | 
			
		||||
         * [parameters]
 | 
			
		||||
         *
 | 
			
		||||
         * array('review' => $review,
 | 
			
		||||
         *       'patch' => $patch);
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        $params = array('review' => $review,
 | 
			
		||||
                        'patch' => $patch);
 | 
			
		||||
        Pluf_Signal::send('IDF_Review::create', 'IDF_Form_ReviewCreate',
 | 
			
		||||
                          $params);
 | 
			
		||||
        return $review;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,12 +30,15 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
 | 
			
		||||
    public $files = null;
 | 
			
		||||
    public $patch = null;
 | 
			
		||||
    public $user = null;
 | 
			
		||||
    public $project = null;
 | 
			
		||||
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
        $this->files = $extra['files'];
 | 
			
		||||
        $this->patch = $extra['patch'];
 | 
			
		||||
        $this->user = $extra['user'];
 | 
			
		||||
        $this->project = $extra['project'];
 | 
			
		||||
 | 
			
		||||
        foreach ($this->files as $filename => $def) {
 | 
			
		||||
            $this->fields[md5($filename)] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
@@ -48,6 +51,43 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
        }
 | 
			
		||||
        $this->fields['content'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('General comment'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'cols' => 58,
 | 
			
		||||
                                                       'rows' => 9,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
        if ($this->user->hasPerm('IDF.project-owner', $this->project)
 | 
			
		||||
            or $this->user->hasPerm('IDF.project-member', $this->project)) {
 | 
			
		||||
            $this->show_full = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->show_full = false;
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->show_full) {
 | 
			
		||||
            $this->fields['summary'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Summary'),
 | 
			
		||||
                                            'initial' => $this->patch->get_review()->summary,
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 200,
 | 
			
		||||
                                                       'size' => 67,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
            $this->fields['status'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Status'),
 | 
			
		||||
                                            'initial' => $this->patch->get_review()->get_status()->name,
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 20,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -64,6 +104,16 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
 | 
			
		||||
        throw new Pluf_Form_Invalid(__('You need to provide comments on at least one file.'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clean_content()
 | 
			
		||||
    {
 | 
			
		||||
        $content = trim($this->cleaned_data['content']);
 | 
			
		||||
        if (!$this->show_full and strlen($content) == 0) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('You need to provide your general comment about the proposal.'));
 | 
			
		||||
        }
 | 
			
		||||
        return $content;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save the model in the database.
 | 
			
		||||
     *
 | 
			
		||||
@@ -76,19 +126,40 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
 | 
			
		||||
        if (!$this->isValid()) {
 | 
			
		||||
            throw new Exception(__('Cannot save the model from an invalid form.'));
 | 
			
		||||
        }
 | 
			
		||||
        // create a base comment
 | 
			
		||||
        $bc = new IDF_Review_Comment();
 | 
			
		||||
        $bc->patch = $this->patch;
 | 
			
		||||
        $bc->submitter = $this->user;
 | 
			
		||||
        $bc->content = $this->cleaned_data['content'];
 | 
			
		||||
        $review = $this->patch->get_review();
 | 
			
		||||
        if ($this->show_full) {
 | 
			
		||||
            // Compare between the old and the new data
 | 
			
		||||
            // Status, summary 
 | 
			
		||||
            $changes = array();
 | 
			
		||||
            $status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
 | 
			
		||||
            if ($status->id != $this->patch->get_review()->status) {
 | 
			
		||||
                $changes['st'] = $status->name;
 | 
			
		||||
            }
 | 
			
		||||
            if (trim($this->patch->get_review()->summary) != trim($this->cleaned_data['summary'])) {
 | 
			
		||||
                $changes['su'] = trim($this->cleaned_data['summary']);
 | 
			
		||||
            }
 | 
			
		||||
            // Update the review
 | 
			
		||||
            $review->summary = trim($this->cleaned_data['summary']);
 | 
			
		||||
            $review->status = $status;
 | 
			
		||||
            $bc->changes = $changes;
 | 
			
		||||
        }
 | 
			
		||||
        $bc->create();
 | 
			
		||||
        foreach ($this->files as $filename => $def) {
 | 
			
		||||
            if (!empty($this->cleaned_data[md5($filename)])) {
 | 
			
		||||
                // Add a comment.
 | 
			
		||||
                $c = new IDF_Review_FileComment();
 | 
			
		||||
                $c->patch = $this->patch;
 | 
			
		||||
                $c->comment = $bc;
 | 
			
		||||
                $c->cfile = $filename;
 | 
			
		||||
                $c->submitter = $this->user;
 | 
			
		||||
                $c->content = $this->cleaned_data[md5($filename)];
 | 
			
		||||
                $c->create();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $this->patch->get_review()->update(); // reindex and put up in
 | 
			
		||||
                                              // the list.
 | 
			
		||||
        return $this->patch;
 | 
			
		||||
        $review->update(); // reindex and put up in the list.
 | 
			
		||||
        return $bc;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ class IDF_Form_SourceConf extends Pluf_Form
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
        $this->conf = $extra['conf'];
 | 
			
		||||
        if ($this->conf->getVal('scm', 'git') == 'svn') {
 | 
			
		||||
        if ($extra['remote_svn']) {
 | 
			
		||||
            $this->fields['svn_username'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                    array('required' => false,
 | 
			
		||||
                          'label' => __('Repository username'),
 | 
			
		||||
@@ -49,6 +49,16 @@ class IDF_Form_SourceConf extends Pluf_Form
 | 
			
		||||
                          'widget' => 'Pluf_Form_Widget_PasswordInput',
 | 
			
		||||
                          ));
 | 
			
		||||
        }
 | 
			
		||||
        Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
 | 
			
		||||
        $url = Pluf_HTTP_URL_urlForView('idf_faq').'#webhooks';
 | 
			
		||||
        $this->fields['webhook_url'] = new Pluf_Form_Field_Url(
 | 
			
		||||
                    array('required' => false,
 | 
			
		||||
                          'label' => __('Webhook URL'),
 | 
			
		||||
                          'initial' => $this->conf->getVal('webhook_url', ''),
 | 
			
		||||
                          'help_text' => sprintf(__('Learn more about the <a href="%s">post-commit web hooks</a>.'), $url),
 | 
			
		||||
                          'widget_attrs' => array('size' => 35),
 | 
			
		||||
                          ));
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ class IDF_Form_TabsConf extends Pluf_Form
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => $label,
 | 
			
		||||
                                            'initial' => $this->conf->getVal($key, 'all'),
 | 
			
		||||
                                            'widget_attrs' => array('choices' => 
 | 
			
		||||
                                            'widget_attrs' => array('choices' =>
 | 
			
		||||
                                          array(
 | 
			
		||||
                                                __('Open to all') => 'all',
 | 
			
		||||
                                                __('Signed in users') => 'login',
 | 
			
		||||
@@ -63,10 +63,11 @@ class IDF_Form_TabsConf extends Pluf_Form
 | 
			
		||||
                    'source_notification_email',
 | 
			
		||||
                    'issues_notification_email',);
 | 
			
		||||
        foreach ($ak as $key) {
 | 
			
		||||
            $this->fields[$key] = new Pluf_Form_Field_Email(
 | 
			
		||||
            $this->fields[$key] = new IDF_Form_Field_EmailList(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => $key,
 | 
			
		||||
                                            'initial' => $this->conf->getVal($key, ''),
 | 
			
		||||
                                            'widget_attrs' => array('size' => 40),
 | 
			
		||||
                                            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -86,6 +87,11 @@ class IDF_Form_TabsConf extends Pluf_Form
 | 
			
		||||
                                            ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clean_authorized_users()
 | 
			
		||||
    {
 | 
			
		||||
        return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['authorized_users']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function save($commit=true)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->isValid()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,16 @@ class IDF_Form_UpdateUpload extends Pluf_Form
 | 
			
		||||
                                                       'size' => 67,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
        $this->fields['changelog'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Description'),
 | 
			
		||||
                                            'initial' => $this->upload->changelog,
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'cols' => 58,
 | 
			
		||||
                                                       'rows' => 13,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
        $tags = $this->upload->get_tags_list();
 | 
			
		||||
        for ($i=1;$i<7;$i++) {
 | 
			
		||||
            $initial = '';
 | 
			
		||||
@@ -76,7 +86,7 @@ class IDF_Form_UpdateUpload extends Pluf_Form
 | 
			
		||||
        $conf = new IDF_Conf();
 | 
			
		||||
        $conf->setProject($this->project);
 | 
			
		||||
        $onemax = array();
 | 
			
		||||
        foreach (split(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
 | 
			
		||||
        foreach (explode(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
 | 
			
		||||
            if (trim($class) != '') {
 | 
			
		||||
                $onemax[] = mb_strtolower(trim($class));
 | 
			
		||||
            }
 | 
			
		||||
@@ -132,9 +142,32 @@ class IDF_Form_UpdateUpload extends Pluf_Form
 | 
			
		||||
        }
 | 
			
		||||
        // Create the upload
 | 
			
		||||
        $this->upload->summary = trim($this->cleaned_data['summary']);
 | 
			
		||||
        $this->upload->changelog = trim($this->cleaned_data['changelog']);
 | 
			
		||||
        $this->upload->modif_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
        $this->upload->update();
 | 
			
		||||
        $this->upload->batchAssoc('IDF_Tag', $tags);
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Upload::update
 | 
			
		||||
         *
 | 
			
		||||
         * [sender]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Form_UpdateUpload
 | 
			
		||||
         *
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal allows an application to perform a set of tasks
 | 
			
		||||
         * just after the update of an uploaded file.
 | 
			
		||||
         *
 | 
			
		||||
         * [parameters]
 | 
			
		||||
         *
 | 
			
		||||
         * array('upload' => $upload);
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        $params = array('upload' => $this->upload);
 | 
			
		||||
        Pluf_Signal::send('IDF_Upload::update', 
 | 
			
		||||
                          'IDF_Form_UpdateUpload', $params);
 | 
			
		||||
        return $this->upload;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,16 @@ class IDF_Form_Upload extends Pluf_Form
 | 
			
		||||
                                                       'size' => 67,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
        $this->fields['changelog'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Description'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'cols' => 58,
 | 
			
		||||
                                                       'rows' => 13,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
        $this->fields['file'] = new Pluf_Form_Field_File(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('File'),
 | 
			
		||||
@@ -71,8 +81,9 @@ class IDF_Form_Upload extends Pluf_Form
 | 
			
		||||
    {
 | 
			
		||||
        $extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext'))));
 | 
			
		||||
        if (strlen($extra)) $extra .= '|';
 | 
			
		||||
        if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|wmv|zip)$/i', $this->cleaned_data['file'])) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('For security reason, you cannot upload a file with this extension.'));
 | 
			
		||||
        if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $this->cleaned_data['file'])) {
 | 
			
		||||
            @unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['file']);
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('For security reasons, you cannot upload a file with this extension.'));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data['file'];
 | 
			
		||||
    }
 | 
			
		||||
@@ -85,7 +96,7 @@ class IDF_Form_Upload extends Pluf_Form
 | 
			
		||||
        $conf = new IDF_Conf();
 | 
			
		||||
        $conf->setProject($this->project);
 | 
			
		||||
        $onemax = array();
 | 
			
		||||
        foreach (split(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
 | 
			
		||||
        foreach (explode(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
 | 
			
		||||
            if (trim($class) != '') {
 | 
			
		||||
                $onemax[] = mb_strtolower(trim($class));
 | 
			
		||||
            }
 | 
			
		||||
@@ -155,6 +166,7 @@ class IDF_Form_Upload extends Pluf_Form
 | 
			
		||||
        $upload->project = $this->project;
 | 
			
		||||
        $upload->submitter = $this->user;
 | 
			
		||||
        $upload->summary = trim($this->cleaned_data['summary']);
 | 
			
		||||
        $upload->changelog = trim($this->cleaned_data['changelog']);
 | 
			
		||||
        $upload->file = $this->cleaned_data['file'];
 | 
			
		||||
        $upload->filesize = filesize(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['file']);
 | 
			
		||||
        $upload->downloads = 0;
 | 
			
		||||
@@ -163,21 +175,29 @@ class IDF_Form_Upload extends Pluf_Form
 | 
			
		||||
            $upload->setAssoc($tag);
 | 
			
		||||
        }
 | 
			
		||||
        // Send the notification
 | 
			
		||||
        if ('' != $this->project->getConf()->getVal('downloads_notification_email', '')) {
 | 
			
		||||
            $context = new Pluf_Template_Context(
 | 
			
		||||
                      array('file' => $upload,
 | 
			
		||||
                            'urlfile' => $upload->getAbsoluteUrl($this->project),
 | 
			
		||||
                            'project' => $this->project,
 | 
			
		||||
                            'tags' => $upload->get_tags_list(),
 | 
			
		||||
                            ));
 | 
			
		||||
            $tmpl = new Pluf_Template('idf/downloads/download-created-email.txt');
 | 
			
		||||
            $text_email = $tmpl->render($context);
 | 
			
		||||
            $email = new Pluf_Mail(Pluf::f('from_email'), $this->project->getConf()->getVal('downloads_notification_email'),
 | 
			
		||||
                                   sprintf(__('New download - %s (%s)'),
 | 
			
		||||
                                           $upload->summary, $this->project->shortname));
 | 
			
		||||
            $email->addTextMessage($text_email);
 | 
			
		||||
            $email->sendMail();
 | 
			
		||||
        }
 | 
			
		||||
        $upload->notify($this->project->getConf());
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Upload::create
 | 
			
		||||
         *
 | 
			
		||||
         * [sender]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Form_Upload
 | 
			
		||||
         *
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal allows an application to perform a set of tasks
 | 
			
		||||
         * just after the upload of a file and after the notification run.
 | 
			
		||||
         *
 | 
			
		||||
         * [parameters]
 | 
			
		||||
         *
 | 
			
		||||
         * array('upload' => $upload);
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        $params = array('upload' => $upload);
 | 
			
		||||
        Pluf_Signal::send('IDF_Upload::create', 'IDF_Form_Upload',
 | 
			
		||||
                          $params);
 | 
			
		||||
        return $upload;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,8 @@ class IDF_Form_UserAccount  extends Pluf_Form
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
        $this->user = $extra['user'];
 | 
			
		||||
        $user_data = IDF_UserData::factory($this->user);
 | 
			
		||||
 | 
			
		||||
        $this->fields['first_name'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('First name'),
 | 
			
		||||
@@ -65,7 +67,7 @@ class IDF_Form_UserAccount  extends Pluf_Form
 | 
			
		||||
                                            'initial' => $this->user->language,
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_SelectInput',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'choices' => 
 | 
			
		||||
                                                       'choices' =>
 | 
			
		||||
                                                       Pluf_L10n::getInstalledLanguages()
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
@@ -77,6 +79,7 @@ class IDF_Form_UserAccount  extends Pluf_Form
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_PasswordInput',
 | 
			
		||||
                                            'help_text' => Pluf_Template::markSafe(__('Leave blank if you do not want to change your password.').'<br />'.__('Your password must be hard for other people to find it, but easy for you to remember.')),
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'autocomplete' => 'off',
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
@@ -87,24 +90,118 @@ class IDF_Form_UserAccount  extends Pluf_Form
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_PasswordInput',
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'autocomplete' => 'off',
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['ssh_key'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
        $this->fields['description'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Your public SSH key'),
 | 
			
		||||
                                            'label' => __('Description'),
 | 
			
		||||
                                            'initial' => $user_data->description,
 | 
			
		||||
                                            'widget_attrs' => array('rows' => 3,
 | 
			
		||||
                                                                    'cols' => 40),
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['twitter'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Twitter username'),
 | 
			
		||||
                                            'initial' => $user_data->twitter,
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['public_email'] = new Pluf_Form_Field_Email(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Public email address'),
 | 
			
		||||
                                            'initial' => $user_data->public_email,
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['website'] = new Pluf_Form_Field_Url(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Website URL'),
 | 
			
		||||
                                            'initial' => $user_data->website,
 | 
			
		||||
                                            'widget_attrs' => array(
 | 
			
		||||
                                                       'maxlength' => 50,
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['custom_avatar'] = new Pluf_Form_Field_File(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Upload custom avatar'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'max_size' => Pluf::f('max_upload_size', 2097152),
 | 
			
		||||
                                            'move_function_params' => array('upload_path' => Pluf::f('upload_path').'/avatars',
 | 
			
		||||
                                                                            'upload_path_create' => true,
 | 
			
		||||
                                                                            'upload_overwrite' => true,
 | 
			
		||||
                                                                            'file_name' => 'user_'.$this->user->id.'_%s'),
 | 
			
		||||
                                            'help_text' => __('An image file with a width and height not larger than 60 pixels (bigger images are scaled down).'),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['remove_custom_avatar'] = new Pluf_Form_Field_Boolean(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Remove custom avatar'),
 | 
			
		||||
                                            'initial' => false,
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_CheckboxInput',
 | 
			
		||||
                                            'widget_attrs' => array(),
 | 
			
		||||
                                            'help_text' => __('Tick this to delete the custom avatar.'),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['public_key'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Add a public key'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'widget_attrs' => array('rows' => 3,
 | 
			
		||||
                                                                    'cols' => 40),
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            'help_text' => __('Be careful to provide your public key and not your private key!')
 | 
			
		||||
                                            'help_text' => __('Paste an SSH or monotone public key. Be careful to not provide your private key here!')
 | 
			
		||||
                                            ));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $this->fields['secondary_mail'] = new Pluf_Form_Field_Email(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Add a secondary mail address'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            'help_text' => __('You will get a mail to confirm that you own the address you specify.'),
 | 
			
		||||
                                            ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function send_validation_mail($new_email, $secondary_mail=false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($secondary_mail) {
 | 
			
		||||
            $type = "secondary";
 | 
			
		||||
        } else {
 | 
			
		||||
            $type = "primary";
 | 
			
		||||
        }
 | 
			
		||||
        $cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
 | 
			
		||||
        $encrypted = trim($cr->encrypt($new_email.':'.$this->user->id.':'.time().':'.$type), '~');
 | 
			
		||||
        $key = substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted;
 | 
			
		||||
        $url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailDo', array($key), array(), false);
 | 
			
		||||
        $urlik = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailInputKey', array(), array(), false);
 | 
			
		||||
        $context = new Pluf_Template_Context(
 | 
			
		||||
             array('key' => Pluf_Template::markSafe($key),
 | 
			
		||||
                   'url' => Pluf_Template::markSafe($url),
 | 
			
		||||
                   'urlik' => Pluf_Template::markSafe($urlik),
 | 
			
		||||
                   'email' => $new_email,
 | 
			
		||||
                   'user'=> $this->user,
 | 
			
		||||
                   )
 | 
			
		||||
        );
 | 
			
		||||
        $tmpl = new Pluf_Template('idf/user/changeemail-email.txt');
 | 
			
		||||
        $text_email = $tmpl->render($context);
 | 
			
		||||
        $email = new Pluf_Mail(Pluf::f('from_email'), $new_email,
 | 
			
		||||
                               __('Confirm your new email address.'));
 | 
			
		||||
        $email->addTextMessage($text_email);
 | 
			
		||||
        $email->sendMail();
 | 
			
		||||
        $this->user->setMessage(sprintf(__('A validation email has been sent to "%s" to validate the email address change.'), Pluf_esc($new_email)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save the model in the database.
 | 
			
		||||
@@ -129,44 +226,52 @@ class IDF_Form_UserAccount  extends Pluf_Form
 | 
			
		||||
        $new_email = $this->cleaned_data['email'];
 | 
			
		||||
        unset($this->cleaned_data['email']);
 | 
			
		||||
        if ($old_email != $new_email) {
 | 
			
		||||
            $cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
 | 
			
		||||
            $encrypted = trim($cr->encrypt($new_email.':'.$this->user->id.':'.time()), '~');
 | 
			
		||||
            $key = substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted;
 | 
			
		||||
            $url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailDo', array($key), array(), false);
 | 
			
		||||
            $urlik = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailInputKey', array(), array(), false);
 | 
			
		||||
            $context = new Pluf_Template_Context(
 | 
			
		||||
                 array('key' => Pluf_Template::markSafe($key),
 | 
			
		||||
                       'url' => Pluf_Template::markSafe($url),
 | 
			
		||||
                       'urlik' => Pluf_Template::markSafe($urlik),
 | 
			
		||||
                       'email' => $new_email,
 | 
			
		||||
                       'user'=> $this->user,
 | 
			
		||||
                       )
 | 
			
		||||
                                                 );
 | 
			
		||||
            $tmpl = new Pluf_Template('idf/user/changeemail-email.txt');
 | 
			
		||||
            $text_email = $tmpl->render($context);
 | 
			
		||||
            $email = new Pluf_Mail(Pluf::f('from_email'), $new_email,
 | 
			
		||||
                                   __('Confirm your new email address.'));
 | 
			
		||||
            $email->addTextMessage($text_email);
 | 
			
		||||
            $email->sendMail();
 | 
			
		||||
            $this->user->setMessage(sprintf(__('A validation email has been sent to "%s" to validate the email address change.'), Pluf_esc($new_email)));
 | 
			
		||||
            $this->send_validation_mail($new_email);
 | 
			
		||||
        }
 | 
			
		||||
        $this->user->setFromFormData($this->cleaned_data);
 | 
			
		||||
        // Get keys
 | 
			
		||||
        $keys = $this->user->get_idf_key_list();
 | 
			
		||||
        if ($keys->count() > 0) {
 | 
			
		||||
            $key = $keys[0];
 | 
			
		||||
        } else {
 | 
			
		||||
        // Add key as needed.
 | 
			
		||||
        if ('' !== $this->cleaned_data['public_key']) {
 | 
			
		||||
            $key = new IDF_Key();
 | 
			
		||||
            $key->user = $this->user;
 | 
			
		||||
        }
 | 
			
		||||
        $key->content = $this->cleaned_data['ssh_key'];
 | 
			
		||||
        if ($commit) {
 | 
			
		||||
            $this->user->update();
 | 
			
		||||
            if ($key->id != '') {
 | 
			
		||||
                $key->update();
 | 
			
		||||
            } else {
 | 
			
		||||
            $key->content = $this->cleaned_data['public_key'];
 | 
			
		||||
            if ($commit) {
 | 
			
		||||
                $key->create();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if ('' !== $this->cleaned_data['secondary_mail']) {
 | 
			
		||||
            $this->send_validation_mail($this->cleaned_data['secondary_mail'], true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($commit) {
 | 
			
		||||
            $this->user->update();
 | 
			
		||||
 | 
			
		||||
            // FIXME: go the extra mile and check the input lengths for
 | 
			
		||||
            // all fields here!
 | 
			
		||||
            // FIXME: this is all doubled in admin/UserUpdate!
 | 
			
		||||
 | 
			
		||||
            $user_data = IDF_UserData::factory($this->user);
 | 
			
		||||
 | 
			
		||||
            // Add or remove avatar - we need to do this here because every
 | 
			
		||||
            // single setter directly leads to a save in the database
 | 
			
		||||
            if ($user_data->avatar != '' &&
 | 
			
		||||
                    ($this->cleaned_data['remove_custom_avatar'] == 1 ||
 | 
			
		||||
                     $this->cleaned_data['custom_avatar'] != '')) {
 | 
			
		||||
                $avatar_path = Pluf::f('upload_path').'/avatars/'.basename($user_data->avatar);
 | 
			
		||||
                if (basename($avatar_path) != '' && is_file($avatar_path)) {
 | 
			
		||||
                    unlink($avatar_path);
 | 
			
		||||
                }
 | 
			
		||||
                $user_data->avatar = '';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ($this->cleaned_data['custom_avatar'] != '') {
 | 
			
		||||
                $user_data->avatar = $this->cleaned_data['custom_avatar'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $user_data->description  = $this->cleaned_data['description'];
 | 
			
		||||
            $user_data->twitter      = $this->cleaned_data['twitter'];
 | 
			
		||||
            $user_data->public_email = $this->cleaned_data['public_email'];
 | 
			
		||||
            $user_data->website      = $this->cleaned_data['website'];
 | 
			
		||||
 | 
			
		||||
            if ($update_pass) {
 | 
			
		||||
                /**
 | 
			
		||||
                 * [signal]
 | 
			
		||||
@@ -195,11 +300,103 @@ class IDF_Form_UserAccount  extends Pluf_Form
 | 
			
		||||
        return $this->user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check arbitrary public keys.
 | 
			
		||||
     *
 | 
			
		||||
     * It will throw a Pluf_Form_Invalid exception if it cannot
 | 
			
		||||
     * validate the key.
 | 
			
		||||
     *
 | 
			
		||||
     * @param $key string The key
 | 
			
		||||
     * @param $user int The user id of the user of the key (0)
 | 
			
		||||
     * @return string The clean key
 | 
			
		||||
     */
 | 
			
		||||
    public static function checkPublicKey($key, $user=0)
 | 
			
		||||
    {
 | 
			
		||||
        $key = trim($key);
 | 
			
		||||
        if (strlen($key) == 0) {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (preg_match('#^ssh\-[a-z]{3}\s\S+(\s\S+)?$#', $key)) {
 | 
			
		||||
            $key = str_replace(array("\n", "\r"), '', $key);
 | 
			
		||||
 | 
			
		||||
            if (Pluf::f('idf_strong_key_check', false)) {
 | 
			
		||||
 | 
			
		||||
                $tmpfile = Pluf::f('tmp_folder', '/tmp').'/'.$user.'-key';
 | 
			
		||||
                file_put_contents($tmpfile, $key, LOCK_EX);
 | 
			
		||||
                $cmd = Pluf::f('idf_exec_cmd_prefix', '').
 | 
			
		||||
                    'ssh-keygen -l -f '.escapeshellarg($tmpfile).' > /dev/null 2>&1';
 | 
			
		||||
                exec($cmd, $out, $return);
 | 
			
		||||
                unlink($tmpfile);
 | 
			
		||||
 | 
			
		||||
                if ($return != 0) {
 | 
			
		||||
                    throw new Pluf_Form_Invalid(
 | 
			
		||||
                        __('Please check the key as it does not appear '.
 | 
			
		||||
                           'to be a valid SSH public key.')
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (preg_match('#^\[pubkey [^\]]+\]\s*\S+\s*\[end\]$#', $key)) {
 | 
			
		||||
            if (Pluf::f('idf_strong_key_check', false)) {
 | 
			
		||||
 | 
			
		||||
                // if monotone can read it, it should be valid
 | 
			
		||||
                $mtn_opts = implode(' ', Pluf::f('mtn_opts', array()));
 | 
			
		||||
                $cmd = Pluf::f('idf_exec_cmd_prefix', '').
 | 
			
		||||
                    sprintf('%s %s -d :memory: read >/tmp/php-out 2>&1',
 | 
			
		||||
                            Pluf::f('mtn_path', 'mtn'), $mtn_opts);
 | 
			
		||||
                $fp = popen($cmd, 'w');
 | 
			
		||||
                fwrite($fp, $key);
 | 
			
		||||
                $return = pclose($fp);
 | 
			
		||||
 | 
			
		||||
                if ($return != 0) {
 | 
			
		||||
                       throw new Pluf_Form_Invalid(
 | 
			
		||||
                        __('Please check the key as it does not appear '.
 | 
			
		||||
                           'to be a valid monotone public key.')
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            throw new Pluf_Form_Invalid(
 | 
			
		||||
                __('Public key looks like neither an SSH '.
 | 
			
		||||
                   'nor monotone public key.'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If $user, then check if not the same key stored
 | 
			
		||||
        if ($user) {
 | 
			
		||||
            $ruser = Pluf::factory('Pluf_User', $user);
 | 
			
		||||
            if ($ruser->id > 0) {
 | 
			
		||||
                $sql = new Pluf_SQL('content=%s', array($key));
 | 
			
		||||
                $keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
 | 
			
		||||
                if (count($keys) > 0) {
 | 
			
		||||
                    throw new Pluf_Form_Invalid(
 | 
			
		||||
                        __('You already have uploaded this key.')
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $key;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function clean_custom_avatar()
 | 
			
		||||
    {
 | 
			
		||||
        // Just png, jpeg/jpg or gif
 | 
			
		||||
        if (!preg_match('/\.(png|jpg|jpeg|gif)$/i', $this->cleaned_data['custom_avatar']) &&
 | 
			
		||||
            $this->cleaned_data['custom_avatar'] != '') {
 | 
			
		||||
            @unlink(Pluf::f('upload_path').'/avatars/'.$this->cleaned_data['custom_avatar']);
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('For security reason, you cannot upload a file with this extension.'));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data['custom_avatar'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function clean_last_name()
 | 
			
		||||
    {
 | 
			
		||||
        $last_name = trim($this->cleaned_data['last_name']);
 | 
			
		||||
        if ($last_name == mb_strtoupper($last_name)) {
 | 
			
		||||
            return mb_convert_case(mb_strtolower($last_name), 
 | 
			
		||||
            return mb_convert_case(mb_strtolower($last_name),
 | 
			
		||||
                                   MB_CASE_TITLE, 'UTF-8');
 | 
			
		||||
        }
 | 
			
		||||
        return $last_name;
 | 
			
		||||
@@ -209,7 +406,7 @@ class IDF_Form_UserAccount  extends Pluf_Form
 | 
			
		||||
    {
 | 
			
		||||
        $first_name = trim($this->cleaned_data['first_name']);
 | 
			
		||||
        if ($first_name == mb_strtoupper($first_name)) {
 | 
			
		||||
            return mb_convert_case(mb_strtolower($first_name), 
 | 
			
		||||
            return mb_convert_case(mb_strtolower($first_name),
 | 
			
		||||
                                   MB_CASE_TITLE, 'UTF-8');
 | 
			
		||||
        }
 | 
			
		||||
        return $first_name;
 | 
			
		||||
@@ -218,21 +415,36 @@ class IDF_Form_UserAccount  extends Pluf_Form
 | 
			
		||||
    function clean_email()
 | 
			
		||||
    {
 | 
			
		||||
        $this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email']));
 | 
			
		||||
        $guser = new Pluf_User();
 | 
			
		||||
        $sql = new Pluf_SQL('email=%s AND id!=%s', 
 | 
			
		||||
                            array($this->cleaned_data['email'], $this->user->id));
 | 
			
		||||
        if ($guser->getCount(array('filter' => $sql->gen())) > 0) {
 | 
			
		||||
        $user = Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($this->cleaned_data['email']);
 | 
			
		||||
        if ($user != null and $user->id != $this->user->id) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used.'), $this->cleaned_data['email']));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data['email'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clean_secondary_mail()
 | 
			
		||||
    {
 | 
			
		||||
        $this->cleaned_data['secondary_mail'] = mb_strtolower(trim($this->cleaned_data['secondary_mail']));
 | 
			
		||||
        if (Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($this->cleaned_data['secondary_mail']) != null) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used.'), $this->cleaned_data['secondary_mail']));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data['secondary_mail'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clean_public_key()
 | 
			
		||||
    {
 | 
			
		||||
        $this->cleaned_data['public_key'] =
 | 
			
		||||
                self::checkPublicKey($this->cleaned_data['public_key'],
 | 
			
		||||
                                     $this->user->id);
 | 
			
		||||
        return $this->cleaned_data['public_key'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check to see if the 2 passwords are the same.
 | 
			
		||||
     * Check to see if the 2 passwords are the same
 | 
			
		||||
     */
 | 
			
		||||
    public function clean()
 | 
			
		||||
    {
 | 
			
		||||
        if (!isset($this->errors['password']) 
 | 
			
		||||
        if (!isset($this->errors['password'])
 | 
			
		||||
            && !isset($this->errors['password2'])) {
 | 
			
		||||
            $password1 = $this->cleaned_data['password'];
 | 
			
		||||
            $password2 = $this->cleaned_data['password2'];
 | 
			
		||||
@@ -240,6 +452,9 @@ class IDF_Form_UserAccount  extends Pluf_Form
 | 
			
		||||
                throw new Pluf_Form_Invalid(__('The passwords do not match. Please give them again.'));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->cleaned_data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@ class IDF_Form_UserChangeEmail extends Pluf_Form
 | 
			
		||||
     * Throw a Pluf_Form_Invalid exception if the key is not valid.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string Key
 | 
			
		||||
     * @return array array($new_email, $user_id, time())
 | 
			
		||||
     * @return array array($new_email, $user_id, time(), [primary|secondary])
 | 
			
		||||
     */
 | 
			
		||||
    public static function validateKey($key)
 | 
			
		||||
    {
 | 
			
		||||
@@ -63,7 +63,7 @@ class IDF_Form_UserChangeEmail extends Pluf_Form
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('The validation key is not valid. Please copy/paste it from your confirmation email.'));
 | 
			
		||||
        }
 | 
			
		||||
        $cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
 | 
			
		||||
        return split(':', $cr->decrypt($encrypted), 3);
 | 
			
		||||
        return explode(':', $cr->decrypt($encrypted), 4);
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -127,7 +127,7 @@ Add your content here. Format your content with:
 | 
			
		||||
        $conf = new IDF_Conf();
 | 
			
		||||
        $conf->setProject($this->project);
 | 
			
		||||
        $onemax = array();
 | 
			
		||||
        foreach (split(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
 | 
			
		||||
        foreach (explode(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
 | 
			
		||||
            if (trim($class) != '') {
 | 
			
		||||
                $onemax[] = mb_strtolower(trim($class));
 | 
			
		||||
            }
 | 
			
		||||
@@ -199,25 +199,7 @@ Add your content here. Format your content with:
 | 
			
		||||
        $rev->submitter = $this->user;
 | 
			
		||||
        $rev->summary = __('Initial page creation');
 | 
			
		||||
        $rev->create();
 | 
			
		||||
        // send the notification
 | 
			
		||||
        if ('' != $this->project->getConf()->getVal('wiki_notification_email', '')) {
 | 
			
		||||
            $context = new Pluf_Template_Context(
 | 
			
		||||
                       array(
 | 
			
		||||
                             'page' => $page,
 | 
			
		||||
                             'rev' => $rev,
 | 
			
		||||
                             'project' => $this->project,
 | 
			
		||||
                             'url_base' => Pluf::f('url_base'),
 | 
			
		||||
                             )
 | 
			
		||||
                                                     );
 | 
			
		||||
            $tmpl = new Pluf_Template('idf/wiki/wiki-created-email.txt');
 | 
			
		||||
            $text_email = $tmpl->render($context);
 | 
			
		||||
            $email = new Pluf_Mail(Pluf::f('from_email'), 
 | 
			
		||||
                       $this->project->getConf()->getVal('wiki_notification_email'),
 | 
			
		||||
                       sprintf(__('New Documentation Page %s - %s (%s)'),
 | 
			
		||||
                               $page->title, $page->summary, $this->project->shortname));
 | 
			
		||||
            $email->addTextMessage($text_email);
 | 
			
		||||
            $email->sendMail();
 | 
			
		||||
        }
 | 
			
		||||
        $rev->notify($this->project->getConf());
 | 
			
		||||
        return $page;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										64
									
								
								src/IDF/Form/WikiDelete.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/IDF/Form/WikiDelete.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Delete a documentation page.
 | 
			
		||||
 *
 | 
			
		||||
 * This is a hard delete of the page and the revisions.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Form_WikiDelete extends Pluf_Form
 | 
			
		||||
{
 | 
			
		||||
    protected $page = null;
 | 
			
		||||
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
        $this->page = $extra['page'];
 | 
			
		||||
        $this->fields['confirm'] = new Pluf_Form_Field_Boolean(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Yes, I understand that the page and all its revisions will be deleted.'),
 | 
			
		||||
                                            'initial' => '',
 | 
			
		||||
                                            ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check the confirmation.
 | 
			
		||||
     */
 | 
			
		||||
    public function clean_confirm()
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->cleaned_data['confirm']) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('You need to confirm the deletion.'));
 | 
			
		||||
        }
 | 
			
		||||
        return $this->cleaned_data['confirm'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function save($commit=true)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->isValid()) {
 | 
			
		||||
            throw new Exception(__('Cannot save the model from an invalid form.'));
 | 
			
		||||
        }
 | 
			
		||||
        $this->page->delete();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -138,7 +138,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
 | 
			
		||||
        $conf = new IDF_Conf();
 | 
			
		||||
        $conf->setProject($this->project);
 | 
			
		||||
        $onemax = array();
 | 
			
		||||
        foreach (split(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
 | 
			
		||||
        foreach (explode(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
 | 
			
		||||
            if (trim($class) != '') {
 | 
			
		||||
                $onemax[] = mb_strtolower(trim($class));
 | 
			
		||||
            }
 | 
			
		||||
@@ -236,25 +236,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
 | 
			
		||||
        $rev->summary = $this->cleaned_data['comment'];
 | 
			
		||||
        $rev->changes = $changes;
 | 
			
		||||
        $rev->create();
 | 
			
		||||
        // send the notification
 | 
			
		||||
        if ('' != $this->project->getConf()->getVal('wiki_notification_email', '')) {
 | 
			
		||||
            $context = new Pluf_Template_Context(
 | 
			
		||||
                       array(
 | 
			
		||||
                             'page' => $this->page,
 | 
			
		||||
                             'rev' => $rev,
 | 
			
		||||
                             'project' => $this->project,
 | 
			
		||||
                             'url_base' => Pluf::f('url_base'),
 | 
			
		||||
                             )
 | 
			
		||||
                                                     );
 | 
			
		||||
            $tmpl = new Pluf_Template('idf/wiki/wiki-updated-email.txt');
 | 
			
		||||
            $text_email = $tmpl->render($context);
 | 
			
		||||
            $email = new Pluf_Mail(Pluf::f('from_email'), 
 | 
			
		||||
                       $this->project->getConf()->getVal('wiki_notification_email'),
 | 
			
		||||
                       sprintf(__('Documentation Page Changed %s - %s (%s)'),
 | 
			
		||||
                               $this->page->title, $this->page->summary, $this->project->shortname));
 | 
			
		||||
            $email->addTextMessage($text_email);
 | 
			
		||||
            $email->sendMail();
 | 
			
		||||
        }
 | 
			
		||||
        $rev->notify($this->project->getConf(), false);
 | 
			
		||||
        return $this->page;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										210
									
								
								src/IDF/Gconf.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								src/IDF/Gconf.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,210 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Configuration of the objects.
 | 
			
		||||
 *
 | 
			
		||||
 * It is just storing a list of key/value pairs associated to
 | 
			
		||||
 * different objects. If you use this table for your model, do not
 | 
			
		||||
 * forget to drop the corresponding keys in your preDelete call.
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Gconf extends Pluf_Model
 | 
			
		||||
{
 | 
			
		||||
    public $_model = __CLASS__;
 | 
			
		||||
    public $datacache = null;
 | 
			
		||||
    public $dirty = array();
 | 
			
		||||
    public $f = null;
 | 
			
		||||
    protected $_mod = null;
 | 
			
		||||
    /**
 | 
			
		||||
     * Do we (un)serialize the data when getting/setting them.
 | 
			
		||||
     */
 | 
			
		||||
    public $serialize = false;
 | 
			
		||||
 | 
			
		||||
    function init()
 | 
			
		||||
    {
 | 
			
		||||
        $this->_a['table'] = 'idf_gconf';
 | 
			
		||||
        $this->_a['model'] = __CLASS__;
 | 
			
		||||
        $this->_a['cols'] = array(
 | 
			
		||||
                             // It is mandatory to have an "id" column.
 | 
			
		||||
                            'id' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  //It is automatically added.
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'model_class' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Varchar',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'size' => 150,
 | 
			
		||||
                                  'verbose' => __('model class'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'model_id' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Integer',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('model id'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'vkey' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Varchar',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'size' => 50,
 | 
			
		||||
                                  'verbose' => __('key'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'vdesc' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Text',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('value'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
        $this->_a['idx'] = array('model_vkey_idx' =>
 | 
			
		||||
                                 array(
 | 
			
		||||
                                       'col' => 'model_class, model_id, vkey',
 | 
			
		||||
                                       'type' => 'unique',
 | 
			
		||||
                                       ),
 | 
			
		||||
                                 );
 | 
			
		||||
        $this->f = new IDF_Config_DataProxy($this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function setModel($model)
 | 
			
		||||
    {
 | 
			
		||||
        $this->datacache = null;
 | 
			
		||||
        $this->_mod = $model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function initCache()
 | 
			
		||||
    {
 | 
			
		||||
        $this->datacache = array();
 | 
			
		||||
        $this->dirty = array();
 | 
			
		||||
        $sql = new Pluf_SQL('model_class=%s AND model_id=%s',
 | 
			
		||||
                            array($this->_mod->_model, $this->_mod->id));
 | 
			
		||||
        foreach ($this->getList(array('filter' => $sql->gen())) as $val) {
 | 
			
		||||
            $this->datacache[$val->vkey] = ($this->serialize) ? unserialize($val->vdesc) : $val->vdesc;
 | 
			
		||||
            $this->dirty[$val->vkey] = $val->id;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * FIXME: This is not efficient when setting a large number of
 | 
			
		||||
     * values in a loop.
 | 
			
		||||
     */
 | 
			
		||||
    function setVal($key, $value)
 | 
			
		||||
    {
 | 
			
		||||
        if (!is_null($this->getVal($key, null))
 | 
			
		||||
            and $value == $this->getVal($key)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $svalue = ($this->serialize) ? serialize($value) : $value;
 | 
			
		||||
        if (isset($this->dirty[$key])) {
 | 
			
		||||
            // we get to check if deleted by other process + update
 | 
			
		||||
            $conf = new IDF_Gconf($this->dirty[$key]);
 | 
			
		||||
            if ($conf->id == $this->dirty[$key]) {
 | 
			
		||||
                $conf->vdesc = $svalue;
 | 
			
		||||
                $conf->update();
 | 
			
		||||
                $this->datacache[$key] = $value;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // we insert
 | 
			
		||||
        $conf = new IDF_Gconf();
 | 
			
		||||
        $conf->model_class = $this->_mod->_model;
 | 
			
		||||
        $conf->model_id = $this->_mod->id;
 | 
			
		||||
        $conf->vkey = $key;
 | 
			
		||||
        $conf->vdesc = $svalue;
 | 
			
		||||
        $conf->create();
 | 
			
		||||
        $this->datacache[$key] = $value;
 | 
			
		||||
        $this->dirty[$key] = $conf->id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getVal($key, $default='')
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->datacache === null) {
 | 
			
		||||
            $this->initCache();
 | 
			
		||||
        }
 | 
			
		||||
        return (isset($this->datacache[$key])) ? $this->datacache[$key] : $default;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function delVal($key, $initcache=true)
 | 
			
		||||
    {
 | 
			
		||||
        $gconf = new IDF_Gconf();
 | 
			
		||||
        $sql = new Pluf_SQL('vkey=%s AND model_class=%s AND model_id=%s', array($key, $this->_mod->_model, $this->_mod->id));
 | 
			
		||||
        foreach ($gconf->getList(array('filter' => $sql->gen())) as $c) {
 | 
			
		||||
            $c->delete();
 | 
			
		||||
        }
 | 
			
		||||
        if ($initcache) {
 | 
			
		||||
            $this->initCache();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Collection selection.
 | 
			
		||||
     *
 | 
			
		||||
     * Suppose you have 5 objects with associated meta data in the
 | 
			
		||||
     * Gconf storage, if you load the data independently for each
 | 
			
		||||
     * object, you end up with 5 SELECT queries. With 25 objects, 25
 | 
			
		||||
     * SELECT. You can select with one query all the data and merge in
 | 
			
		||||
     * the code. It is faster. The collection selection get a
 | 
			
		||||
     * model_class and a list of ids and returns an id indexed array
 | 
			
		||||
     * of associative array data. This is for read only access as you
 | 
			
		||||
     * do not get a series of Gconf objects.
 | 
			
		||||
     */
 | 
			
		||||
    public static function collect($class, $ids)
 | 
			
		||||
    {
 | 
			
		||||
        $gconf = new IDF_Gconf();
 | 
			
		||||
        $stmpl = sprintf('model_class=%%s AND model_id IN (%s)', 
 | 
			
		||||
                         implode(',' , $ids));
 | 
			
		||||
        $sql = new Pluf_SQL($stmpl, array($class));
 | 
			
		||||
        $out = array_fill_keys($ids, array());
 | 
			
		||||
        foreach ($gconf->getList(array('filter' => $sql->gen())) as $c) {
 | 
			
		||||
            $out[$c->model_id][$c->vkey] = $c->vdesc;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $out;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Drop the conf of a model.
 | 
			
		||||
     *
 | 
			
		||||
     * If your model is using this table, just add the following line
 | 
			
		||||
     * in your preDelete() method:
 | 
			
		||||
     *
 | 
			
		||||
     * IDF_Gconf::dropForModel($this)
 | 
			
		||||
     *
 | 
			
		||||
     * It will take care of the cleaning.
 | 
			
		||||
     */
 | 
			
		||||
    static public function dropForModel($model)
 | 
			
		||||
    {
 | 
			
		||||
        $table = Pluf::factory(__CLASS__)->getSqlTable();
 | 
			
		||||
        $sql = new Pluf_SQL('model_class=%s AND model_id=%s',
 | 
			
		||||
                            array($model->_model, $model->id));
 | 
			
		||||
        $db = &Pluf::db();
 | 
			
		||||
        $db->execute('DELETE FROM '.$table.' WHERE '.$sql->gen());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static public function dropUser($signal, &$params)
 | 
			
		||||
    {
 | 
			
		||||
        self::dropForModel($params['user']);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -42,9 +42,9 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
                            'id' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  'blank' => true, 
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'project' => 
 | 
			
		||||
                            'project' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'IDF_Project',
 | 
			
		||||
@@ -59,7 +59,7 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
                                  'size' => 250,
 | 
			
		||||
                                  'verbose' => __('summary'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'submitter' => 
 | 
			
		||||
                            'submitter' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
@@ -67,7 +67,7 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
                                  'verbose' => __('submitter'),
 | 
			
		||||
                                  'relate_name' => 'submitted_issue',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'owner' => 
 | 
			
		||||
                            'owner' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
@@ -76,7 +76,7 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
                                  'verbose' => __('owner'),
 | 
			
		||||
                                  'relate_name' => 'owned_issue',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'interested' => 
 | 
			
		||||
                            'interested' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Manytomany',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
@@ -86,14 +86,14 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'tags' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Manytomany', 
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Manytomany',
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  'model' => 'IDF_Tag',
 | 
			
		||||
                                  'verbose' => __('labels'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'status' => 
 | 
			
		||||
                            'status' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey', 
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'model' => 'IDF_Tag',
 | 
			
		||||
                                  'verbose' => __('status'),
 | 
			
		||||
@@ -111,7 +111,7 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
                                  'verbose' => __('modification date'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
        $this->_a['idx'] = array(                           
 | 
			
		||||
        $this->_a['idx'] = array(
 | 
			
		||||
                            'modif_dtime_idx' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'col' => 'modif_dtime',
 | 
			
		||||
@@ -120,7 +120,7 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
                            );
 | 
			
		||||
        $table = $this->_con->pfx.'idf_issue_idf_tag_assoc';
 | 
			
		||||
        $this->_a['views'] = array(
 | 
			
		||||
                              'join_tags' => 
 | 
			
		||||
                              'join_tags' =>
 | 
			
		||||
                              array(
 | 
			
		||||
                                    'join' => 'LEFT JOIN '.$table
 | 
			
		||||
                                    .' ON idf_issue_id=id',
 | 
			
		||||
@@ -164,7 +164,7 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
        // that the issue as at least one comment in the database when
 | 
			
		||||
        // doing the indexing.
 | 
			
		||||
        if ($create) {
 | 
			
		||||
            IDF_Timeline::insert($this, $this->get_project(), 
 | 
			
		||||
            IDF_Timeline::insert($this, $this->get_project(),
 | 
			
		||||
                                 $this->get_submitter());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -177,12 +177,12 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
     * as such create links to other items etc. You can consider that
 | 
			
		||||
     * if displayed, you can create a link to it.
 | 
			
		||||
     *
 | 
			
		||||
     * @param Pluf_HTTP_Request 
 | 
			
		||||
     * @param Pluf_HTTP_Request
 | 
			
		||||
     * @return Pluf_Template_SafeString
 | 
			
		||||
     */
 | 
			
		||||
    public function timelineFragment($request)
 | 
			
		||||
    {
 | 
			
		||||
        $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', 
 | 
			
		||||
        $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
 | 
			
		||||
                                        array($request->project->shortname,
 | 
			
		||||
                                              $this->id));
 | 
			
		||||
        $out = '<tr class="log"><td><a href="'.$url.'">'.
 | 
			
		||||
@@ -193,38 +193,136 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
        $ic = (in_array($this->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
 | 
			
		||||
        $out .= sprintf(__('<a href="%1$s" class="%2$s" title="View issue">Issue %3$d</a>, %4$s'), $url, $ic, $this->id, Pluf_esc($this->summary)).'</td>';
 | 
			
		||||
        $out .= "\n".'<tr class="extra"><td colspan="2">
 | 
			
		||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">issue %d</a>'), $url, $ic, $this->id).', '.__('by').' '.$user.'</div></td></tr>'; 
 | 
			
		||||
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">issue %d</a>, by %s'), $url, $ic, $this->id, $user).'</div></td></tr>';
 | 
			
		||||
        return Pluf_Template::markSafe($out);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function feedFragment($request)
 | 
			
		||||
    {
 | 
			
		||||
        $base = '<entry>
 | 
			
		||||
   <title>%%title%%</title>
 | 
			
		||||
   <link href="%%url%%"/>
 | 
			
		||||
   <id>%%url%%</id>
 | 
			
		||||
   <updated>%%date%%</updated>
 | 
			
		||||
   <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
 | 
			
		||||
   <pre>%%content%%</pre>
 | 
			
		||||
   </div></content>
 | 
			
		||||
</entry>';
 | 
			
		||||
        $url = Pluf::f('url_base')
 | 
			
		||||
            .Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', 
 | 
			
		||||
            .Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
 | 
			
		||||
                                      array($request->project->shortname,
 | 
			
		||||
                                            $this->id));
 | 
			
		||||
        $title = sprintf(__('%s: Issue %d created - %s'),
 | 
			
		||||
                         Pluf_esc($request->project->name),
 | 
			
		||||
                         $this->id, Pluf_esc($this->summary));
 | 
			
		||||
        // Get the first comment of this issue.
 | 
			
		||||
                         $request->project->name,
 | 
			
		||||
                         $this->id, $this->summary);
 | 
			
		||||
        $cts = $this->get_comments_list(array('order' => 'id ASC',
 | 
			
		||||
                                              'nb' => 1));
 | 
			
		||||
        $tag = new IDF_Template_IssueComment();
 | 
			
		||||
        $content = $tag->start($cts[0]->content, $request, false);
 | 
			
		||||
        $date = Pluf_Date::gmDateToGmString($this->creation_dtime);
 | 
			
		||||
        return Pluf_Translation::sprintf($base,
 | 
			
		||||
                                         array('url' => $url,
 | 
			
		||||
                                               'title' => $title,
 | 
			
		||||
                                               'content' => $content,
 | 
			
		||||
                                               'date' => $date));
 | 
			
		||||
        $context = new Pluf_Template_Context_Request(
 | 
			
		||||
                       $request,
 | 
			
		||||
                       array('url' => $url,
 | 
			
		||||
                             'author' => $this->get_submitter(),
 | 
			
		||||
                             'title' => $title,
 | 
			
		||||
                             'c' => $cts[0],
 | 
			
		||||
                             'issue' => $this,
 | 
			
		||||
                             'date' => $date)
 | 
			
		||||
                                                     );
 | 
			
		||||
        $tmpl = new Pluf_Template('idf/issues/feedfragment.xml');
 | 
			
		||||
        return $tmpl->render($context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notification of change of the object.
 | 
			
		||||
     *
 | 
			
		||||
     * For the moment, only email, but one can add webhooks later.
 | 
			
		||||
     *
 | 
			
		||||
     * Usage:
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * $this->notify($conf); // Notify the creation
 | 
			
		||||
     * $this->notify($conf, false); // Notify the update of the object
 | 
			
		||||
     * </pre>
 | 
			
		||||
     *
 | 
			
		||||
     * @param IDF_Conf Current configuration
 | 
			
		||||
     * @param bool Creation (true)
 | 
			
		||||
     */
 | 
			
		||||
    public function notify($conf, $create=true)
 | 
			
		||||
    {
 | 
			
		||||
        $prj = $this->get_project();
 | 
			
		||||
        $to_email = array();
 | 
			
		||||
        if ('' != $conf->getVal('issues_notification_email', '')) {
 | 
			
		||||
            $langs     = Pluf::f('languages', array('en'));
 | 
			
		||||
            $addresses = explode(',', $conf->getVal('issues_notification_email'));
 | 
			
		||||
            foreach ($addresses as $address) {
 | 
			
		||||
                $to_email[] = array($address, $langs[0]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $current_locale = Pluf_Translation::getLocale();
 | 
			
		||||
        $id = '<'.md5($this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
 | 
			
		||||
        if ($create) {
 | 
			
		||||
            if (null != $this->get_owner() and $this->owner != $this->submitter) {
 | 
			
		||||
                $email_lang = array($this->get_owner()->email,
 | 
			
		||||
                                    $this->get_owner()->language);
 | 
			
		||||
                if (!in_array($email_lang, $to_email)) {
 | 
			
		||||
                    $to_email[] = $email_lang;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $comments = $this->get_comments_list(array('order' => 'id ASC'));
 | 
			
		||||
            $context = new Pluf_Template_Context(
 | 
			
		||||
                                   array(
 | 
			
		||||
                                         'issue' => $this,
 | 
			
		||||
                                         'comment' => $comments[0],
 | 
			
		||||
                                         'project' => $prj,
 | 
			
		||||
                                         'url_base' => Pluf::f('url_base'),
 | 
			
		||||
                                         )
 | 
			
		||||
                                                 );
 | 
			
		||||
            foreach ($to_email as $email_lang) {
 | 
			
		||||
                Pluf_Translation::loadSetLocale($email_lang[1]);
 | 
			
		||||
                $email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
 | 
			
		||||
                                       sprintf(__('Issue %s - %s (%s)'),
 | 
			
		||||
                                               $this->id, $this->summary, $prj->shortname));
 | 
			
		||||
                $tmpl = new Pluf_Template('idf/issues/issue-created-email.txt');
 | 
			
		||||
                $email->addTextMessage($tmpl->render($context));
 | 
			
		||||
                $email->addHeaders(array('Message-ID'=>$id));
 | 
			
		||||
                $email->sendMail();
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            $comments = $this->get_comments_list(array('order' => 'id DESC'));
 | 
			
		||||
            $email_sender = '';
 | 
			
		||||
            if (isset($comments[0])) {
 | 
			
		||||
                $email_sender = $comments[0]->get_submitter()->email;
 | 
			
		||||
            }
 | 
			
		||||
            foreach ($this->get_interested_list() as $interested) {
 | 
			
		||||
                $email_lang = array($interested->email,
 | 
			
		||||
                                    $interested->language);
 | 
			
		||||
                if (!in_array($email_lang, $to_email)) {
 | 
			
		||||
                    $to_email[] = $email_lang;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $email_lang = array($this->get_submitter()->email,
 | 
			
		||||
                                $this->get_submitter()->language);
 | 
			
		||||
            if (!in_array($email_lang, $to_email)) {
 | 
			
		||||
                $to_email[] = $email_lang;
 | 
			
		||||
            }
 | 
			
		||||
            if (null != $this->get_owner()) {
 | 
			
		||||
                $email_lang = array($this->get_owner()->email,
 | 
			
		||||
                                    $this->get_owner()->language);
 | 
			
		||||
                if (!in_array($email_lang, $to_email)) {
 | 
			
		||||
                    $to_email[] = $email_lang;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $context = new Pluf_Template_Context(
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'issue' => $this,
 | 
			
		||||
                                  'comments' => $comments,
 | 
			
		||||
                                  'project' => $prj,
 | 
			
		||||
                                  'url_base' => Pluf::f('url_base'),
 | 
			
		||||
                                  ));
 | 
			
		||||
            foreach ($to_email as $email_lang) {
 | 
			
		||||
                if ($email_lang[0] == $email_sender) {
 | 
			
		||||
                    continue; // Do not notify the one having created
 | 
			
		||||
                              // the comment
 | 
			
		||||
                }
 | 
			
		||||
                Pluf_Translation::loadSetLocale($email_lang[1]);
 | 
			
		||||
                $email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
 | 
			
		||||
                                       sprintf(__('Updated Issue %s - %s (%s)'),
 | 
			
		||||
                                               $this->id, $this->summary, $prj->shortname));
 | 
			
		||||
                $tmpl = new Pluf_Template('idf/issues/issue-updated-email.txt');
 | 
			
		||||
                $email->addTextMessage($tmpl->render($context));
 | 
			
		||||
                $email->addHeaders(array('References'=>$id));
 | 
			
		||||
                $email->sendMail();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Pluf_Translation::loadSetLocale($current_locale);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -41,9 +41,9 @@ class IDF_IssueComment extends Pluf_Model
 | 
			
		||||
                            'id' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  'blank' => true, 
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'issue' => 
 | 
			
		||||
                            'issue' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'IDF_Issue',
 | 
			
		||||
@@ -57,7 +57,7 @@ class IDF_IssueComment extends Pluf_Model
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('comment'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'submitter' => 
 | 
			
		||||
                            'submitter' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
@@ -79,7 +79,7 @@ class IDF_IssueComment extends Pluf_Model
 | 
			
		||||
                                  'verbose' => __('creation date'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
        $this->_a['idx'] = array(                           
 | 
			
		||||
        $this->_a['idx'] = array(
 | 
			
		||||
                            'creation_dtime_idx' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'col' => 'creation_dtime',
 | 
			
		||||
@@ -119,7 +119,7 @@ class IDF_IssueComment extends Pluf_Model
 | 
			
		||||
            $sql = new Pluf_SQL('issue=%s', array($this->issue));
 | 
			
		||||
            $co = Pluf::factory('IDF_IssueComment')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
            if ($co->count() > 1) {
 | 
			
		||||
                IDF_Timeline::insert($this, $this->get_issue()->get_project(), 
 | 
			
		||||
                IDF_Timeline::insert($this, $this->get_issue()->get_project(),
 | 
			
		||||
                                     $this->get_submitter());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -129,7 +129,7 @@ class IDF_IssueComment extends Pluf_Model
 | 
			
		||||
    public function timelineFragment($request)
 | 
			
		||||
    {
 | 
			
		||||
        $issue = $this->get_issue();
 | 
			
		||||
        $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', 
 | 
			
		||||
        $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
 | 
			
		||||
                                        array($request->project->shortname,
 | 
			
		||||
                                              $issue->id));
 | 
			
		||||
        $url .= '#ic'.$this->id;
 | 
			
		||||
@@ -167,66 +167,38 @@ class IDF_IssueComment extends Pluf_Model
 | 
			
		||||
            $out .= '</div>';
 | 
			
		||||
        }
 | 
			
		||||
        $out .= '</td></tr>';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $out .= "\n".'<tr class="extra"><td colspan="2">
 | 
			
		||||
<div class="helptext right">'.sprintf(__('Comment on <a href="%s" class="%s">issue %d</a>'), $url, $ic, $issue->id).', '.__('by').' '.$user.'</div></td></tr>'; 
 | 
			
		||||
 | 
			
		||||
<div class="helptext right">'.sprintf(__('Comment on <a href="%s" class="%s">issue %d</a>, by %s'), $url, $ic, $issue->id, $user).'</div></td></tr>';
 | 
			
		||||
        return Pluf_Template::markSafe($out);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function feedFragment($request)
 | 
			
		||||
    {
 | 
			
		||||
        $base = '<entry>
 | 
			
		||||
   <title>%%title%%</title>
 | 
			
		||||
   <link href="%%url%%"/>
 | 
			
		||||
   <id>%%url%%</id>
 | 
			
		||||
   <updated>%%date%%</updated>
 | 
			
		||||
   <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
 | 
			
		||||
   %%content%%
 | 
			
		||||
   </div></content>
 | 
			
		||||
</entry>';
 | 
			
		||||
        $issue = $this->get_issue();
 | 
			
		||||
        $url = Pluf::f('url_base')
 | 
			
		||||
            .Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', 
 | 
			
		||||
            .Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
 | 
			
		||||
                                      array($request->project->shortname,
 | 
			
		||||
                                            $issue->id));
 | 
			
		||||
        $url .= '#ic'.$this->id;
 | 
			
		||||
        $title = sprintf(__('%s: Comment on issue %d - %s'),
 | 
			
		||||
                         Pluf_esc($request->project->name),
 | 
			
		||||
                         $issue->id, Pluf_esc($issue->summary));
 | 
			
		||||
        $submitter = $this->get_submitter();
 | 
			
		||||
        $tag = new IDF_Template_IssueComment();
 | 
			
		||||
        $content = '<p><pre>'.$tag->start($this->content, $request, false).'</pre></p>';
 | 
			
		||||
        if ($this->changedIssue()) {
 | 
			
		||||
            $content .= '<p>';
 | 
			
		||||
            foreach ($this->changes as $w => $v) {
 | 
			
		||||
                $content .= '<strong>';
 | 
			
		||||
                switch ($w) {
 | 
			
		||||
                case 'su':
 | 
			
		||||
                    $content .= __('Summary:'); break;
 | 
			
		||||
                case 'st':
 | 
			
		||||
                    $content .= __('Status:'); break;
 | 
			
		||||
                case 'ow':
 | 
			
		||||
                    $content .= __('Owner:'); break;
 | 
			
		||||
                case 'lb':
 | 
			
		||||
                    $content .= __('Labels:'); break;
 | 
			
		||||
                }
 | 
			
		||||
                $content .= '</strong> ';
 | 
			
		||||
                if ($w == 'lb') {
 | 
			
		||||
                    $content .= Pluf_esc(implode(', ', $v));
 | 
			
		||||
                } else {
 | 
			
		||||
                    $content .= Pluf_esc($v);
 | 
			
		||||
                }
 | 
			
		||||
                $content .= ' ';
 | 
			
		||||
            }
 | 
			
		||||
            $content .= '</p>';
 | 
			
		||||
        }
 | 
			
		||||
        $url .= '#ic'.$this->id;
 | 
			
		||||
        $date = Pluf_Date::gmDateToGmString($this->creation_dtime);
 | 
			
		||||
        return Pluf_Translation::sprintf($base,
 | 
			
		||||
                                         array('url' => $url,
 | 
			
		||||
                                               'title' => $title,
 | 
			
		||||
                                               'content' => $content,
 | 
			
		||||
                                               'date' => $date));
 | 
			
		||||
        $context = new Pluf_Template_Context_Request(
 | 
			
		||||
                       $request,
 | 
			
		||||
                       array('url' => $url,
 | 
			
		||||
                             'author' => $issue->get_submitter(),
 | 
			
		||||
                             'title' => $title,
 | 
			
		||||
                             'c' => $this,
 | 
			
		||||
                             'issue' => $issue,
 | 
			
		||||
                             'date' => $date)
 | 
			
		||||
                                                     );
 | 
			
		||||
        $tmpl = new Pluf_Template('idf/issues/feedfragment.xml');
 | 
			
		||||
        return $tmpl->render($context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function get_submitter_data()
 | 
			
		||||
    {
 | 
			
		||||
        return IDF_UserData::factory($this->get_submitter());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,9 +39,9 @@ class IDF_IssueFile extends Pluf_Model
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  //It is automatically added.
 | 
			
		||||
                                  'blank' => true, 
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'comment' => 
 | 
			
		||||
                            'comment' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'IDF_IssueComment',
 | 
			
		||||
@@ -49,7 +49,7 @@ class IDF_IssueFile extends Pluf_Model
 | 
			
		||||
                                  'verbose' => __('comment'),
 | 
			
		||||
                                  'relate_name' => 'attachment',
 | 
			
		||||
                                   ),
 | 
			
		||||
                            'submitter' => 
 | 
			
		||||
                            'submitter' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
@@ -63,7 +63,7 @@ class IDF_IssueFile extends Pluf_Model
 | 
			
		||||
                                  'size' => 100,
 | 
			
		||||
                                  'verbose' => __('file name'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'attachment' => 
 | 
			
		||||
                            'attachment' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_File',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
@@ -76,7 +76,7 @@ class IDF_IssueFile extends Pluf_Model
 | 
			
		||||
                                  'verbose' => __('file size'),
 | 
			
		||||
                                  'help_text' => 'Size in bytes.',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'type' => 
 | 
			
		||||
                            'type' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Varchar',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
@@ -111,7 +111,7 @@ class IDF_IssueFile extends Pluf_Model
 | 
			
		||||
            $file = Pluf::f('upload_issue_path').'/'.$this->attachment;
 | 
			
		||||
            $this->filesize = filesize($file);
 | 
			
		||||
            // remove .dummy
 | 
			
		||||
            $this->filename = substr(basename($file), 0, -6); 
 | 
			
		||||
            $this->filename = substr(basename($file), 0, -6);
 | 
			
		||||
            $img_extensions = array('jpeg', 'jpg', 'png', 'gif');
 | 
			
		||||
            $info = pathinfo($this->filename);
 | 
			
		||||
            if (!isset($info['extension'])) $info['extension'] = '';
 | 
			
		||||
@@ -128,4 +128,10 @@ class IDF_IssueFile extends Pluf_Model
 | 
			
		||||
    {
 | 
			
		||||
        @unlink(Pluf::f('upload_issue_path').'/'.$this->attachment);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function isText()
 | 
			
		||||
    {
 | 
			
		||||
        $info = IDF_FileUtil::getMimeType($this->filename);
 | 
			
		||||
        return IDF_FileUtil::isText($info);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Storage of the SSH keys.
 | 
			
		||||
 * Storage of the public keys (ssh or monotone).
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Key extends Pluf_Model
 | 
			
		||||
@@ -39,9 +39,9 @@ class IDF_Key extends Pluf_Model
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  //It is automatically added.
 | 
			
		||||
                                  'blank' => true, 
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'user' => 
 | 
			
		||||
                            'user' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
@@ -52,14 +52,14 @@ class IDF_Key extends Pluf_Model
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Text',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('ssh key'),
 | 
			
		||||
                                  'verbose' => __('public key'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
        // WARNING: Not using getSqlTable on the Pluf_User object to
 | 
			
		||||
        // avoid recursion.
 | 
			
		||||
        $t_users = $this->_con->pfx.'users'; 
 | 
			
		||||
        $t_users = $this->_con->pfx.'users';
 | 
			
		||||
        $this->_a['views'] = array(
 | 
			
		||||
                              'join_user' => 
 | 
			
		||||
                              'join_user' =>
 | 
			
		||||
                              array(
 | 
			
		||||
                                    'join' => 'LEFT JOIN '.$t_users
 | 
			
		||||
                                    .' ON '.$t_users.'.id='.$this->_con->qn('user'),
 | 
			
		||||
@@ -70,6 +70,63 @@ class IDF_Key extends Pluf_Model
 | 
			
		||||
                                   );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showCompact()
 | 
			
		||||
    {
 | 
			
		||||
        return Pluf_Template::markSafe(Pluf_esc(substr($this->content, 0, 25)).' [...] '.Pluf_esc(substr($this->content, -55)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function parseContent()
 | 
			
		||||
    {
 | 
			
		||||
        if (preg_match('#^\[pubkey ([^\]]+)\]\s*(\S+)\s*\[end\]$#', $this->content, $m)) {
 | 
			
		||||
            return array('mtn', $m[1], $m[2]);
 | 
			
		||||
        }
 | 
			
		||||
        else if (preg_match('#^ssh\-[a-z]{3}\s(\S+)(?:\s(\S+))?$#', $this->content, $m)) {
 | 
			
		||||
            return array('ssh', $m[2], $m[1]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new Exception(__('Invalid or unknown key data detected.'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the type of the public key
 | 
			
		||||
     *
 | 
			
		||||
     * @return string 'ssh' or 'mtn'
 | 
			
		||||
     */
 | 
			
		||||
    function getType()
 | 
			
		||||
    {
 | 
			
		||||
        list($type, , ) = $this->parseContent();
 | 
			
		||||
        return $type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the key name of the key
 | 
			
		||||
     *
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    function getName()
 | 
			
		||||
    {
 | 
			
		||||
        list(, $keyName, ) = $this->parseContent();
 | 
			
		||||
        return $keyName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This function should be used to calculate the key id from the
 | 
			
		||||
     * public key hash for authentication purposes. This avoids clashes
 | 
			
		||||
     * in case the key name is not unique across the project
 | 
			
		||||
     *
 | 
			
		||||
     * And yes, this is actually how monotone itself calculates the key
 | 
			
		||||
     * id...
 | 
			
		||||
     *
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    function getMtnId()
 | 
			
		||||
    {
 | 
			
		||||
        list($type, $keyName, $keyData) = $this->parseContent();
 | 
			
		||||
        if ($type != 'mtn')
 | 
			
		||||
            throw new Exception('key is not a monotone public key');
 | 
			
		||||
        return sha1($keyName.":".$keyData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function postSave($create=false)
 | 
			
		||||
    {
 | 
			
		||||
        /**
 | 
			
		||||
@@ -84,7 +141,7 @@ class IDF_Key extends Pluf_Model
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal allows an application to perform special
 | 
			
		||||
         * operations after the saving of a SSH Key.
 | 
			
		||||
         * operations after the saving of a public Key.
 | 
			
		||||
         *
 | 
			
		||||
         * [parameters]
 | 
			
		||||
         *
 | 
			
		||||
@@ -97,4 +154,29 @@ class IDF_Key extends Pluf_Model
 | 
			
		||||
                          'IDF_Key', $params);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function preDelete()
 | 
			
		||||
    {
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Key::preDelete
 | 
			
		||||
         *
 | 
			
		||||
         * [sender]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Key
 | 
			
		||||
         *
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal allows an application to perform special
 | 
			
		||||
         * operations before a key is deleted.
 | 
			
		||||
         *
 | 
			
		||||
         * [parameters]
 | 
			
		||||
         *
 | 
			
		||||
         * array('key' => $key)
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        $params = array('key' => $this);
 | 
			
		||||
        Pluf_Signal::send('IDF_Key::preDelete',
 | 
			
		||||
                          'IDF_Key', $params);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -87,11 +87,13 @@ class IDF_Middleware
 | 
			
		||||
                              'markdown' => 'IDF_Template_Markdown',
 | 
			
		||||
                              'showuser' => 'IDF_Template_ShowUser',
 | 
			
		||||
                              'ashowuser' => 'IDF_Template_AssignShowUser',
 | 
			
		||||
                              'appversion' => 'IDF_Template_AppVersion',
 | 
			
		||||
                                            ));
 | 
			
		||||
        $params['modifiers'] = array_merge($params['modifiers'],
 | 
			
		||||
                                           array(
 | 
			
		||||
                                   'size' => 'IDF_Views_Source_PrettySize',
 | 
			
		||||
                                   'ssize' => 'IDF_Views_Source_PrettySizeSimple',
 | 
			
		||||
                                   'shorten' => 'IDF_Views_Source_ShortenString',
 | 
			
		||||
                                                 ));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -104,12 +106,14 @@ function IDF_Middleware_ContextPreProcessor($request)
 | 
			
		||||
    $c['isAdmin'] = ($request->user->administrator or $request->user->staff);
 | 
			
		||||
    if (isset($request->project)) {
 | 
			
		||||
        $c['project'] = $request->project;
 | 
			
		||||
        $c['isOwner'] = $request->user->hasPerm('IDF.project-owner', 
 | 
			
		||||
        $c['isOwner'] = $request->user->hasPerm('IDF.project-owner',
 | 
			
		||||
                                                $request->project);
 | 
			
		||||
        $c['isMember'] = $request->user->hasPerm('IDF.project-member', 
 | 
			
		||||
        $c['isMember'] = $request->user->hasPerm('IDF.project-member',
 | 
			
		||||
                                                 $request->project);
 | 
			
		||||
        $c = array_merge($c, $request->rights);
 | 
			
		||||
    }
 | 
			
		||||
    $c['usherConfigured'] = Pluf::f("mtn_usher_conf", null) !== null;
 | 
			
		||||
    $c['allProjects'] = IDF_Views::getProjects($request->user);
 | 
			
		||||
    return $c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								src/IDF/Migrations/12DownloadDesc.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/IDF/Migrations/12DownloadDesc.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the private column for the project.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_12DownloadDesc_up($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $table = Pluf::factory('IDF_Upload')->getSqlTable();
 | 
			
		||||
    $sql = array();
 | 
			
		||||
    $sql['PostgreSQL'] = 'ALTER TABLE '.$table.' ADD COLUMN "changelog" TEXT DEFAULT \'\'';
 | 
			
		||||
    $sql['MySQL'] = 'ALTER TABLE '.$table.' ADD COLUMN `changelog` LONGTEXT DEFAULT \'\'';
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $engine = Pluf::f('db_engine');
 | 
			
		||||
    if (!isset($sql[$engine])) {
 | 
			
		||||
        throw new Exception('SQLite complex migration not supported.');
 | 
			
		||||
    }
 | 
			
		||||
    $db->execute($sql[$engine]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_12DownloadDesc_down($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $table = Pluf::factory('IDF_Upload')->getSqlTable();
 | 
			
		||||
    $sql = array();
 | 
			
		||||
    $sql['PostgreSQL'] = 'ALTER TABLE '.$table.' DROP COLUMN "changelog"';
 | 
			
		||||
    $sql['MySQL'] = 'ALTER TABLE '.$table.' DROP COLUMN `changelog`';
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $engine = Pluf::f('db_engine');
 | 
			
		||||
    if (!isset($sql[$engine])) {
 | 
			
		||||
        throw new Exception('SQLite complex migration not supported.');
 | 
			
		||||
    }
 | 
			
		||||
    $db->execute($sql[$engine]);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								src/IDF/Migrations/13NewReview.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/IDF/Migrations/13NewReview.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove the old review and add the new one.
 | 
			
		||||
 *
 | 
			
		||||
 * This is a destructive operation.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_13NewReview_up($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $extra = (Pluf::f('db_engine') == 'PostgreSQL') ? ' CASCADE' : '';
 | 
			
		||||
    $pfx = Pluf::f('db_table_prefix');
 | 
			
		||||
    $tables = array('idf_review_filecomments',
 | 
			
		||||
                    'idf_review_patches',
 | 
			
		||||
                    'idf_review_pluf_user_assoc',
 | 
			
		||||
                    'idf_review_idf_tag_assoc',
 | 
			
		||||
                    'idf_reviews');
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    foreach ($tables as $table) {
 | 
			
		||||
        $db->execute('DROP TABLE IF EXISTS '.$pfx.$table.$extra);
 | 
			
		||||
    }
 | 
			
		||||
    $models = array(
 | 
			
		||||
                    'IDF_Review',
 | 
			
		||||
                    'IDF_Review_Patch',
 | 
			
		||||
                    'IDF_Review_Comment',
 | 
			
		||||
                    'IDF_Review_FileComment',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
    foreach ($models as $model) {
 | 
			
		||||
        $schema->model = new $model();
 | 
			
		||||
        $schema->createTables();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_13NewReview_down($params=null)
 | 
			
		||||
{
 | 
			
		||||
    // We do nothing as we cannot go back to the old reviews
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								src/IDF/Migrations/14Queue.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/IDF/Migrations/14Queue.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the new IDF_Queue model.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_14Queue_up($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $models = array(
 | 
			
		||||
                    'IDF_Queue',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
    foreach ($models as $model) {
 | 
			
		||||
        $schema->model = new $model();
 | 
			
		||||
        $schema->createTables();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_14Queue_down($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $models = array(
 | 
			
		||||
                    'IDF_Queue',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
    foreach ($models as $model) {
 | 
			
		||||
        $schema->model = new $model();
 | 
			
		||||
        $schema->dropTables();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								src/IDF/Migrations/15AddGconf.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/IDF/Migrations/15AddGconf.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the new IDF_Gconf model.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_15AddGconf_up($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $models = array(
 | 
			
		||||
                    'IDF_Gconf',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
    foreach ($models as $model) {
 | 
			
		||||
        $schema->model = new $model();
 | 
			
		||||
        $schema->createTables();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_15AddGconf_down($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $models = array(
 | 
			
		||||
                    'IDF_Gconf',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
    foreach ($models as $model) {
 | 
			
		||||
        $schema->model = new $model();
 | 
			
		||||
        $schema->dropTables();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								src/IDF/Migrations/16AddUserMail.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/IDF/Migrations/16AddUserMail.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the new IDF_Gconf model.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_16AddUserMail_up($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
    $schema->model = new IDF_EmailAddress();
 | 
			
		||||
    $schema->createTables();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_16AddUserMail_down($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
    $schema->model = new IDF_EmailAddress();
 | 
			
		||||
    $schema->dropTables();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										115
									
								
								src/IDF/Migrations/Backup.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/IDF/Migrations/Backup.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Backup of InDefero.
 | 
			
		||||
 *
 | 
			
		||||
 * !! You need also to backup Pluf if you want the full backup. !!
 | 
			
		||||
 *
 | 
			
		||||
 * @param string Path to the folder where to store the backup
 | 
			
		||||
 * @param string Name of the backup (null)
 | 
			
		||||
 * @return int The backup was correctly written
 | 
			
		||||
 */
 | 
			
		||||
function IDF_Migrations_Backup_run($folder, $name=null)
 | 
			
		||||
{
 | 
			
		||||
    $models = array(
 | 
			
		||||
                    'IDF_Project',
 | 
			
		||||
                    'IDF_Tag',
 | 
			
		||||
                    'IDF_Issue',
 | 
			
		||||
                    'IDF_IssueComment',
 | 
			
		||||
                    'IDF_Conf',
 | 
			
		||||
                    'IDF_Upload',
 | 
			
		||||
                    'IDF_Search_Occ',
 | 
			
		||||
                    'IDF_IssueFile',
 | 
			
		||||
                    'IDF_Commit',
 | 
			
		||||
                    'IDF_Timeline',
 | 
			
		||||
                    'IDF_WikiPage',
 | 
			
		||||
                    'IDF_WikiRevision',
 | 
			
		||||
                    'IDF_Review',
 | 
			
		||||
                    'IDF_Review_Patch',
 | 
			
		||||
                    'IDF_Review_Comment',
 | 
			
		||||
                    'IDF_Review_FileComment',
 | 
			
		||||
                    'IDF_Key',
 | 
			
		||||
                    'IDF_Scm_Cache_Git',
 | 
			
		||||
                    'IDF_Queue',
 | 
			
		||||
                    'IDF_Gconf',
 | 
			
		||||
                    'IDF_EmailAddress',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    // Now, for each table, we dump the content in json, this is a
 | 
			
		||||
    // memory intensive operation
 | 
			
		||||
    $to_json = array();
 | 
			
		||||
    foreach ($models as $model) {
 | 
			
		||||
        $to_json[$model] = Pluf_Test_Fixture::dump($model, false);
 | 
			
		||||
    }
 | 
			
		||||
    if (null == $name) {
 | 
			
		||||
        $name = date('Y-m-d');
 | 
			
		||||
    }
 | 
			
		||||
    return file_put_contents(sprintf('%s/%s-IDF.json', $folder, $name),
 | 
			
		||||
                             json_encode($to_json), LOCK_EX);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Restore IDF from a backup.
 | 
			
		||||
 *
 | 
			
		||||
 * @param string Path to the backup folder
 | 
			
		||||
 * @param string Backup name
 | 
			
		||||
 * @return bool Success
 | 
			
		||||
 */
 | 
			
		||||
function IDF_Migrations_Backup_restore($folder, $name)
 | 
			
		||||
{
 | 
			
		||||
    $models = array(
 | 
			
		||||
                    'IDF_Project',
 | 
			
		||||
                    'IDF_Tag',
 | 
			
		||||
                    'IDF_Issue',
 | 
			
		||||
                    'IDF_IssueComment',
 | 
			
		||||
                    'IDF_Conf',
 | 
			
		||||
                    'IDF_Upload',
 | 
			
		||||
                    'IDF_Search_Occ',
 | 
			
		||||
                    'IDF_IssueFile',
 | 
			
		||||
                    'IDF_Commit',
 | 
			
		||||
                    'IDF_Timeline',
 | 
			
		||||
                    'IDF_WikiPage',
 | 
			
		||||
                    'IDF_WikiRevision',
 | 
			
		||||
                    'IDF_Review',
 | 
			
		||||
                    'IDF_Review_Patch',
 | 
			
		||||
                    'IDF_Review_Comment',
 | 
			
		||||
                    'IDF_Review_FileComment',
 | 
			
		||||
                    'IDF_Key',
 | 
			
		||||
                    'IDF_Scm_Cache_Git',
 | 
			
		||||
                    'IDF_Queue',
 | 
			
		||||
                    'IDF_Gconf',
 | 
			
		||||
                    'IDF_EmailAddress',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
    foreach ($models as $model) {
 | 
			
		||||
        $schema->model = new $model();
 | 
			
		||||
        $schema->createTables();
 | 
			
		||||
    }
 | 
			
		||||
    $full_data = json_decode(file_get_contents(sprintf('%s/%s-IDF.json', $folder, $name)), true);
 | 
			
		||||
    foreach ($full_data as $model => $data) {
 | 
			
		||||
        Pluf_Test_Fixture::load($data, false);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
@@ -44,8 +44,13 @@ function IDF_Migrations_Install_setup($params=null)
 | 
			
		||||
                    'IDF_WikiRevision',
 | 
			
		||||
                    'IDF_Review',
 | 
			
		||||
                    'IDF_Review_Patch',
 | 
			
		||||
                    'IDF_Review_Comment',
 | 
			
		||||
                    'IDF_Review_FileComment',
 | 
			
		||||
                    'IDF_Key',
 | 
			
		||||
                    'IDF_Scm_Cache_Git',
 | 
			
		||||
                    'IDF_Queue',
 | 
			
		||||
                    'IDF_Gconf',
 | 
			
		||||
                    'IDF_EmailAddress',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
@@ -83,8 +88,12 @@ function IDF_Migrations_Install_teardown($params=null)
 | 
			
		||||
    $perm = Pluf_Permission::getFromString('IDF.project-authorized-user');
 | 
			
		||||
    if ($perm) $perm->delete();
 | 
			
		||||
    $models = array(
 | 
			
		||||
                    'IDF_Gconf',
 | 
			
		||||
                    'IDF_Queue',
 | 
			
		||||
                    'IDF_Scm_Cache_Git',
 | 
			
		||||
                    'IDF_Key',
 | 
			
		||||
                    'IDF_Review_FileComment',
 | 
			
		||||
                    'IDF_Review_Comment',
 | 
			
		||||
                    'IDF_Review_Patch',
 | 
			
		||||
                    'IDF_Review',
 | 
			
		||||
                    'IDF_WikiRevision',
 | 
			
		||||
@@ -99,6 +108,7 @@ function IDF_Migrations_Install_teardown($params=null)
 | 
			
		||||
                    'IDF_Tag',
 | 
			
		||||
                    'IDF_Commit',
 | 
			
		||||
                    'IDF_Project',
 | 
			
		||||
                    'IDF_EmailAddress',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
@@ -106,4 +116,4 @@ function IDF_Migrations_Install_teardown($params=null)
 | 
			
		||||
        $schema->model = new $model();
 | 
			
		||||
        $schema->dropTables();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,9 +44,38 @@ class IDF_Plugin_SyncGit
 | 
			
		||||
    {
 | 
			
		||||
        // First check for the single mandatory config variable.
 | 
			
		||||
        if (!Pluf::f('idf_plugin_syncgit_sync_file', false)) {
 | 
			
		||||
            Pluf_Log::debug('IDF_Plugin_SyncGit plugin not configured.');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        @touch(Pluf::f('idf_plugin_syncgit_sync_file'));
 | 
			
		||||
        @chmod(Pluf::f('idf_plugin_syncgit_sync_file'), 0666);
 | 
			
		||||
        if ($signal != 'gitpostupdate.php::run') {
 | 
			
		||||
            Pluf_Log::event('IDF_Plugin_SyncGit', 'create', 
 | 
			
		||||
                            Pluf::f('idf_plugin_syncgit_sync_file'));
 | 
			
		||||
            @touch(Pluf::f('idf_plugin_syncgit_sync_file'));
 | 
			
		||||
            @chmod(Pluf::f('idf_plugin_syncgit_sync_file'), 0777);
 | 
			
		||||
        } else {
 | 
			
		||||
            self::postUpdate($signal, $params);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Entry point for the post-update signal.
 | 
			
		||||
     *
 | 
			
		||||
     * It tries to find the name of the project, when found it runs an
 | 
			
		||||
     * update of the timeline.
 | 
			
		||||
     */
 | 
			
		||||
    static public function postUpdate($signal, &$params)
 | 
			
		||||
    {
 | 
			
		||||
        // Chop the ".git" and get what is left
 | 
			
		||||
        $pname = basename($params['git_dir'], '.git');
 | 
			
		||||
        try {
 | 
			
		||||
            $project = IDF_Project::getOr404($pname);
 | 
			
		||||
        } catch (Pluf_HTTP_Error404 $e) {
 | 
			
		||||
            Pluf_Log::event(array('IDF_Plugin_SyncGit::postUpdate', 'Project not found.', array($pname, $params)));
 | 
			
		||||
            return false; // Project not found
 | 
			
		||||
        }
 | 
			
		||||
        // Now we have the project and can update the timeline
 | 
			
		||||
        Pluf_Log::debug(array('IDF_Plugin_SyncGit::postUpdate', 'Project found', $pname, $project->id));
 | 
			
		||||
        IDF_Scm::syncTimeline($project, true);
 | 
			
		||||
        Pluf_Log::event(array('IDF_Plugin_SyncGit::postUpdate', 'sync', array($pname, $project->id)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ class IDF_Plugin_SyncGit_Cron
 | 
			
		||||
        $cmd = Pluf::f('idf_plugin_syncgit_path_gitserve', '/dev/null');
 | 
			
		||||
        $authorized_keys = Pluf::f('idf_plugin_syncgit_path_authorized_keys', false);
 | 
			
		||||
        if (false == $authorized_keys) {
 | 
			
		||||
            throw new Pluf_Exception_SettingError('Setting git_path_authorized_keys not set.');
 | 
			
		||||
            throw new Pluf_Exception_SettingError('Setting idf_plugin_syncgit_path_authorized_keys not set.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!is_writable($authorized_keys)) {
 | 
			
		||||
            throw new Exception('Cannot create file: '.$authorized_keys);
 | 
			
		||||
@@ -48,13 +48,18 @@ class IDF_Plugin_SyncGit_Cron
 | 
			
		||||
        $out = '';
 | 
			
		||||
        $keys = Pluf::factory('IDF_Key')->getList(array('view'=>'join_user'));
 | 
			
		||||
        foreach ($keys as $key) {
 | 
			
		||||
            if (strlen($key->content) > 40 // minimal check
 | 
			
		||||
                and preg_match('/^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$/', $key->login)) {
 | 
			
		||||
            try {
 | 
			
		||||
                $key_type = $key->getType();
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                // The key is a bad key, skip it
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if ($key_type == 'ssh' and preg_match('/^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$/', $key->login)) {
 | 
			
		||||
                $content = trim(str_replace(array("\n", "\r"), '', $key->content));
 | 
			
		||||
                $out .= sprintf($template, $cmd, $key->login, $content)."\n";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        file_put_contents($authorized_keys, $out, LOCK_EX);        
 | 
			
		||||
        file_put_contents($authorized_keys, $out, LOCK_EX);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -69,6 +74,45 @@ class IDF_Plugin_SyncGit_Cron
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove orphan repositories.
 | 
			
		||||
     */
 | 
			
		||||
    public static function removeOrphanRepositories()
 | 
			
		||||
    {
 | 
			
		||||
        $path = Pluf::f('idf_plugin_syncgit_base_repositories', '/home/git/repositories');
 | 
			
		||||
        if (!is_dir($path) || is_link($path)) {
 | 
			
		||||
            throw new Pluf_Exception_SettingError(sprintf(
 | 
			
		||||
                'Directory %s does not exist! Setting "idf_plugin_syncgit_base_repositories not set.',
 | 
			
		||||
                $path));
 | 
			
		||||
        }
 | 
			
		||||
        if (!is_writable($path)) {
 | 
			
		||||
            throw new Exception(sprintf('Repository %s is not writable.', $path));
 | 
			
		||||
        }
 | 
			
		||||
        $projects = array();
 | 
			
		||||
        foreach (Pluf::factory('IDF_Project')->getList() as $project) {
 | 
			
		||||
            $projects[] = $project->shortname;
 | 
			
		||||
        }
 | 
			
		||||
        unset($project);
 | 
			
		||||
        $it = new DirectoryIterator($path);
 | 
			
		||||
        $orphans = array();
 | 
			
		||||
        while ($it->valid()) {
 | 
			
		||||
            if (!$it->isDot() && $it->isDir() && !in_array(basename($it->getFileName(), '.git'), $projects)) {
 | 
			
		||||
                $orphans[] = $it->getPathName();
 | 
			
		||||
            }
 | 
			
		||||
            $it->next();
 | 
			
		||||
        }
 | 
			
		||||
        if (count($orphans)) {
 | 
			
		||||
            $cmd = Pluf::f('idf_exec_cmd_prefix', '').'rm -rf '.implode(' ', $orphans);
 | 
			
		||||
            exec($cmd);
 | 
			
		||||
            clearstatcache();
 | 
			
		||||
            while (list(, $project) = each($orphans)) {
 | 
			
		||||
                if (is_dir($project)) {
 | 
			
		||||
                    throw new Exception(sprintf('Cannot remove %s directory.', $project));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if a sync is needed.
 | 
			
		||||
     *
 | 
			
		||||
@@ -79,6 +123,9 @@ class IDF_Plugin_SyncGit_Cron
 | 
			
		||||
            @unlink(Pluf::f('idf_plugin_syncgit_sync_file'));
 | 
			
		||||
            self::sync();
 | 
			
		||||
            self::markExport();
 | 
			
		||||
            if (Pluf::f('idf_plugin_syncgit_remove_orphans', false)) {
 | 
			
		||||
                self::removeOrphanRepositories();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -91,11 +91,14 @@ class IDF_Plugin_SyncGit_Serve
 | 
			
		||||
            // the repository on the fly
 | 
			
		||||
            $p = explode(DIRECTORY_SEPARATOR, $fullpath);
 | 
			
		||||
            $mpath = implode(DIRECTORY_SEPARATOR, array_slice($p, 0, -1));
 | 
			
		||||
            mkdir($mpath, 0750, true);
 | 
			
		||||
            if (!file_exists($mpath)) {
 | 
			
		||||
                mkdir($mpath, 0750, true);
 | 
			
		||||
            }
 | 
			
		||||
            $this->initRepository($fullpath);
 | 
			
		||||
            $this->setGitExport($relpath, $fullpath);
 | 
			
		||||
        }
 | 
			
		||||
        $new_cmd = sprintf("%s '%s'", $verb, $fullpath);
 | 
			
		||||
        Pluf_Log::info(array('IDF_Plugin_Git_Serve::serve', $username, $cmd, $new_cmd));
 | 
			
		||||
        return $new_cmd;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -159,7 +162,7 @@ class IDF_Plugin_SyncGit_Serve
 | 
			
		||||
        $request->user = $user;
 | 
			
		||||
        $request->conf = $conf;
 | 
			
		||||
        $request->project = $project;
 | 
			
		||||
        if (true === IDF_Precondition::accessTabGeneric($request, 'source_access_rights')) {
 | 
			
		||||
        if (true === IDF_Precondition::accessSource($request)) {
 | 
			
		||||
            if ($mode == 'readonly') {
 | 
			
		||||
                return array(Pluf::f('idf_plugin_syncgit_base_repositories', '/home/git/repositories'),
 | 
			
		||||
                             $project->shortname);
 | 
			
		||||
@@ -190,13 +193,61 @@ class IDF_Plugin_SyncGit_Serve
 | 
			
		||||
     */
 | 
			
		||||
    public function initRepository($fullpath)
 | 
			
		||||
    {
 | 
			
		||||
        mkdir($fullpath, 0750, true);
 | 
			
		||||
        if (!file_exists($fullpath)) {
 | 
			
		||||
            mkdir($fullpath, 0750, true);
 | 
			
		||||
        }
 | 
			
		||||
        $out = array();
 | 
			
		||||
        $res = 0;
 | 
			
		||||
        exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').
 | 
			
		||||
                     Pluf::f('git_path', 'git').' --git-dir=%s init', escapeshellarg($fullpath)), 
 | 
			
		||||
             $out, $res);
 | 
			
		||||
        if ($res != 0) {
 | 
			
		||||
            Pluf_Log::error(array('IDF_Plugin_Git_Serve::initRepository', $res, $fullpath));
 | 
			
		||||
            throw new Exception(sprintf('Init repository error, exit status %d.', $res));
 | 
			
		||||
        }
 | 
			
		||||
        Pluf_Log::event(array('IDF_Plugin_Git_Serve::initRepository', 'success', $fullpath));
 | 
			
		||||
        // Add the post-update hook by removing the original one and add the 
 | 
			
		||||
        // Indefero's one.
 | 
			
		||||
        $p = realpath(dirname(__FILE__).'/../../../../scripts/git-post-update');
 | 
			
		||||
        $p = Pluf::f('idf_plugin_syncgit_post_update', $p);
 | 
			
		||||
        if (!@unlink($fullpath.'/hooks/post-update')) {
 | 
			
		||||
            Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository', 
 | 
			
		||||
                                 'post-update hook removal error.', 
 | 
			
		||||
                                 $fullpath.'/hooks/post-update'));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $out = array();
 | 
			
		||||
        $res = 0;
 | 
			
		||||
        exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s', 
 | 
			
		||||
                     escapeshellarg($p), 
 | 
			
		||||
                     escapeshellarg($fullpath.'/hooks/post-update')),
 | 
			
		||||
             $out, $res);
 | 
			
		||||
        if ($res != 0) {
 | 
			
		||||
            Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository', 
 | 
			
		||||
                                 'post-update hook creation error.', 
 | 
			
		||||
                                 $fullpath.'/hooks/post-update'));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository', 
 | 
			
		||||
                              'Added post-update hook.', $fullpath));
 | 
			
		||||
        // Configure the core.quotepath option
 | 
			
		||||
        $quotepath = (Pluf::f('git_core_quotepath', true) == true) ? 'true' : 'false';
 | 
			
		||||
        $out = array();
 | 
			
		||||
        $res = 0;
 | 
			
		||||
        exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').
 | 
			
		||||
                     Pluf::f('git_path', 'git').' config -f %s/config --add core.quotepath %s',
 | 
			
		||||
                     escapeshellarg($fullpath),
 | 
			
		||||
                     escapeshellarg($quotepath)
 | 
			
		||||
                    ),
 | 
			
		||||
             $out, $res);
 | 
			
		||||
        if ($res != 0) {
 | 
			
		||||
            Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository', 
 | 
			
		||||
                                 'core.quotepath configuration error.', 
 | 
			
		||||
                                 $quotepath));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository', 
 | 
			
		||||
                              'core.quotepath configured.', $quotepath));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -209,7 +260,7 @@ class IDF_Plugin_SyncGit_Serve
 | 
			
		||||
    {
 | 
			
		||||
        $sql = new Pluf_SQL('shortname=%s', array($relpath));
 | 
			
		||||
        $projects = Pluf::factory('IDF_Project')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
        if ($projects->count() != 1) {
 | 
			
		||||
        if ($projects->count() != 1 and file_exists($fullpath)) {
 | 
			
		||||
            return $this->gitExportDeny($fullpath);
 | 
			
		||||
        }
 | 
			
		||||
        $project = $projects[0];
 | 
			
		||||
@@ -217,7 +268,8 @@ class IDF_Plugin_SyncGit_Serve
 | 
			
		||||
        $conf->setProject($project);
 | 
			
		||||
        $scm = $conf->getVal('scm', 'git');
 | 
			
		||||
        if ($scm == 'git' and !file_exists($fullpath)) {
 | 
			
		||||
            $this->initRepository($fullpath);
 | 
			
		||||
            // No repository yet, just skip
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if ($scm != 'git' or $project->private) {
 | 
			
		||||
            return $this->gitExportDeny($fullpath);
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,9 @@ class IDF_Plugin_SyncMercurial
 | 
			
		||||
        case 'Pluf_User::passwordUpdated':
 | 
			
		||||
            $plug->processSyncPasswd($params['user']);
 | 
			
		||||
            break;
 | 
			
		||||
        case 'hgchangegroup.php::run':
 | 
			
		||||
            $plug->processSyncTimeline($params);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -97,7 +100,8 @@ class IDF_Plugin_SyncMercurial
 | 
			
		||||
        }
 | 
			
		||||
        $ht = new File_Passwd_Authbasic($passwd_file);
 | 
			
		||||
        $ht->load();
 | 
			
		||||
        $ht->setMode(FILE_PASSWD_SHA); 
 | 
			
		||||
        $ht->setMode(Pluf::f('idf_plugin_syncmercurial_passwd_mode',
 | 
			
		||||
                             FILE_PASSWD_SHA)); 
 | 
			
		||||
        if ($ht->userExists($user->login)) {
 | 
			
		||||
            $ht->changePasswd($user->login, $this->getMercurialPass($user));
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -138,7 +142,8 @@ class IDF_Plugin_SyncMercurial
 | 
			
		||||
            throw new Exception (sprintf(__('%s does not exist or is not writable.'), $passwd_file));
 | 
			
		||||
        }
 | 
			
		||||
        $ht = new File_Passwd_Authbasic($passwd_file);
 | 
			
		||||
        $ht->setMode(FILE_PASSWD_SHA); 
 | 
			
		||||
        $ht->setMode(Pluf::f('idf_plugin_syncmercurial_passwd_mode',
 | 
			
		||||
                             FILE_PASSWD_SHA)); 
 | 
			
		||||
        $ht->load();
 | 
			
		||||
        $mem = $project->getMembershipData();
 | 
			
		||||
        $members = array_merge((array)$mem['members'], (array)$mem['owners'], 
 | 
			
		||||
@@ -220,4 +225,25 @@ class IDF_Plugin_SyncMercurial
 | 
			
		||||
        file_put_contents($notify_file, ' ', LOCK_EX);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the timeline in post commit.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public function processSyncTimeline($params)
 | 
			
		||||
    {
 | 
			
		||||
        $pname = basename($params['rel_dir']);
 | 
			
		||||
        try {
 | 
			
		||||
            $project = IDF_Project::getOr404($pname);
 | 
			
		||||
        } catch (Pluf_HTTP_Error404 $e) {
 | 
			
		||||
            Pluf_Log::event(array('IDF_Plugin_SyncMercurial::processSyncTimeline', 'Project not found.', array($pname, $params)));
 | 
			
		||||
            return false; // Project not found
 | 
			
		||||
        }
 | 
			
		||||
        // Now we have the project and can update the timeline
 | 
			
		||||
        Pluf_Log::debug(array('IDF_Plugin_SyncMercurial::processSyncTimeline', 'Project found', $pname, $project->id));
 | 
			
		||||
        IDF_Scm::syncTimeline($project, true);
 | 
			
		||||
        Pluf_Log::event(array('IDF_Plugin_SyncMercurial::processSyncTimeline', 'sync', array($pname, $project->id)));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										830
									
								
								src/IDF/Plugin/SyncMonotone.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										830
									
								
								src/IDF/Plugin/SyncMonotone.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,830 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2010 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classes is a plugin which allows to synchronise access rights
 | 
			
		||||
 * between indefero and monotone usher setups.
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Plugin_SyncMonotone
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Entry point of the plugin.
 | 
			
		||||
     */
 | 
			
		||||
    static public function entry($signal, &$params)
 | 
			
		||||
    {
 | 
			
		||||
        $plug = new IDF_Plugin_SyncMonotone();
 | 
			
		||||
        switch ($signal) {
 | 
			
		||||
        case 'IDF_Project::created':
 | 
			
		||||
            $plug->processProjectCreate($params['project']);
 | 
			
		||||
            break;
 | 
			
		||||
        case 'IDF_Project::membershipsUpdated':
 | 
			
		||||
            $plug->processMembershipsUpdated($params['project']);
 | 
			
		||||
            break;
 | 
			
		||||
        case 'IDF_Project::preDelete':
 | 
			
		||||
            $plug->processProjectDelete($params['project']);
 | 
			
		||||
            break;
 | 
			
		||||
        case 'IDF_Key::postSave':
 | 
			
		||||
            $plug->processKeyCreate($params['key']);
 | 
			
		||||
            break;
 | 
			
		||||
        case 'IDF_Key::preDelete':
 | 
			
		||||
            $plug->processKeyDelete($params['key']);
 | 
			
		||||
            break;
 | 
			
		||||
        case 'mtnpostpush.php::run':
 | 
			
		||||
            $plug->processSyncTimeline($params['project']);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initial steps to setup a new monotone project:
 | 
			
		||||
     *
 | 
			
		||||
     *  1) run mtn db init to initialize a new database underknees
 | 
			
		||||
     *     'mtn_repositories'
 | 
			
		||||
     *  2) create a new server key in the same directory
 | 
			
		||||
     *  3) create a new client key for IDF and store it in the project conf
 | 
			
		||||
     *  4) setup the configuration
 | 
			
		||||
     *  5) add the database as new local server in the usher configuration
 | 
			
		||||
     *  6) reload the running usher instance so it acknowledges the new server
 | 
			
		||||
     *
 | 
			
		||||
     * The initial right setup happens in processMembershipsUpdated()
 | 
			
		||||
     *
 | 
			
		||||
     * @param IDF_Project
 | 
			
		||||
     */
 | 
			
		||||
    function processProjectCreate($project)
 | 
			
		||||
    {
 | 
			
		||||
        if ($project->getConf()->getVal('scm') != 'mtn') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Pluf::f('mtn_db_access', 'local') == 'local') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $projecttempl = Pluf::f('mtn_repositories', false);
 | 
			
		||||
        if ($projecttempl === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(
 | 
			
		||||
                 __('"mtn_repositories" must be defined in your configuration file.')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $usher_config = Pluf::f('mtn_usher_conf', false);
 | 
			
		||||
        if (!$usher_config || !is_writable($usher_config)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(
 | 
			
		||||
                 __('"mtn_usher_conf" does not exist or is not writable.')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $mtnpostpush = realpath(dirname(__FILE__) . '/../../../scripts/mtn-post-push');
 | 
			
		||||
        if (!file_exists($mtnpostpush)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not find mtn-post-push script "%s".'), $mtnpostpush
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check some static configuration files
 | 
			
		||||
        $confdir = Pluf::f('mtn_confdir', false);
 | 
			
		||||
        if ($confdir === false) {
 | 
			
		||||
            $confdir = dirname(__FILE__).'/SyncMonotone/';
 | 
			
		||||
        }
 | 
			
		||||
        $confdir_contents = array(
 | 
			
		||||
             'monotonerc.in',
 | 
			
		||||
             'remote-automate-permissions.in',
 | 
			
		||||
             'hooks.d/',
 | 
			
		||||
             // this is linked and not copied to be able to update
 | 
			
		||||
             // the list of read-only commands on upgrades
 | 
			
		||||
             'hooks.d/indefero_authorize_remote_automate.conf',
 | 
			
		||||
             'hooks.d/indefero_authorize_remote_automate.lua',
 | 
			
		||||
             'hooks.d/indefero_post_push.conf.in',
 | 
			
		||||
             'hooks.d/indefero_post_push.lua',
 | 
			
		||||
        );
 | 
			
		||||
        if (!$project->private) {
 | 
			
		||||
            // this is linked and not copied to be able to update
 | 
			
		||||
            // the list of read-only commands on upgrades
 | 
			
		||||
            $confdir_contents[] = 'hooks.d/indefero_authorize_remote_automate.conf';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check whether we should handle additional files in the config directory
 | 
			
		||||
        $confdir_extra_contents = Pluf::f('mtn_confdir_extra', false);
 | 
			
		||||
        if ($confdir_extra_contents !== false) {
 | 
			
		||||
            $confdir_contents =
 | 
			
		||||
                array_merge($confdir_contents, $confdir_extra_contents);
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($confdir_contents as $content) {
 | 
			
		||||
            if (!file_exists($confdir.$content)) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('The configuration file %s is missing.'), $content
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $shortname = $project->shortname;
 | 
			
		||||
        $projectpath = sprintf($projecttempl, $shortname);
 | 
			
		||||
        if (file_exists($projectpath)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('The project path %s already exists.'), $projectpath
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!mkdir($projectpath)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('The project path %s could not be created.'), $projectpath
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // step 1) create a new database
 | 
			
		||||
        //
 | 
			
		||||
        $dbfile = $projectpath.'/database.mtn';
 | 
			
		||||
        $cmd = sprintf('db init -d %s', escapeshellarg($dbfile));
 | 
			
		||||
        self::_mtn_exec($cmd);
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // step 2) create a server key
 | 
			
		||||
        //
 | 
			
		||||
        // try to parse the key's domain part from the remote_url's host
 | 
			
		||||
        // name, otherwise fall back to the configured Apache server name
 | 
			
		||||
        $server = $_SERVER['SERVER_NAME'];
 | 
			
		||||
        $remote_url = Pluf::f('mtn_remote_url');
 | 
			
		||||
        if (($parsed = parse_url($remote_url)) !== false &&
 | 
			
		||||
            !empty($parsed['host'])) {
 | 
			
		||||
            $server = $parsed['host'];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $serverkey = $shortname.'-server@'.$server;
 | 
			
		||||
        $cmd = sprintf('au generate_key --confdir=%s %s ""',
 | 
			
		||||
            escapeshellarg($projectpath),
 | 
			
		||||
            escapeshellarg($serverkey)
 | 
			
		||||
        );
 | 
			
		||||
        self::_mtn_exec($cmd);
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // step 3) create a client key, and save it in IDF
 | 
			
		||||
        //
 | 
			
		||||
        $keydir = Pluf::f('tmp_folder').'/mtn-client-keys';
 | 
			
		||||
        if (!file_exists($keydir)) {
 | 
			
		||||
            if (!mkdir($keydir)) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('The key directory %s could not be created.'), $keydir
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $clientkey_name = $shortname.'-client@'.$server;
 | 
			
		||||
        $cmd = sprintf('au generate_key --keydir=%s %s ""',
 | 
			
		||||
            escapeshellarg($keydir),
 | 
			
		||||
            escapeshellarg($clientkey_name)
 | 
			
		||||
        );
 | 
			
		||||
        $keyinfo = self::_mtn_exec($cmd);
 | 
			
		||||
 | 
			
		||||
        $parsed_keyinfo = array();
 | 
			
		||||
        try {
 | 
			
		||||
            $parsed_keyinfo = IDF_Scm_Monotone_BasicIO::parse($keyinfo);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception $e) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not parse key information: %s'), $e->getMessage()
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $clientkey_hash = $parsed_keyinfo[0][1]['hash'];
 | 
			
		||||
        $clientkey_file = $keydir . '/' . $clientkey_name . '.' . $clientkey_hash;
 | 
			
		||||
        $clientkey_data = file_get_contents($clientkey_file);
 | 
			
		||||
 | 
			
		||||
        $project->getConf()->setVal('mtn_client_key_name', $clientkey_name);
 | 
			
		||||
        $project->getConf()->setVal('mtn_client_key_hash', $clientkey_hash);
 | 
			
		||||
        $project->getConf()->setVal('mtn_client_key_data', $clientkey_data);
 | 
			
		||||
 | 
			
		||||
        // add the public client key to the server
 | 
			
		||||
        $cmd = sprintf('au get_public_key --keydir=%s %s',
 | 
			
		||||
            escapeshellarg($keydir),
 | 
			
		||||
            escapeshellarg($clientkey_hash)
 | 
			
		||||
        );
 | 
			
		||||
        $clientkey_pubdata = self::_mtn_exec($cmd);
 | 
			
		||||
 | 
			
		||||
        $cmd = sprintf('au put_public_key --db=%s %s',
 | 
			
		||||
            escapeshellarg($dbfile),
 | 
			
		||||
            escapeshellarg($clientkey_pubdata)
 | 
			
		||||
        );
 | 
			
		||||
        self::_mtn_exec($cmd);
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // step 4) setup the configuration
 | 
			
		||||
        //
 | 
			
		||||
 | 
			
		||||
        // we assume that all confdir entries ending with a slash mean a
 | 
			
		||||
        // directory that has to be created, that all files ending on ".in"
 | 
			
		||||
        // have to be processed and copied in place and that all other files
 | 
			
		||||
        // just need to be symlinked from the original location
 | 
			
		||||
        foreach ($confdir_contents as $content) {
 | 
			
		||||
            $filepath = $projectpath.'/'.$content;
 | 
			
		||||
            if (substr($content, -1) == '/') {
 | 
			
		||||
                if (!mkdir($filepath)) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('Could not create configuration directory "%s"'), $filepath
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (substr($content, -3) != '.in') {
 | 
			
		||||
                if (!symlink($confdir.$content, $filepath)) {
 | 
			
		||||
                    IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('Could not create symlink "%s"'), $filepath
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $filecontents = file_get_contents($confdir.'/'.$content);
 | 
			
		||||
            $filecontents = str_replace(
 | 
			
		||||
                array('%%MTNPOSTPUSH%%', '%%PROJECT%%', '%%MTNCLIENTKEY%%'),
 | 
			
		||||
                array($mtnpostpush, $shortname, $clientkey_hash),
 | 
			
		||||
                $filecontents
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // remove the .in
 | 
			
		||||
            $filepath = substr($filepath, 0, -3);
 | 
			
		||||
            if (file_put_contents($filepath, $filecontents, LOCK_EX) === false) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('Could not write configuration file "%s"'), $filepath
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // step 5) read in and append the usher config with the new server
 | 
			
		||||
        //
 | 
			
		||||
        $usher_rc = file_get_contents($usher_config);
 | 
			
		||||
        $parsed_config = array();
 | 
			
		||||
        try {
 | 
			
		||||
            $parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception $e) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not parse usher configuration in "%s": %s'),
 | 
			
		||||
                $usher_config, $e->getMessage()
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // ensure we haven't configured a server with this name already
 | 
			
		||||
        foreach ($parsed_config as $stanzas) {
 | 
			
		||||
            foreach ($stanzas as $stanza_line) {
 | 
			
		||||
                if ($stanza_line['key'] == 'server' &&
 | 
			
		||||
                    $stanza_line['values'][0] == $shortname) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('usher configuration already contains a server '.
 | 
			
		||||
                           'entry named "%s"'),
 | 
			
		||||
                        $shortname
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $new_server = array(
 | 
			
		||||
            array('key' => 'server', 'values' => array($shortname)),
 | 
			
		||||
            array('key' => 'local', 'values' => array(
 | 
			
		||||
                '--confdir', $projectpath,
 | 
			
		||||
                '-d', $dbfile,
 | 
			
		||||
                '--timestamps',
 | 
			
		||||
                '--ticker=dot'
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $parsed_config[] = $new_server;
 | 
			
		||||
        $usher_rc = IDF_Scm_Monotone_BasicIO::compile($parsed_config);
 | 
			
		||||
 | 
			
		||||
        // FIXME: more sanity - what happens on failing writes? we do not
 | 
			
		||||
        // have a backup copy of usher.conf around...
 | 
			
		||||
        if (file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not write usher configuration file "%s"'), $usher_config
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // step 6) reload usher to pick up the new configuration
 | 
			
		||||
        //
 | 
			
		||||
        IDF_Scm_Monotone_Usher::reload();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the read / write permissions for the monotone database
 | 
			
		||||
     *
 | 
			
		||||
     * @param IDF_Project
 | 
			
		||||
     */
 | 
			
		||||
    public function processMembershipsUpdated($project)
 | 
			
		||||
    {
 | 
			
		||||
        if ($project->getConf()->getVal('scm') != 'mtn') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Pluf::f('mtn_db_access', 'local') == 'local') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $mtn = IDF_Scm_Monotone::factory($project);
 | 
			
		||||
        $stdio = $mtn->getStdio();
 | 
			
		||||
 | 
			
		||||
        $projectpath = self::_get_project_path($project);
 | 
			
		||||
        $auth_ids    = self::_get_authorized_user_ids($project);
 | 
			
		||||
        $key_ids     = array();
 | 
			
		||||
        foreach ($auth_ids as $auth_id) {
 | 
			
		||||
            $sql = new Pluf_SQL('user=%s', array($auth_id));
 | 
			
		||||
            $keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
 | 
			
		||||
            foreach ($keys as $key) {
 | 
			
		||||
                if ($key->getType() != 'mtn')
 | 
			
		||||
                    continue;
 | 
			
		||||
                $stdio->exec(array('put_public_key', $key->content));
 | 
			
		||||
                $key_ids[] = $key->getMtnId();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $write_permissions = implode("\n", $key_ids);
 | 
			
		||||
        $rcfile = $projectpath.'/write-permissions';
 | 
			
		||||
        if (file_put_contents($rcfile, $write_permissions, LOCK_EX) === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not write write-permissions file "%s"'), $rcfile
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($project->private) {
 | 
			
		||||
            $stanza = array(
 | 
			
		||||
                array('key' => 'pattern', 'values' => array('*')),
 | 
			
		||||
            );
 | 
			
		||||
            foreach ($key_ids as $key_id)
 | 
			
		||||
            {
 | 
			
		||||
                $stanza[] = array('key' => 'allow', 'values' => array($key_id));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            $stanza = array(
 | 
			
		||||
                array('key' => 'pattern', 'values' => array('*')),
 | 
			
		||||
                array('key' => 'allow', 'values' => array('*')),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        $read_permissions = IDF_Scm_Monotone_BasicIO::compile(array($stanza));
 | 
			
		||||
        $rcfile = $projectpath.'/read-permissions';
 | 
			
		||||
        if (file_put_contents($rcfile, $read_permissions, LOCK_EX) === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not write read-permissions file "%s"'), $rcfile
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // link / unlink the read-only automate permissions for the project
 | 
			
		||||
        $confdir = Pluf::f('mtn_confdir', false);
 | 
			
		||||
        if ($confdir === false) {
 | 
			
		||||
            $confdir = dirname(__FILE__).'/SyncMonotone/';
 | 
			
		||||
        }
 | 
			
		||||
        $file = 'hooks.d/indefero_authorize_remote_automate.conf';
 | 
			
		||||
        $projectfile = $projectpath.'/'.$file;
 | 
			
		||||
        $templatefile = $confdir.'/'.$file;
 | 
			
		||||
 | 
			
		||||
        $serverRestartRequired = false;
 | 
			
		||||
        if ($project->private && file_exists($projectfile) && is_link($projectfile)) {
 | 
			
		||||
            if (!unlink($projectfile)) {
 | 
			
		||||
                IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('Could not remove symlink "%s"'), $projectfile
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
            $serverRestartRequired = true;
 | 
			
		||||
        } else
 | 
			
		||||
        if (!$project->private && !file_exists($projectfile)) {
 | 
			
		||||
            if (!symlink($templatefile, $projectfile)) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('Could not create symlink "%s"'), $projectfile
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
            $serverRestartRequired = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($serverRestartRequired) {
 | 
			
		||||
            // FIXME: we should actually use stopServer() here, but this
 | 
			
		||||
            // seems to be ignored when the server should be started
 | 
			
		||||
            // again immediately afterwards
 | 
			
		||||
            IDF_Scm_Monotone_Usher::killServer($project->shortname);
 | 
			
		||||
            IDF_Scm_Monotone_Usher::startServer($project->shortname);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clean up after a mtn project was deleted
 | 
			
		||||
     *
 | 
			
		||||
     * @param IDF_Project
 | 
			
		||||
     */
 | 
			
		||||
    public function processProjectDelete($project)
 | 
			
		||||
    {
 | 
			
		||||
        if ($project->getConf()->getVal('scm') != 'mtn') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Pluf::f('mtn_db_access', 'local') == 'local') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $usher_config = Pluf::f('mtn_usher_conf', false);
 | 
			
		||||
        if (!$usher_config || !is_writable($usher_config)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(
 | 
			
		||||
                 __('"mtn_usher_conf" does not exist or is not writable.')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $shortname = $project->shortname;
 | 
			
		||||
        IDF_Scm_Monotone_Usher::killServer($shortname);
 | 
			
		||||
 | 
			
		||||
        $projecttempl = Pluf::f('mtn_repositories', false);
 | 
			
		||||
        if ($projecttempl === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(
 | 
			
		||||
                 __('"mtn_repositories" must be defined in your configuration file.')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $projectpath = sprintf($projecttempl, $shortname);
 | 
			
		||||
        if (file_exists($projectpath)) {
 | 
			
		||||
            if (!self::_delete_recursive($projectpath)) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('One or more paths underknees %s could not be deleted.'), $projectpath
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $keydir = Pluf::f('tmp_folder').'/mtn-client-keys';
 | 
			
		||||
        $keyname = $project->getConf()->getVal('mtn_client_key_name', false);
 | 
			
		||||
        $keyhash = $project->getConf()->getVal('mtn_client_key_hash', false);
 | 
			
		||||
        if ($keyname && $keyhash &&
 | 
			
		||||
            file_exists($keydir .'/'. $keyname . '.' . $keyhash)) {
 | 
			
		||||
            if (!@unlink($keydir .'/'. $keyname . '.' . $keyhash)) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('Could not delete client private key %s'), $keyname
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $usher_rc = file_get_contents($usher_config);
 | 
			
		||||
        $parsed_config = array();
 | 
			
		||||
        try {
 | 
			
		||||
            $parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception $e) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not parse usher configuration in "%s": %s'),
 | 
			
		||||
                $usher_config, $e->getMessage()
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($parsed_config as $idx => $stanzas) {
 | 
			
		||||
            foreach ($stanzas as $stanza_line) {
 | 
			
		||||
                if ($stanza_line['key'] == 'server' &&
 | 
			
		||||
                    $stanza_line['values'][0] == $shortname) {
 | 
			
		||||
                    unset($parsed_config[$idx]);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $usher_rc = IDF_Scm_Monotone_BasicIO::compile($parsed_config);
 | 
			
		||||
 | 
			
		||||
        // FIXME: more sanity - what happens on failing writes? we do not
 | 
			
		||||
        // have a backup copy of usher.conf around...
 | 
			
		||||
        if (file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not write usher configuration file "%s"'), $usher_config
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IDF_Scm_Monotone_Usher::reload();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds the (monotone) key to all monotone projects of this forge
 | 
			
		||||
     * where the user of the key has write access to
 | 
			
		||||
     */
 | 
			
		||||
    public function processKeyCreate($key)
 | 
			
		||||
    {
 | 
			
		||||
        if ($key->getType() != 'mtn') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Pluf::f('mtn_db_access', 'local') == 'local') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach (Pluf::factory('IDF_Project')->getList() as $project) {
 | 
			
		||||
            $conf = new IDF_Conf();
 | 
			
		||||
            $conf->setProject($project);
 | 
			
		||||
            $scm = $conf->getVal('scm', 'mtn');
 | 
			
		||||
            if ($scm != 'mtn')
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            $projectpath = self::_get_project_path($project);
 | 
			
		||||
            $auth_ids    = self::_get_authorized_user_ids($project);
 | 
			
		||||
            if (!in_array($key->user, $auth_ids))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            $mtn_key_id = $key->getMtnId();
 | 
			
		||||
 | 
			
		||||
            // if the project is not defined as private, all people have
 | 
			
		||||
            // read access already, so we don't need to write anything
 | 
			
		||||
            // and we currently do not check if read-permissions really
 | 
			
		||||
            // contains
 | 
			
		||||
            //      pattern "*"
 | 
			
		||||
            //      allow "*"
 | 
			
		||||
            // which is the default for non-private projects
 | 
			
		||||
            if ($project->private == true) {
 | 
			
		||||
                $read_perms = file_get_contents($projectpath.'/read-permissions');
 | 
			
		||||
                $parsed_read_perms = array();
 | 
			
		||||
                try {
 | 
			
		||||
                    $parsed_read_perms = IDF_Scm_Monotone_BasicIO::parse($read_perms);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception $e) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('Could not parse read-permissions for project "%s": %s'),
 | 
			
		||||
                        $shortname, $e->getMessage()
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $wildcard_section = null;
 | 
			
		||||
                for ($i=0; $i<count($parsed_read_perms); ++$i) {
 | 
			
		||||
                    foreach ($parsed_read_perms[$i] as $stanza_line) {
 | 
			
		||||
                        if ($stanza_line['key'] == 'pattern' &&
 | 
			
		||||
                            $stanza_line['values'][0] == '*') {
 | 
			
		||||
                            $wildcard_section =& $parsed_read_perms[$i];
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($wildcard_section == null)
 | 
			
		||||
                {
 | 
			
		||||
                    $wildcard_section = array(
 | 
			
		||||
                        array('key' => 'pattern', 'values' => array('*'))
 | 
			
		||||
                    );
 | 
			
		||||
                    $parsed_read_perms[] =& $wildcard_section;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $key_found = false;
 | 
			
		||||
                foreach ($wildcard_section as $line)
 | 
			
		||||
                {
 | 
			
		||||
                    if ($line['key'] == 'allow' && $line['values'][0] == $mtn_key_id) {
 | 
			
		||||
                        $key_found = true;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!$key_found) {
 | 
			
		||||
                    $wildcard_section[] = array(
 | 
			
		||||
                        'key' => 'allow', 'values' => array($mtn_key_id)
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms);
 | 
			
		||||
 | 
			
		||||
                if (file_put_contents($projectpath.'/read-permissions',
 | 
			
		||||
                                      $read_perms, LOCK_EX) === false) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('Could not write read-permissions for project "%s"'), $shortname
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $write_perms = file_get_contents($projectpath.'/write-permissions');
 | 
			
		||||
            $lines = preg_split("/(\n|\r\n)/", $write_perms, -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
            if (!in_array('*', $lines) && !in_array($mtn_key_id, $lines)) {
 | 
			
		||||
                $lines[] = $mtn_key_id;
 | 
			
		||||
            }
 | 
			
		||||
            if (file_put_contents($projectpath.'/write-permissions',
 | 
			
		||||
                                  implode("\n", $lines) . "\n", LOCK_EX) === false) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('Could not write write-permissions file for project "%s"'),
 | 
			
		||||
                    $shortname
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $mtn = IDF_Scm_Monotone::factory($project);
 | 
			
		||||
            $stdio = $mtn->getStdio();
 | 
			
		||||
            $stdio->exec(array('put_public_key', $key->content));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes the (monotone) key from all monotone projects of this forge
 | 
			
		||||
     * where the user of the key has write access to
 | 
			
		||||
     */
 | 
			
		||||
    public function processKeyDelete($key)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($key->getType() != 'mtn') {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            // bad key type, skip it.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Pluf::f('mtn_db_access', 'local') == 'local') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach (Pluf::factory('IDF_Project')->getList() as $project) {
 | 
			
		||||
            $conf = new IDF_Conf();
 | 
			
		||||
            $conf->setProject($project);
 | 
			
		||||
            $scm = $conf->getVal('scm', 'mtn');
 | 
			
		||||
            if ($scm != 'mtn')
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            $projectpath = self::_get_project_path($project);
 | 
			
		||||
            $auth_ids    = self::_get_authorized_user_ids($project);
 | 
			
		||||
            if (!in_array($key->user, $auth_ids))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            $mtn_key_id = $key->getMtnId();
 | 
			
		||||
 | 
			
		||||
            // if the project is not defined as private, all people have
 | 
			
		||||
            // read access already, so we don't need to write anything
 | 
			
		||||
            // and we currently do not check if read-permissions really
 | 
			
		||||
            // contains
 | 
			
		||||
            //      pattern "*"
 | 
			
		||||
            //      allow "*"
 | 
			
		||||
            // which is the default for non-private projects
 | 
			
		||||
            if ($project->private) {
 | 
			
		||||
                $read_perms = file_get_contents($projectpath.'/read-permissions');
 | 
			
		||||
                $parsed_read_perms = array();
 | 
			
		||||
                try {
 | 
			
		||||
                    $parsed_read_perms = IDF_Scm_Monotone_BasicIO::parse($read_perms);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception $e) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('Could not parse read-permissions for project "%s": %s'),
 | 
			
		||||
                        $shortname, $e->getMessage()
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // while we add new keys only to an existing wild-card entry
 | 
			
		||||
                // we remove dropped keys from all sections since the key
 | 
			
		||||
                // should be simply unavailable for all of them
 | 
			
		||||
                for ($h=0; $h<count($parsed_read_perms); ++$h) {
 | 
			
		||||
                    for ($i=0; $i<count($parsed_read_perms[$h]); ++$i) {
 | 
			
		||||
                        if ($parsed_read_perms[$h][$i]['key'] == 'allow' &&
 | 
			
		||||
                            $parsed_read_perms[$h][$i]['values'][0] == $mtn_key_id) {
 | 
			
		||||
                            unset($parsed_read_perms[$h][$i]);
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms);
 | 
			
		||||
 | 
			
		||||
                if (file_put_contents($projectpath.'/read-permissions',
 | 
			
		||||
                                      $read_perms, LOCK_EX) === false) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('Could not write read-permissions for project "%s"'), $shortname
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $write_perms = file_get_contents($projectpath.'/write-permissions');
 | 
			
		||||
            $lines = preg_split("/(\n|\r\n)/", $write_perms, -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
            for ($i=0; $i<count($lines); ++$i) {
 | 
			
		||||
                if ($lines[$i] == $mtn_key_id) {
 | 
			
		||||
                    unset($lines[$i]);
 | 
			
		||||
                    // the key should actually only exist once in the
 | 
			
		||||
                    // file, but we're paranoid
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (file_put_contents($projectpath.'/write-permissions',
 | 
			
		||||
                                  implode("\n", $lines) . "\n", LOCK_EX) === false) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('Could not write write-permissions file for project "%s"'),
 | 
			
		||||
                    $shortname
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $mtn = IDF_Scm_Monotone::factory($project);
 | 
			
		||||
            $stdio = $mtn->getStdio();
 | 
			
		||||
            // if the public key did not sign any revisions, drop it from
 | 
			
		||||
            // the database as well
 | 
			
		||||
            try {
 | 
			
		||||
                if (strlen($stdio->exec(array('select', 'k:' . $mtn_key_id))) == 0) {
 | 
			
		||||
                    $stdio->exec(array('drop_public_key', $mtn_key_id));
 | 
			
		||||
                }
 | 
			
		||||
            } catch (IDF_Scm_Exception $e) {
 | 
			
		||||
                if (strpos($e->getMessage(), 'there is no key named') === false)
 | 
			
		||||
                    throw $e;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the timeline after a push
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public function processSyncTimeline($project_name)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $project = IDF_Project::getOr404($project_name);
 | 
			
		||||
        } catch (Pluf_HTTP_Error404 $e) {
 | 
			
		||||
            Pluf_Log::event(array(
 | 
			
		||||
                'IDF_Plugin_SyncMonotone::processSyncTimeline',
 | 
			
		||||
                'Project not found.',
 | 
			
		||||
                array($project_name, $params)
 | 
			
		||||
            ));
 | 
			
		||||
            return false; // Project not found
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Pluf_Log::debug(array(
 | 
			
		||||
            'IDF_Plugin_SyncMonotone::processSyncTimeline',
 | 
			
		||||
            'Project found', $project_name, $project->id
 | 
			
		||||
        ));
 | 
			
		||||
        IDF_Scm::syncTimeline($project, true);
 | 
			
		||||
        Pluf_Log::event(array(
 | 
			
		||||
            'IDF_Plugin_SyncMonotone::processSyncTimeline',
 | 
			
		||||
            'sync', array($project_name, $project->id)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function _get_authorized_user_ids($project)
 | 
			
		||||
    {
 | 
			
		||||
        $mem = $project->getMembershipData();
 | 
			
		||||
        $members = array_merge((array)$mem['members'],
 | 
			
		||||
                               (array)$mem['owners'],
 | 
			
		||||
                               (array)$mem['authorized']);
 | 
			
		||||
        $userids = array();
 | 
			
		||||
        foreach ($members as $member) {
 | 
			
		||||
            $userids[] = $member->id;
 | 
			
		||||
        }
 | 
			
		||||
        return $userids;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function _get_project_path($project)
 | 
			
		||||
    {
 | 
			
		||||
        $projecttempl = Pluf::f('mtn_repositories', false);
 | 
			
		||||
        if ($projecttempl === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(
 | 
			
		||||
                 __('"mtn_repositories" must be defined in your configuration file.')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $projectpath = sprintf($projecttempl, $project->shortname);
 | 
			
		||||
        if (!file_exists($projectpath)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('The project path %s does not exists.'), $projectpath
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        return $projectpath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function _mtn_exec($cmd)
 | 
			
		||||
    {
 | 
			
		||||
        $fullcmd = sprintf('%s %s %s',
 | 
			
		||||
            Pluf::f('idf_exec_cmd_prefix', ''),
 | 
			
		||||
            Pluf::f('mtn_path', 'mtn'),
 | 
			
		||||
            $cmd
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $output = $return = null;
 | 
			
		||||
        exec($fullcmd, $output, $return);
 | 
			
		||||
        if ($return != 0) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('The command "%s" could not be executed.'), $cmd
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        return implode("\n", $output);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function _delete_recursive($path)
 | 
			
		||||
    {
 | 
			
		||||
        if (is_file($path) || is_link($path)) {
 | 
			
		||||
            return @unlink($path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (is_dir($path)) {
 | 
			
		||||
            $scan = glob(rtrim($path, '/') . '/*');
 | 
			
		||||
            $status = 0;
 | 
			
		||||
            foreach ($scan as $subpath) {
 | 
			
		||||
                $status |= self::_delete_recursive($subpath);
 | 
			
		||||
            }
 | 
			
		||||
            $status |= rmdir($path);
 | 
			
		||||
            return $status;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
ARA_safe_commands = {
 | 
			
		||||
   "get_corresponding_path", "get_content_changed", "tags", "branches",
 | 
			
		||||
   "common_ancestors", "packet_for_fdelta", "packet_for_fdata",
 | 
			
		||||
   "packets_for_certs", "packet_for_rdata", "get_manifest_of",
 | 
			
		||||
   "get_revision", "select", "graph", "children", "parents", "roots",
 | 
			
		||||
   "leaves", "ancestry_difference", "toposort", "erase_ancestors",
 | 
			
		||||
   "descendents", "ancestors", "heads", "get_file_of", "get_file",
 | 
			
		||||
   "interface_version", "get_attributes", "content_diff",
 | 
			
		||||
   "file_merge", "show_conflicts", "certs", "keys", "get_extended_manifest_of"
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,88 @@
 | 
			
		||||
-- ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
-- This file is part of InDefero, an open source project management application.
 | 
			
		||||
-- Copyright (C) 2011 Céondo Ltd and contributors.
 | 
			
		||||
-- Copyright (C) 2010 Thomas Keller <me@thomaskeller.biz>
 | 
			
		||||
--                    Richard Levitte <richard@levitte.org>
 | 
			
		||||
--
 | 
			
		||||
-- InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
-- it under the terms of the GNU General Public License as published by
 | 
			
		||||
-- the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
-- (at your option) any later version.
 | 
			
		||||
--
 | 
			
		||||
-- InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
-- GNU General Public License for more details.
 | 
			
		||||
--
 | 
			
		||||
-- You should have received a copy of the GNU General Public License
 | 
			
		||||
-- along with this program; if not, write to the Free Software
 | 
			
		||||
-- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
--
 | 
			
		||||
-- ***** END LICENSE BLOCK *****
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- This script reads key identities from a file "remote-automate-permissions"
 | 
			
		||||
-- in the configuration directory and permits those authenticating with one
 | 
			
		||||
-- of those keys to perform dangerous (read/write) remote automate operations.
 | 
			
		||||
-- The format of the file is very simple, one key identity on every line.
 | 
			
		||||
-- Lines starting with # are ignore, as well as empty lines.
 | 
			
		||||
--
 | 
			
		||||
-- It's possible to configure this script to allow the performance of some
 | 
			
		||||
-- remote automate commands anonymously, through the variable
 | 
			
		||||
-- ARA_safe_commands, which has to be a table of commands as strings.
 | 
			
		||||
-- One example configuration, taken from the setup at code.monotone.ca, could
 | 
			
		||||
-- be this:
 | 
			
		||||
--
 | 
			
		||||
-- ARA_safe_commands = {
 | 
			
		||||
--    "get_corresponding_path", "get_content_changed", "tags", "branches",
 | 
			
		||||
--    "common_ancestors", "packet_for_fdelta", "packet_for_fdata",
 | 
			
		||||
--    "packets_for_certs", "packet_for_rdata", "get_manifest_of",
 | 
			
		||||
--    "get_revision", "select", "graph", "children", "parents", "roots",
 | 
			
		||||
--    "leaves", "ancestry_difference", "toposort", "erase_ancestors",
 | 
			
		||||
--    "descendents", "ancestors", "heads", "get_file_of", "get_file",
 | 
			
		||||
--    "interface_version", "get_attributes", "content_diff",
 | 
			
		||||
--    "file_merge", "show_conflicts", "certs", "keys", "get_extended_manifest_of"
 | 
			
		||||
-- }
 | 
			
		||||
--
 | 
			
		||||
do
 | 
			
		||||
   local _safe_commands = {}
 | 
			
		||||
   if ARA_safe_commands then
 | 
			
		||||
      _safe_commands = ARA_safe_commands
 | 
			
		||||
   end
 | 
			
		||||
 | 
			
		||||
   local _save_get_remote_automate_permitted = get_remote_automate_permitted
 | 
			
		||||
   function get_remote_automate_permitted(key_identity, command, options)
 | 
			
		||||
      local permfile =
 | 
			
		||||
	 io.open(get_confdir() .. "/remote-automate-permissions", "r")
 | 
			
		||||
      if (permfile == nil) then
 | 
			
		||||
        return false
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      -- See if the incoming key matches any of the key identities or
 | 
			
		||||
      -- patterns found in the permissions file.
 | 
			
		||||
      local matches = false
 | 
			
		||||
      local line = permfile:read()
 | 
			
		||||
      while (not matches and line ~= nil) do
 | 
			
		||||
        if not globish_match("#*", line) then
 | 
			
		||||
          local _, _, ln = string.find(line, "%s*([^%s]*)%s*")
 | 
			
		||||
          if ln == "*" then matches = true end
 | 
			
		||||
          if ln == key_identity.id then matches = true end
 | 
			
		||||
          if globish_match(ln, key_identity.name) then matches = true end
 | 
			
		||||
          line = permfile:read()
 | 
			
		||||
	    end
 | 
			
		||||
      end
 | 
			
		||||
      io.close(permfile)
 | 
			
		||||
      if matches then return true end
 | 
			
		||||
 | 
			
		||||
      -- No matching key found, let's see if the command matches one the
 | 
			
		||||
      -- admin allowed to be performed anonymously
 | 
			
		||||
      for _,v in ipairs(_safe_commands) do
 | 
			
		||||
        if (v == command[1]) then
 | 
			
		||||
          return true
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      -- No matches found anywhere, then don't permit this operation
 | 
			
		||||
      return false
 | 
			
		||||
   end
 | 
			
		||||
end
 | 
			
		||||
@@ -0,0 +1,2 @@
 | 
			
		||||
IDF_project = "%%PROJECT%%"
 | 
			
		||||
IDF_push_script = "%%MTNPOSTPUSH%%"
 | 
			
		||||
							
								
								
									
										58
									
								
								src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
-- ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
-- This file is part of InDefero, an open source project management application.
 | 
			
		||||
-- Copyright (C) 2011 Céondo Ltd and contributors.
 | 
			
		||||
--
 | 
			
		||||
-- InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
-- it under the terms of the GNU General Public License as published by
 | 
			
		||||
-- the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
-- (at your option) any later version.
 | 
			
		||||
--
 | 
			
		||||
-- InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
-- GNU General Public License for more details.
 | 
			
		||||
--
 | 
			
		||||
-- You should have received a copy of the GNU General Public License
 | 
			
		||||
-- along with this program; if not, write to the Free Software
 | 
			
		||||
-- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
--
 | 
			
		||||
-- ***** END LICENSE BLOCK *****
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- let IDF know of new arriving revisions to fill its timeline
 | 
			
		||||
--
 | 
			
		||||
_idf_revs = {}
 | 
			
		||||
push_hook_functions(
 | 
			
		||||
   {
 | 
			
		||||
      start =
 | 
			
		||||
	 function (session_id)
 | 
			
		||||
	    _idf_revs[session_id] = {}
 | 
			
		||||
	    return "continue",nil
 | 
			
		||||
	 end,
 | 
			
		||||
      revision_received =
 | 
			
		||||
	 function (new_id, revision, certs, session_id)
 | 
			
		||||
	    table.insert(_idf_revs[session_id], new_id)
 | 
			
		||||
	    return "continue",nil
 | 
			
		||||
	 end,
 | 
			
		||||
      ["end"] =
 | 
			
		||||
	 function (session_id, ...)
 | 
			
		||||
	    if table.getn(_idf_revs[session_id]) == 0 then
 | 
			
		||||
	       return "continue",nil
 | 
			
		||||
	    end
 | 
			
		||||
 | 
			
		||||
	    local pin,pout,pid = spawn_pipe(IDF_push_script, IDF_project);
 | 
			
		||||
	    if pid == -1 then
 | 
			
		||||
	       print("could not execute " .. IDF_push_script)
 | 
			
		||||
	       return "continue",nil
 | 
			
		||||
	    end
 | 
			
		||||
 | 
			
		||||
	    for _,r in ipairs(_idf_revs[session_id]) do
 | 
			
		||||
	       pin:write(r .. "\n")
 | 
			
		||||
	    end
 | 
			
		||||
	    pin:close()
 | 
			
		||||
 | 
			
		||||
	    wait(pid)
 | 
			
		||||
	    return "continue",nil
 | 
			
		||||
	 end
 | 
			
		||||
   })
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								src/IDF/Plugin/SyncMonotone/monotonerc.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/IDF/Plugin/SyncMonotone/monotonerc.in
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
-- ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
-- This file is part of InDefero, an open source project management application.
 | 
			
		||||
-- Copyright (C) 2011 Céondo Ltd and contributors.
 | 
			
		||||
--
 | 
			
		||||
-- InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
-- it under the terms of the GNU General Public License as published by
 | 
			
		||||
-- the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
-- (at your option) any later version.
 | 
			
		||||
--
 | 
			
		||||
-- InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
-- GNU General Public License for more details.
 | 
			
		||||
--
 | 
			
		||||
-- You should have received a copy of the GNU General Public License
 | 
			
		||||
-- along with this program; if not, write to the Free Software
 | 
			
		||||
-- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
--
 | 
			
		||||
-- ***** END LICENSE BLOCK *****
 | 
			
		||||
 | 
			
		||||
---- Load local hooks if they exist.
 | 
			
		||||
-- The way this is supposed to work is that hooks.d can contain symbolic
 | 
			
		||||
-- links to lua scripts.  These links MUST have the extension .lua
 | 
			
		||||
-- If the script needs some configuration, a corresponding file with
 | 
			
		||||
-- the extension .conf is the right spot.
 | 
			
		||||
----
 | 
			
		||||
-- First load the configuration of the hooks, if applicable
 | 
			
		||||
includedirpattern(get_confdir() .. "/hooks.d/","*.conf")
 | 
			
		||||
-- Then load the hooks themselves
 | 
			
		||||
includedirpattern(get_confdir() .. "/hooks.d/","*.lua")
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
%%MTNCLIENTKEY%%
 | 
			
		||||
@@ -37,7 +37,7 @@ class IDF_Plugin_SyncSvn
 | 
			
		||||
        // First check for the 3 mandatory config variables.
 | 
			
		||||
        if (!Pluf::f('idf_plugin_syncsvn_authz_file', false) or
 | 
			
		||||
            !Pluf::f('idf_plugin_syncsvn_passwd_file', false) or
 | 
			
		||||
            !Pluf::f('idf_plugin_syncsvn_svn_path'. false)) {
 | 
			
		||||
            !Pluf::f('idf_plugin_syncsvn_svn_path', false)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        include_once 'File/Passwd/Authdigest.php'; // $ pear install File_Passwd
 | 
			
		||||
@@ -52,6 +52,12 @@ class IDF_Plugin_SyncSvn
 | 
			
		||||
        case 'Pluf_User::passwordUpdated':
 | 
			
		||||
            $plug->processSyncPasswd($params['user']);
 | 
			
		||||
            break;
 | 
			
		||||
        case 'IDF_Project::preDelete':
 | 
			
		||||
            $plug->processSvnDelete($params['project']);
 | 
			
		||||
            break;
 | 
			
		||||
        case 'svnpostcommit.php::run':
 | 
			
		||||
            $plug->processSvnUpdateTimeline($params);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -81,8 +87,63 @@ class IDF_Plugin_SyncSvn
 | 
			
		||||
                       escapeshellarg($svn_path.'/'.$shortname));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $ll = exec($cmd, $output, $return);
 | 
			
		||||
        if ($return != 0) {
 | 
			
		||||
            Pluf_Log::error(array('IDF_Plugin_SyncSvn::processSvnCreate', 
 | 
			
		||||
                                  'Error', 
 | 
			
		||||
                                  array('path' => $svn_path.'/'.$shortname,
 | 
			
		||||
                                        'output' => $output)));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $p = realpath(dirname(__FILE__).'/../../../scripts/svn-post-commit');
 | 
			
		||||
        exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s', 
 | 
			
		||||
                     escapeshellarg($p), 
 | 
			
		||||
                     escapeshellarg($svn_path.'/'.$shortname.'/hooks/post-commit')),
 | 
			
		||||
             $out, $res);
 | 
			
		||||
        if ($res != 0) {
 | 
			
		||||
            Pluf_Log::warn(array('IDF_Plugin_SyncSvn::processSvnCreate', 
 | 
			
		||||
                                 'post-commit hook creation error.', 
 | 
			
		||||
                                 $svn_path.'/'.$shortname.'/hooks/post-commit'));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $p = realpath(dirname(__FILE__).'/../../../scripts/svn-post-revprop-change');
 | 
			
		||||
        exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s', 
 | 
			
		||||
                     escapeshellarg($p), 
 | 
			
		||||
                     escapeshellarg($svn_path.'/'.$shortname.'/hooks/post-revprop-change')),
 | 
			
		||||
             $out, $res);
 | 
			
		||||
        if ($res != 0) {
 | 
			
		||||
            Pluf_Log::warn(array('IDF_Plugin_SyncSvn::processSvnCreate', 
 | 
			
		||||
                                 'post-revprop-change hook creation error.', 
 | 
			
		||||
                                 $svn_path.'/'.$shortname.'/hooks/post-revprop-change'));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ($return == 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove the project from the drive and update the access rights.
 | 
			
		||||
     *
 | 
			
		||||
     * @param IDF_Project 
 | 
			
		||||
     * @return bool Success
 | 
			
		||||
     */
 | 
			
		||||
    function processSvnDelete($project)
 | 
			
		||||
    {
 | 
			
		||||
        if (!Pluf::f('idf_plugin_syncsvn_remove_orphans', false)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if ($project->getConf()->getVal('scm') != 'svn') {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        $this->SyncAccess($project); // exclude $project
 | 
			
		||||
        $shortname = $project->shortname;
 | 
			
		||||
        if (false===($svn_path=Pluf::f('idf_plugin_syncsvn_svn_path',false))) {
 | 
			
		||||
            throw new Pluf_Exception_SettingError("'idf_plugin_syncsvn_svn_path' must be defined in your configuration file.");
 | 
			
		||||
        }
 | 
			
		||||
        if (file_exists($svn_path.'/'.$shortname)) {
 | 
			
		||||
            $cmd = Pluf::f('idf_exec_cmd_prefix', '').'rm -rf '.$svn_path.'/'.$shortname;
 | 
			
		||||
            exec($cmd);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronise an user's password.
 | 
			
		||||
@@ -156,8 +217,10 @@ class IDF_Plugin_SyncSvn
 | 
			
		||||
     * We rebuild the complete file each time. This is just to be sure
 | 
			
		||||
     * not to bork the rights when trying to just edit part of the
 | 
			
		||||
     * file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param IDF_Project Possibly exclude a project (null)
 | 
			
		||||
     */
 | 
			
		||||
    function SyncAccess()
 | 
			
		||||
    function SyncAccess($exclude=null)
 | 
			
		||||
    {
 | 
			
		||||
        $authz_file = Pluf::f('idf_plugin_syncsvn_authz_file');
 | 
			
		||||
        $access_owners = Pluf::f('idf_plugin_syncsvn_access_owners', 'rw');
 | 
			
		||||
@@ -170,6 +233,9 @@ class IDF_Plugin_SyncSvn
 | 
			
		||||
        }
 | 
			
		||||
        $fcontent = '';
 | 
			
		||||
        foreach (Pluf::factory('IDF_Project')->getList() as $project) {
 | 
			
		||||
            if ($exclude and $exclude->id == $project->id) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $conf = new IDF_Conf();
 | 
			
		||||
            $conf->setProject($project);
 | 
			
		||||
            if ($conf->getVal('scm') != 'svn' or 
 | 
			
		||||
@@ -199,4 +265,25 @@ class IDF_Plugin_SyncSvn
 | 
			
		||||
        file_put_contents($authz_file, $fcontent, LOCK_EX);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the timeline in post commit.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public function processSvnUpdateTimeline($params)
 | 
			
		||||
    {
 | 
			
		||||
        $pname = basename($params['repo_dir']);
 | 
			
		||||
        try {
 | 
			
		||||
            $project = IDF_Project::getOr404($pname);
 | 
			
		||||
        } catch (Pluf_HTTP_Error404 $e) {
 | 
			
		||||
            Pluf_Log::event(array('IDF_Plugin_SyncSvn::processSvnUpdateTimeline', 'Project not found.', array($pname, $params)));
 | 
			
		||||
            return false; // Project not found
 | 
			
		||||
        }
 | 
			
		||||
        // Now we have the project and can update the timeline
 | 
			
		||||
        Pluf_Log::debug(array('IDF_Plugin_SyncGit::processSvnUpdateTimeline', 'Project found', $pname, $project->id));
 | 
			
		||||
        IDF_Scm::syncTimeline($project, true);
 | 
			
		||||
        Pluf_Log::event(array('IDF_Plugin_SyncGit::processSvnUpdateTimeline', 'sync', array($pname, $project->id)));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -190,7 +190,12 @@ class IDF_Precondition
 | 
			
		||||
            return true; // Again need authentication error
 | 
			
		||||
        }
 | 
			
		||||
        $request->user = $users[0];
 | 
			
		||||
        IDF_Middleware::setRights($request);
 | 
			
		||||
        
 | 
			
		||||
        // Don't try to load projects rights access if we are not in one
 | 
			
		||||
        if ($request->query !== "/api/") {
 | 
			
		||||
            IDF_Middleware::setRights($request);
 | 
			
		||||
        }
 | 
			
		||||
            
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -230,7 +235,7 @@ class IDF_Precondition
 | 
			
		||||
            return true; // no match in the hash, anonymous
 | 
			
		||||
        }
 | 
			
		||||
        $cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
 | 
			
		||||
        list($userid, $projectid) = split(':', $cr->decrypt($encrypted), 2);
 | 
			
		||||
        list($userid, $projectid) = explode(':', $cr->decrypt($encrypted), 2);
 | 
			
		||||
        if ($projectid != $request->project->id) {
 | 
			
		||||
           return true; // anonymous
 | 
			
		||||
        }
 | 
			
		||||
@@ -256,4 +261,4 @@ class IDF_Precondition
 | 
			
		||||
        $encrypted = trim($cr->encrypt($user->id.':'.$project->id), '~');
 | 
			
		||||
        return substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,14 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
    public $_model = __CLASS__;
 | 
			
		||||
    public $_extra_cache = array();
 | 
			
		||||
    protected $_pconf = null;
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the project as one restricted tab.
 | 
			
		||||
     *
 | 
			
		||||
     * This is the cached information.
 | 
			
		||||
     *
 | 
			
		||||
     * @see self::isRestricted
 | 
			
		||||
     */
 | 
			
		||||
    protected $_isRestricted = null;
 | 
			
		||||
 | 
			
		||||
    function init()
 | 
			
		||||
    {
 | 
			
		||||
@@ -44,7 +52,7 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
                            'id' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  'blank' => true, 
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'name' =>
 | 
			
		||||
                            array(
 | 
			
		||||
@@ -105,7 +113,7 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    function preSave($create=false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->id == '') {
 | 
			
		||||
@@ -173,7 +181,7 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
     */
 | 
			
		||||
    public function getTagIdsByStatus($status='open', $cache_refresh=false)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$cache_refresh 
 | 
			
		||||
        if (!$cache_refresh
 | 
			
		||||
            and isset($this->_extra_cache['getTagIdsByStatus-'.$status])) {
 | 
			
		||||
            return $this->_extra_cache['getTagIdsByStatus-'.$status];
 | 
			
		||||
        }
 | 
			
		||||
@@ -189,7 +197,7 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        $tags = array();
 | 
			
		||||
        foreach ($this->getTagsFromConfig($key, $default, 'Status') as $tag) { 
 | 
			
		||||
        foreach ($this->getTagsFromConfig($key, $default, 'Status') as $tag) {
 | 
			
		||||
            $tags[] = (int) $tag->id;
 | 
			
		||||
        }
 | 
			
		||||
        $this->_extra_cache['getTagIdsByStatus-'.$status] = $tags;
 | 
			
		||||
@@ -210,9 +218,9 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
        $conf = $this->getConf();
 | 
			
		||||
        $tags = array();
 | 
			
		||||
        foreach (preg_split("/\015\012|\015|\012/", $conf->getVal($cfg_key, $default), -1, PREG_SPLIT_NO_EMPTY) as $s) {
 | 
			
		||||
            $_s = split('=', $s, 2);
 | 
			
		||||
            $_s = explode('=', $s, 2);
 | 
			
		||||
            $v = trim($_s[0]);
 | 
			
		||||
            $_v = split(':', $v, 2);
 | 
			
		||||
            $_v = explode(':', $v, 2);
 | 
			
		||||
            if (count($_v) > 1) {
 | 
			
		||||
                $class = trim($_v[0]);
 | 
			
		||||
                $name = trim($_v[1]);
 | 
			
		||||
@@ -281,9 +289,9 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
        if ($fmt == 'objects') {
 | 
			
		||||
            return new Pluf_Template_ContextVars(array('members' => $members, 'owners' => $owners, 'authorized' => $authorized));
 | 
			
		||||
        } else {
 | 
			
		||||
            return array('members' => implode("\n", (array) $members), 
 | 
			
		||||
            return array('members' => implode("\n", (array) $members),
 | 
			
		||||
                         'owners' => implode("\n", (array) $owners),
 | 
			
		||||
                         'authorized' => implode("\n", (array) $authorized), 
 | 
			
		||||
                         'authorized' => implode("\n", (array) $authorized),
 | 
			
		||||
                         );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -349,17 +357,59 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
        return new Pluf_Template_ContextVars($tags);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the repository size.
 | 
			
		||||
     *
 | 
			
		||||
     * @param bool Force to skip the cache (false)
 | 
			
		||||
     * @return int Size in byte or -1 if not available
 | 
			
		||||
     */
 | 
			
		||||
    public function getRepositorySize($force=false)
 | 
			
		||||
    {
 | 
			
		||||
        $last_eval = $this->getConf()->getVal('repository_size_check_date', 0);
 | 
			
		||||
        if (Pluf::f('idf_no_size_check', false) or
 | 
			
		||||
            (!$force and $last_eval > time()-172800)) {
 | 
			
		||||
            return $this->getConf()->getVal('repository_size', -1);
 | 
			
		||||
        }
 | 
			
		||||
        $this->getConf()->setVal('repository_size_check_date', time());
 | 
			
		||||
        $scm = IDF_Scm::get($this);
 | 
			
		||||
        $this->getConf()->setVal('repository_size', $scm->getRepositorySize());
 | 
			
		||||
        return $this->getConf()->getVal('repository_size', -1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the access url to the repository.
 | 
			
		||||
     *
 | 
			
		||||
     * This will return the right url based on the user.
 | 
			
		||||
     *
 | 
			
		||||
     * @param Pluf_User The user (null)
 | 
			
		||||
     * @param string    A specific commit to access
 | 
			
		||||
     */
 | 
			
		||||
    public function getSourceAccessUrl($user=null, $commit=null)
 | 
			
		||||
    {
 | 
			
		||||
        $right = $this->getConf()->getVal('source_access_rights', 'all');
 | 
			
		||||
        if (($user == null or $user->isAnonymous())
 | 
			
		||||
            and  $right == 'all' and !$this->private) {
 | 
			
		||||
            return $this->getRemoteAccessUrl($commit);
 | 
			
		||||
        }
 | 
			
		||||
        return $this->getWriteRemoteAccessUrl($user, $commit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the remote access url to the repository.
 | 
			
		||||
     *
 | 
			
		||||
     * This will always return the anonymous access url.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string    A specific commit to access
 | 
			
		||||
     */
 | 
			
		||||
    public function getRemoteAccessUrl()
 | 
			
		||||
    public function getRemoteAccessUrl($commit=null)
 | 
			
		||||
    {
 | 
			
		||||
        $conf = $this->getConf();
 | 
			
		||||
        $scm = $conf->getVal('scm', 'git');
 | 
			
		||||
        $scms = Pluf::f('allowed_scm');
 | 
			
		||||
        Pluf::loadClass($scms[$scm]);
 | 
			
		||||
        return call_user_func(array($scms[$scm], 'getAnonymousAccessUrl'),
 | 
			
		||||
                              $this);
 | 
			
		||||
                              $this, $commit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -368,14 +418,27 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
     * Some SCM have a remote access URL to write which is not the
 | 
			
		||||
     * same as the one to read. For example, you do a checkout with
 | 
			
		||||
     * git-daemon and push with SSH.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string    A specific commit to access
 | 
			
		||||
     */
 | 
			
		||||
    public function getWriteRemoteAccessUrl($user)
 | 
			
		||||
    public function getWriteRemoteAccessUrl($user,$commit=null)
 | 
			
		||||
    {
 | 
			
		||||
        $conf = $this->getConf();
 | 
			
		||||
        $scm = $conf->getVal('scm', 'git');
 | 
			
		||||
        $scms = Pluf::f('allowed_scm');
 | 
			
		||||
        return call_user_func(array($scms[$scm], 'getAuthAccessUrl'),
 | 
			
		||||
                              $this, $user);
 | 
			
		||||
                              $this, $user, $commit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the post commit hook key.
 | 
			
		||||
     *
 | 
			
		||||
     * The goal is to get something predictable but from which one
 | 
			
		||||
     * cannot reverse find the secret key.
 | 
			
		||||
     */
 | 
			
		||||
    public function getPostCommitHookKey()
 | 
			
		||||
    {
 | 
			
		||||
        return md5($this->id.sha1(Pluf::f('secret_key')).$this->shortname);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -387,9 +450,10 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
    {
 | 
			
		||||
        $conf = $this->getConf();
 | 
			
		||||
        $roots = array(
 | 
			
		||||
                       'git' => 'master', 
 | 
			
		||||
                       'svn' => 'HEAD', 
 | 
			
		||||
                       'mercurial' => 'tip'
 | 
			
		||||
                       'git' => 'master',
 | 
			
		||||
                       'svn' => 'HEAD',
 | 
			
		||||
                       'mercurial' => 'tip',
 | 
			
		||||
                       'mtn' => 'h:'.$conf->getVal('mtn_master_branch', '*'),
 | 
			
		||||
                       );
 | 
			
		||||
        $scm = $conf->getVal('scm', 'git');
 | 
			
		||||
        return $roots[$scm];
 | 
			
		||||
@@ -402,7 +466,7 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
     * By convention, all the objects belonging to a project have the
 | 
			
		||||
     * 'project' property set, so this is easy to check.
 | 
			
		||||
     *
 | 
			
		||||
     * @param Pluf_Model 
 | 
			
		||||
     * @param Pluf_Model
 | 
			
		||||
     */
 | 
			
		||||
    public function inOr404($obj)
 | 
			
		||||
    {
 | 
			
		||||
@@ -459,7 +523,7 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
         *
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal allows an application to update the statistics 
 | 
			
		||||
         * This signal allows an application to update the statistics
 | 
			
		||||
         * array of a project. For example to add the on disk size
 | 
			
		||||
         * of the repository if available.
 | 
			
		||||
         *
 | 
			
		||||
@@ -548,8 +612,30 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
     */
 | 
			
		||||
    public function preDelete()
 | 
			
		||||
    {
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Project::preDelete
 | 
			
		||||
         *
 | 
			
		||||
         * [sender]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Project
 | 
			
		||||
         *
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal allows an application to perform special
 | 
			
		||||
         * operations at the deletion of a project.
 | 
			
		||||
         *
 | 
			
		||||
         * [parameters]
 | 
			
		||||
         *
 | 
			
		||||
         * array('project' => $project)
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        $params = array('project' => $this);
 | 
			
		||||
        Pluf_Signal::send('IDF_Project::preDelete',
 | 
			
		||||
                          'IDF_Project', $params);
 | 
			
		||||
        $what = array('IDF_Upload', 'IDF_Review', 'IDF_Issue',
 | 
			
		||||
                      'IDF_WikiPage', 'IDF_Commit',
 | 
			
		||||
                      'IDF_WikiPage', 'IDF_Commit', 'IDF_Tag',
 | 
			
		||||
                      );
 | 
			
		||||
        foreach ($what as $m) {
 | 
			
		||||
            foreach (Pluf::factory($m)->getList(array('filter' => 'project='.(int)$this->id)) as $item) {
 | 
			
		||||
@@ -557,4 +643,37 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the project has one restricted tab.
 | 
			
		||||
     *
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function isRestricted()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->_isRestricted !== null) {
 | 
			
		||||
            return $this->_isRestricted;
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->private) {
 | 
			
		||||
            $this->_isRestricted = true;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        $tabs = array(
 | 
			
		||||
                      'source_access_rights',
 | 
			
		||||
                      'issues_access_rights',
 | 
			
		||||
                      'downloads_access_rights',
 | 
			
		||||
                      'wiki_access_rights',
 | 
			
		||||
                      'review_access_rights'
 | 
			
		||||
                      );
 | 
			
		||||
        $conf = $this->getConf();
 | 
			
		||||
        foreach ($tabs as $tab) {
 | 
			
		||||
            if (!in_array($conf->getVal($tab, 'all'),
 | 
			
		||||
                          array('all', 'none'))) {
 | 
			
		||||
                $this->_isRestricted = true;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $this->_isRestricted = false;
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										222
									
								
								src/IDF/Queue.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								src/IDF/Queue.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
n# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Queue system for the management of asynchronous operations.
 | 
			
		||||
 *
 | 
			
		||||
 * Anybody can add an item to the queue and any application can
 | 
			
		||||
 * register itself to process an item from the queue.
 | 
			
		||||
 *
 | 
			
		||||
 * An item in the queue is considered as fully processed when all the
 | 
			
		||||
 * handlers have processed it successfully.
 | 
			
		||||
 *
 | 
			
		||||
 * To push a new item in the queue:
 | 
			
		||||
 *
 | 
			
		||||
 * <code>
 | 
			
		||||
 * $item = new IDF_Queue();
 | 
			
		||||
 * $item->type = 'new_commit';
 | 
			
		||||
 * $item->payload = array('what', 'ever', array('data'));
 | 
			
		||||
 * $item->create();
 | 
			
		||||
 * </code>
 | 
			
		||||
 *
 | 
			
		||||
 * To process one item from the queue, you first need to register an
 | 
			
		||||
 * handler, by adding the following in your relations.php file before
 | 
			
		||||
 * the return statement or in your config file.
 | 
			
		||||
 *
 | 
			
		||||
 * <code>
 | 
			
		||||
 * Pluf_Signal::connect('IDF_Queue::processItem', 
 | 
			
		||||
 *                       array('YourApp_Class', 'processItem'));
 | 
			
		||||
 * </code>
 | 
			
		||||
 *
 | 
			
		||||
 * The processItem method will be called with two arguments, the first
 | 
			
		||||
 * is the name of the signal ('IDF_Queue::processItem') and the second
 | 
			
		||||
 * is an array with:
 | 
			
		||||
 *
 | 
			
		||||
 * <code>
 | 
			
		||||
 * array('item' => $item,
 | 
			
		||||
 *       'res' => array('OtherApp_Class::handler' => false,
 | 
			
		||||
 *                      'FooApp_Class::processItem' => true));
 | 
			
		||||
 * </code>
 | 
			
		||||
 *
 | 
			
		||||
 * When you process an item, you need first to check if the type is
 | 
			
		||||
 * corresponding to what you want to work with, then you need to check
 | 
			
		||||
 * in 'res' if you have not already processed successfully the item,
 | 
			
		||||
 * that is the key 'YourApp_Class::processItem' must be set to true,
 | 
			
		||||
 * and then you can process the item. At the end of your processing,
 | 
			
		||||
 * you need to modify by reference the 'res' key to add your status.
 | 
			
		||||
 *
 | 
			
		||||
 * All the data except for the type is in the payload, this makes the
 | 
			
		||||
 * queue flexible to manage many different kind of tasks.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Queue extends Pluf_Model
 | 
			
		||||
{
 | 
			
		||||
    public $_model = __CLASS__;
 | 
			
		||||
 | 
			
		||||
    function init()
 | 
			
		||||
    {
 | 
			
		||||
        $this->_a['table'] = 'idf_queue';
 | 
			
		||||
        $this->_a['model'] = __CLASS__;
 | 
			
		||||
        $this->_a['cols'] = array(
 | 
			
		||||
                             // It is mandatory to have an "id" column.
 | 
			
		||||
                            'id' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  'blank' => true, 
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'status' => 
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Integer',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'choices' => array(
 | 
			
		||||
                                                     'pending' => 0,
 | 
			
		||||
                                                     'in_progress' => 1,
 | 
			
		||||
                                                     'need_retry' => 2,
 | 
			
		||||
                                                     'done' => 3,
 | 
			
		||||
                                                     'error' => 4,
 | 
			
		||||
                                                     ),
 | 
			
		||||
                                  'default' => 0,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'trials' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Integer',
 | 
			
		||||
                                  'default' => 0,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'type' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Varchar',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'size' => 50,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'payload' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Serialized',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'results' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Serialized',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'lasttry_dtime' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Datetime',
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'creation_dtime' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Datetime',
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function preSave($create=false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($create) {
 | 
			
		||||
            $this->creation_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
            $this->lasttry_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
            $this->results = array();
 | 
			
		||||
            $this->trials = 0;
 | 
			
		||||
            $this->status = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The current item is going to be processed.
 | 
			
		||||
     */
 | 
			
		||||
    function processItem()
 | 
			
		||||
    {
 | 
			
		||||
        /**
 | 
			
		||||
         * [signal]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Queue::processItem
 | 
			
		||||
         *
 | 
			
		||||
         * [sender]
 | 
			
		||||
         *
 | 
			
		||||
         * IDF_Queue
 | 
			
		||||
         *
 | 
			
		||||
         * [description]
 | 
			
		||||
         *
 | 
			
		||||
         * This signal allows an application to run an asynchronous
 | 
			
		||||
         * job. The handler gets the queue item and the results from
 | 
			
		||||
         * the previous run. If the handler key is not set, then the
 | 
			
		||||
         * job was not run. If set it can be either true (already done)
 | 
			
		||||
         * or false (error at last run).
 | 
			
		||||
         *
 | 
			
		||||
         * [parameters]
 | 
			
		||||
         *
 | 
			
		||||
         * array('item' => $item, 'res' => $res)
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        $params = array('item' => $this, 'res' => $this->results);
 | 
			
		||||
        Pluf_Signal::send('IDF_Queue::processItem',
 | 
			
		||||
                          'IDF_Queue', $params);
 | 
			
		||||
        $this->status = 3; // Success
 | 
			
		||||
        foreach ($params['res'] as $handler=>$ok) {
 | 
			
		||||
            if (!$ok) {
 | 
			
		||||
                $this->status = 2; // Set to need retry
 | 
			
		||||
                $this->trials += 1;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $this->results = $params['res'];
 | 
			
		||||
        $this->lasttry_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
        $this->update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** 
 | 
			
		||||
     * Parse the queue.
 | 
			
		||||
     *
 | 
			
		||||
     * It is a signal handler to just hook itself at the right time in
 | 
			
		||||
     * the cron job performing the maintainance work.
 | 
			
		||||
     *
 | 
			
		||||
     * The processing relies on the fact that no other processing jobs
 | 
			
		||||
     * must run at the same time. That is, your cron job must use a
 | 
			
		||||
     * lock file or something like to not run in parallel.
 | 
			
		||||
     *
 | 
			
		||||
     * The processing is simple, first get 500 queue items, mark them
 | 
			
		||||
     * as being processed and for each of them call the processItem()
 | 
			
		||||
     * method which will trigger another event for processing.
 | 
			
		||||
     *
 | 
			
		||||
     * If you are processing more than 500 items per batch, you need
 | 
			
		||||
     * to switch to a different solution.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public static function process($sender, &$params)
 | 
			
		||||
    {
 | 
			
		||||
        $where = 'status=0 OR status=2';
 | 
			
		||||
        $items = Pluf::factory('IDF_Queue')->getList(array('filter'=>$where,
 | 
			
		||||
                                                           'nb'=> 500));
 | 
			
		||||
        Pluf_Log::event(array('IDF_Queue::process', $items->count()));
 | 
			
		||||
        foreach ($items as $item) {
 | 
			
		||||
            $item->status = 1;
 | 
			
		||||
            $item->update();
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($items as $item) {
 | 
			
		||||
            $item->status = 1;
 | 
			
		||||
            $item->processItem();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -32,6 +32,15 @@ Pluf::loadFunction('Pluf_Template_dateAgo');
 | 
			
		||||
 *
 | 
			
		||||
 * The real content of the review is in the IDF_Review_Patch which
 | 
			
		||||
 * contains a given patch and associated comments from reviewers.
 | 
			
		||||
 *
 | 
			
		||||
 * Basically the hierarchy of the models is:
 | 
			
		||||
 * - Review > Patch > Comment > Comment on file
 | 
			
		||||
 * 
 | 
			
		||||
 * For each review, one can have several patches. Each patch, is
 | 
			
		||||
 * getting a series of comments. A comment is tracking the state
 | 
			
		||||
 * change in the review (like the issue comments). For each comment,
 | 
			
		||||
 * we have a series of file comments. The file comments are associated
 | 
			
		||||
 * to the a given modified file in the patch.
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Review extends Pluf_Model
 | 
			
		||||
{
 | 
			
		||||
@@ -71,12 +80,12 @@ class IDF_Review extends Pluf_Model
 | 
			
		||||
                                  'verbose' => __('submitter'),
 | 
			
		||||
                                  'relate_name' => 'submitted_review',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'reviewers' => 
 | 
			
		||||
                            'interested' => 
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Manytomany',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  'help_text' => 'Reviewers will get an email notification when the review is changed.',
 | 
			
		||||
                                  'help_text' => 'Interested users will get an email notification when the review is changed.',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'tags' =>
 | 
			
		||||
                            array(
 | 
			
		||||
@@ -117,11 +126,25 @@ class IDF_Review extends Pluf_Model
 | 
			
		||||
                              'join_tags' => 
 | 
			
		||||
                              array(
 | 
			
		||||
                                    'join' => 'LEFT JOIN '.$table
 | 
			
		||||
                                    .' ON idf_issue_id=id',
 | 
			
		||||
                                    .' ON idf_review_id=id',
 | 
			
		||||
                                    ),
 | 
			
		||||
                                   );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Iterate through the patches and comments to get the reviewers.
 | 
			
		||||
     */
 | 
			
		||||
    function getReviewers()
 | 
			
		||||
    {
 | 
			
		||||
        $rev = new ArrayObject();
 | 
			
		||||
        foreach ($this->get_patches_list() as $p) {
 | 
			
		||||
            foreach ($p->get_comments_list() as $c) {
 | 
			
		||||
                $rev[] = $c->get_submitter();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return Pluf_Model_RemoveDuplicates($rev);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function __toString()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->id.' - '.$this->summary;
 | 
			
		||||
@@ -140,7 +163,7 @@ class IDF_Review extends Pluf_Model
 | 
			
		||||
 | 
			
		||||
    function preSave($create=false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->id == '') {
 | 
			
		||||
        if ($create) {
 | 
			
		||||
            $this->creation_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
        }
 | 
			
		||||
        $this->modif_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
@@ -148,6 +171,8 @@ class IDF_Review extends Pluf_Model
 | 
			
		||||
 | 
			
		||||
    function postSave($create=false)
 | 
			
		||||
    {
 | 
			
		||||
        // At creation, we index after saving the associated patch.
 | 
			
		||||
        if (!$create) IDF_Search::index($this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										229
									
								
								src/IDF/Review/Comment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								src/IDF/Review/Comment.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,229 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A comment set on a review.
 | 
			
		||||
 *
 | 
			
		||||
 * A comment is associated to a patch as a review can have many
 | 
			
		||||
 * patches associated to it.
 | 
			
		||||
 *
 | 
			
		||||
 * A comment is also tracking the changes in the review in the same
 | 
			
		||||
 * way the issue comment is tracking the changes in the issue.
 | 
			
		||||
 *
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Review_Comment extends Pluf_Model
 | 
			
		||||
{
 | 
			
		||||
    public $_model = __CLASS__;
 | 
			
		||||
 | 
			
		||||
    function init()
 | 
			
		||||
    {
 | 
			
		||||
        $this->_a['table'] = 'idf_review_comments';
 | 
			
		||||
        $this->_a['model'] = __CLASS__;
 | 
			
		||||
        $this->_a['cols'] = array(
 | 
			
		||||
                             // It is mandatory to have an "id" column.
 | 
			
		||||
                            'id' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'patch' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'IDF_Review_Patch',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('patch'),
 | 
			
		||||
                                  'relate_name' => 'comments',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'content' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Text',
 | 
			
		||||
                                  'blank' => true, // if only commented on lines
 | 
			
		||||
                                  'verbose' => __('comment'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'submitter' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('submitter'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'changes' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Serialized',
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  'verbose' => __('changes'),
 | 
			
		||||
                                  'help_text' => 'Serialized array of the changes in the review.',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'vote' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Integer',
 | 
			
		||||
                                  'default' => 0,
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  'verbose' => __('vote'),
 | 
			
		||||
                                  'help_text' => '1, 0 or -1 for positive, neutral or negative vote.',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'creation_dtime' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Datetime',
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  'verbose' => __('creation date'),
 | 
			
		||||
                                  'index' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function changedReview()
 | 
			
		||||
    {
 | 
			
		||||
        return (is_array($this->changes) and count($this->changes) > 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function _toIndex()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->content;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function preDelete()
 | 
			
		||||
    {
 | 
			
		||||
        IDF_Timeline::remove($this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function preSave($create=false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($create) {
 | 
			
		||||
            $this->creation_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function postSave($create=false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($create) {
 | 
			
		||||
            IDF_Timeline::insert($this,
 | 
			
		||||
                                 $this->get_patch()->get_review()->get_project(),
 | 
			
		||||
                                 $this->get_submitter());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function timelineFragment($request)
 | 
			
		||||
    {
 | 
			
		||||
        $review = $this->get_patch()->get_review();
 | 
			
		||||
        $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
 | 
			
		||||
                                        array($request->project->shortname,
 | 
			
		||||
                                              $review->id));
 | 
			
		||||
        $out = '<tr class="log"><td><a href="'.$url.'">'.
 | 
			
		||||
            Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
 | 
			
		||||
            '</a></td><td>';
 | 
			
		||||
        $stag = new IDF_Template_ShowUser();
 | 
			
		||||
        $user = $stag->start($this->get_submitter(), $request, '', false);
 | 
			
		||||
        $ic = (in_array($review->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
 | 
			
		||||
        $out .= sprintf(__('<a href="%1$s" class="%2$s" title="View review">Review %3$d</a>, %4$s'), $url, $ic, $review->id, Pluf_esc($review->summary)).'</td>';
 | 
			
		||||
        $out .= "\n".'<tr class="extra"><td colspan="2">
 | 
			
		||||
<div class="helptext right">'.sprintf(__('Update of <a href="%s" class="%s">review %d</a>, by %s'), $url, $ic, $review->id, $user).'</div></td></tr>';
 | 
			
		||||
        return Pluf_Template::markSafe($out);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function feedFragment($request)
 | 
			
		||||
    {
 | 
			
		||||
        $review = $this->get_patch()->get_review();
 | 
			
		||||
        $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
 | 
			
		||||
                                        array($request->project->shortname,
 | 
			
		||||
                                              $review->id));
 | 
			
		||||
        $title = sprintf(__('%s: Updated review %d - %s'),
 | 
			
		||||
                         Pluf_esc($request->project->name),
 | 
			
		||||
                         $review->id, Pluf_esc($review->summary));
 | 
			
		||||
        $url .= '#ic'.$this->id;
 | 
			
		||||
        $date = Pluf_Date::gmDateToGmString($this->creation_dtime);
 | 
			
		||||
        $context = new Pluf_Template_Context_Request(
 | 
			
		||||
                       $request,
 | 
			
		||||
                       array('url' => $url,
 | 
			
		||||
                             'author' => $this->get_submitter(),
 | 
			
		||||
                             'title' => $title,
 | 
			
		||||
                             'c' => $this,
 | 
			
		||||
                             'review' => $review,
 | 
			
		||||
                             'date' => $date)
 | 
			
		||||
                                                     );
 | 
			
		||||
        $tmpl = new Pluf_Template('idf/review/feedfragment.xml');
 | 
			
		||||
        return $tmpl->render($context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notify of the update of the review.
 | 
			
		||||
     *
 | 
			
		||||
     *
 | 
			
		||||
     * @param IDF_Conf Current configuration
 | 
			
		||||
     * @param bool Creation (true)
 | 
			
		||||
     */
 | 
			
		||||
    public function notify($conf, $create=true)
 | 
			
		||||
    {
 | 
			
		||||
        $patch = $this->get_patch();
 | 
			
		||||
        $review = $patch->get_review();
 | 
			
		||||
        $prj = $review->get_project();
 | 
			
		||||
        $to_email = array();
 | 
			
		||||
        if ('' != $conf->getVal('review_notification_email', '')) {
 | 
			
		||||
            $langs = Pluf::f('languages', array('en'));
 | 
			
		||||
            $to_email[] = array($conf->getVal('issues_notification_email'),
 | 
			
		||||
                                $langs[0]);
 | 
			
		||||
        }
 | 
			
		||||
        $current_locale = Pluf_Translation::getLocale();
 | 
			
		||||
        $reviewers = $review->getReviewers();
 | 
			
		||||
        if (!Pluf_Model_InArray($review->get_submitter(), $reviewers)) {
 | 
			
		||||
            $reviewers[] = $review->get_submitter();
 | 
			
		||||
        }
 | 
			
		||||
        $comments = $patch->getFileComments(array('order' => 'id DESC'));
 | 
			
		||||
        $gcomments = $patch->get_comments_list(array('order' => 'id DESC'));
 | 
			
		||||
        $context = new Pluf_Template_Context(
 | 
			
		||||
                       array(
 | 
			
		||||
                             'review' => $review,
 | 
			
		||||
                             'patch' => $patch,
 | 
			
		||||
                             'comments' => $comments,
 | 
			
		||||
                             'gcomments' => $gcomments,
 | 
			
		||||
                             'project' => $prj,
 | 
			
		||||
                             'url_base' => Pluf::f('url_base'),
 | 
			
		||||
                             )
 | 
			
		||||
                                             );
 | 
			
		||||
        // build the list of emails and lang
 | 
			
		||||
        foreach ($reviewers as $user) {
 | 
			
		||||
            $email_lang = array($user->email,
 | 
			
		||||
                                $user->language);
 | 
			
		||||
            if (!in_array($email_lang, $to_email)) {
 | 
			
		||||
                $to_email[] = $email_lang;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $tmpl = new Pluf_Template('idf/review/review-updated-email.txt');
 | 
			
		||||
        foreach ($to_email as $email_lang) {
 | 
			
		||||
            Pluf_Translation::loadSetLocale($email_lang[1]);
 | 
			
		||||
            $email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
 | 
			
		||||
                                   sprintf(__('Updated Code Review %s - %s (%s)'),
 | 
			
		||||
                                           $review->id, $review->summary, $prj->shortname));
 | 
			
		||||
 | 
			
		||||
            $email->addTextMessage($tmpl->render($context));
 | 
			
		||||
            $email->sendMail();
 | 
			
		||||
        }
 | 
			
		||||
        Pluf_Translation::loadSetLocale($current_locale);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function get_submitter_data()
 | 
			
		||||
    {
 | 
			
		||||
        return IDF_UserData::factory($this->get_submitter());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -40,13 +40,13 @@ class IDF_Review_FileComment extends Pluf_Model
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  'blank' => true, 
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'patch' => 
 | 
			
		||||
                            'comment' => 
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'IDF_Review_Patch',
 | 
			
		||||
                                  'model' => 'IDF_Review_Comment',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('patch'),
 | 
			
		||||
                                  'relate_name' => 'filecomments',
 | 
			
		||||
                                  'verbose' => __('comment'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'cfile' =>
 | 
			
		||||
                            array(
 | 
			
		||||
@@ -55,32 +55,25 @@ class IDF_Review_FileComment extends Pluf_Model
 | 
			
		||||
                                  'size' => 250,
 | 
			
		||||
                                  'help_text' => 'The changed file, for example src/foo/bar.txt, this is the path to access it in the repository.',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'cline' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Integer',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'default' => 0,
 | 
			
		||||
                                  'help_text' => 'The commented line, negative value is the old file, positive the new, 0 general comment.',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'content' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Text',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('comment'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'submitter' => 
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('submitter'),
 | 
			
		||||
                                  'relate_name' => 'commented_patched_files',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'creation_dtime' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Datetime',
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  'verbose' => __('creation date'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
        $this->_a['idx'] = array(                           
 | 
			
		||||
                            'creation_dtime_idx' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'col' => 'creation_dtime',
 | 
			
		||||
                                  'type' => 'normal',
 | 
			
		||||
                                  'index' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
    }
 | 
			
		||||
@@ -92,12 +85,11 @@ class IDF_Review_FileComment extends Pluf_Model
 | 
			
		||||
 | 
			
		||||
    function preDelete()
 | 
			
		||||
    {
 | 
			
		||||
        IDF_Timeline::remove($this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function preSave($create=false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->id == '') {
 | 
			
		||||
        if ($create) {
 | 
			
		||||
            $this->creation_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user