partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

commit 605531f57ea5e6a6f38a27ec86637be27fd835a8
parent 017f7f1f1b38a3e9ac7461d1feac85b9c4647161
Author: felicitus <felicitus@felicitus.org>
Date:   Sun, 24 Jun 2012 02:40:17 +0200

Merge branch 'master' of github.com:partkeepr/PartKeepr

Diffstat:
Mbuild.properties | 5+++--
Mbuild.xml | 16+++++++++-------
Mcli-config.php | 2+-
Mconfig-test.php | 4++--
Mconfig.php.template | 4++--
Mcronjobs/CheckForUpdates.php | 8++++----
Mcronjobs/CreateRSSFeed.php | 10+++++-----
Mcronjobs/CreateStatisticSnapshot.php | 10+++++-----
Mcronjobs/UpdatePartCacheData.php | 12++++++------
Mcronjobs/UpdateTipsOfTheDay.php | 10+++++-----
Mdoctrine.php | 6+++---
Mdocumentation/internals/NEW-RELEASE | 3+++
Mmigrations.yml | 4++--
Mscripts/UpdateCategoryPathCache.php | 12++++++------
Asrc/backend/PartKeepr/Auth/AuthService.php | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Category/AbstractCategory.php | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Category/AbstractCategoryManager.php | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Category/AbstractCategoryService.php | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Category/Exceptions/CategoryNotFoundException.php | 16++++++++++++++++
Asrc/backend/PartKeepr/CronLogger/CronLogger.php | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/CronLogger/CronLoggerManager.php | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Distributor/Distributor.php | 232+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Distributor/DistributorManager.php | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Distributor/DistributorService.php | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Footprint/Footprint.php | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Footprint/FootprintAttachment.php | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Footprint/FootprintImage.php | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Footprint/FootprintManager.php | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Footprint/FootprintService.php | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/FootprintAttachment/FootprintAttachmentManager.php | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/FootprintAttachment/FootprintAttachmentService.php | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/FootprintCategory/FootprintCategory.php | 15+++++++++++++++
Asrc/backend/PartKeepr/FootprintCategory/FootprintCategoryManager.php | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/FootprintCategory/FootprintCategoryService.php | 10++++++++++
Asrc/backend/PartKeepr/FulltextSearch/FulltextSearch.php | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Image/CachedImage.php | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Image/Exceptions/InvalidImageTypeException.php | 12++++++++++++
Asrc/backend/PartKeepr/Image/Image.php | 228+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Image/ImageRenderer.php | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Image/RenderableImage.php | 17+++++++++++++++++
Asrc/backend/PartKeepr/Logger/Logger.php | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Manager/AbstractManager.php | 231+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Manager/Exceptions/EntityInUseException.php | 32++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Manager/ManagerFilter.php | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Manager/Sorter.php | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Manufacturer/Manufacturer.php | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Manufacturer/ManufacturerICLogo.php | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Manufacturer/ManufacturerManager.php | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Manufacturer/ManufacturerService.php | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/ManufacturerICLogo/ManufacturerICLogoManager.php | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/ManufacturerICLogo/ManufacturerICLogoService.php | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Part/Exceptions/CategoryNotAssignedException.php | 21+++++++++++++++++++++
Asrc/backend/PartKeepr/Part/Exceptions/StorageLocationNotAssignedException.php | 20++++++++++++++++++++
Asrc/backend/PartKeepr/Part/Part.php | 640+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Part/PartAttachment.php | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Part/PartDistributor.php | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Part/PartFulltextSearch.php | 29+++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Part/PartImage.php | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Part/PartManager.php | 442+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Part/PartManufacturer.php | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Part/PartService.php | 263+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Part/PartUnit.php | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/PartCategory/PartCategory.php | 15+++++++++++++++
Asrc/backend/PartKeepr/PartCategory/PartCategoryManager.php | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/PartCategory/PartCategoryService.php | 10++++++++++
Asrc/backend/PartKeepr/PartDistributor/PartDistributorManager.php | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/PartDistributor/PartDistributorService.php | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/PartKeepr.php | 435+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/PartKeeprVersion.php | 16++++++++++++++++
Asrc/backend/PartKeepr/PartParameter/PartParameter.php | 247+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/PartUnit/PartUnitManager.php | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/PartUnit/PartUnitService.php | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Ping/PingService.php | 20++++++++++++++++++++
Asrc/backend/PartKeepr/Project/Project.php | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Project/ProjectAttachment.php | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Project/ProjectManager.php | 34++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Project/ProjectPart.php | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Project/ProjectService.php | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/ProjectAttachment/ProjectAttachmentManager.php | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/ProjectAttachment/ProjectAttachmentService.php | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/ProjectReport/ProjectReportService.php | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/REST/ApplicationController.php | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/REST/Model.php | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/REST/Request.php | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/REST/Response.php | 21+++++++++++++++++++++
Asrc/backend/PartKeepr/Service/AdminService.php | 14++++++++++++++
Asrc/backend/PartKeepr/Service/AnonService.php | 6++++++
Asrc/backend/PartKeepr/Service/Exceptions/ServiceException.php | 6++++++
Asrc/backend/PartKeepr/Service/RestfulService.php | 10++++++++++
Asrc/backend/PartKeepr/Service/Service.php | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/ServiceManager.php | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Session/Exceptions/SessionNotFoundException.php | 11+++++++++++
Asrc/backend/PartKeepr/Session/Session.php | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Session/SessionManager.php | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/AbstractSetup.php | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/ConfigFileSetup.php | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/FootprintSetup.php | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/ManufacturerSetup.php | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/Migration/PartDB/DistributorMigration.php | 34++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/Migration/PartDB/FootprintMigration.php | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/Migration/PartDB/PartCategoryMigration.php | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/Migration/PartDB/PartDBMigration.php | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/Migration/PartDB/PartMigration.php | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/Migration/PartDB/StorageLocationMigration.php | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/MiscSettingsSetup.php | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/PartCategorySetup.php | 28++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/PartUnitSetup.php | 36++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/SchemaSetup.php | 35+++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/Setup.php | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/SiPrefixSetup.php | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/UnitSetup.php | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Setup/UserSetup.php | 32++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/SiPrefix/SiPrefix.php | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/SiPrefix/SiPrefixManager.php | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/SiPrefix/SiPrefixService.php | 27+++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Statistic/StatisticService.php | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Statistic/StatisticSnapshot.php | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Statistic/StatisticSnapshotManager.php | 39+++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Statistic/StatisticSnapshotUnit.php | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Stock/StockEntry.php | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Stock/StockManager.php | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Stock/StockService.php | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/StorageLocation/Exceptions/StorageLocationNotFoundException.php | 13+++++++++++++
Asrc/backend/PartKeepr/StorageLocation/StorageLocation.php | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/StorageLocation/StorageLocationImage.php | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/StorageLocation/StorageLocationManager.php | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/StorageLocation/StorageLocationService.php | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/System/SystemInformationRecord.php | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/System/SystemService.php | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/SystemNotice/SystemNotice.php | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/SystemNotice/SystemNoticeManager.php | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/SystemNotice/SystemNoticeService.php | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/TempFile/TempFileService.php | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/TempImage/TempImage.php | 20++++++++++++++++++++
Asrc/backend/PartKeepr/TempImage/TempImageService.php | 31+++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/TipOfTheDay/TipOfTheDay.php | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/TipOfTheDay/TipOfTheDayHistory.php | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/TipOfTheDay/TipOfTheDayService.php | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Unit/Unit.php | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Unit/UnitManager.php | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Unit/UnitService.php | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/UploadedFile/TempUploadedFile.php | 15+++++++++++++++
Asrc/backend/PartKeepr/UploadedFile/UploadedFile.php | 301+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/User/Exceptions/InvalidLoginDataException.php | 15+++++++++++++++
Asrc/backend/PartKeepr/User/Exceptions/UserAlreadyExistsException.php | 20++++++++++++++++++++
Asrc/backend/PartKeepr/User/Exceptions/UserDoesNotExistException.php | 18++++++++++++++++++
Asrc/backend/PartKeepr/User/User.php | 260+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/User/UserManager.php | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/User/UserService.php | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/UserPreference/Exceptions/UserPreferenceNotFoundException.php | 21+++++++++++++++++++++
Asrc/backend/PartKeepr/UserPreference/UserPreference.php | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/UserPreference/UserPreferenceService.php | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Util/BaseEntity.php | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Util/Configuration.php | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Util/Deserializable.php | 11+++++++++++
Asrc/backend/PartKeepr/Util/Exceptions/EntityNotFoundException.php | 17+++++++++++++++++
Asrc/backend/PartKeepr/Util/Exceptions/EntityNotPersistantException.php | 14++++++++++++++
Asrc/backend/PartKeepr/Util/Exceptions/OutOfRangeException.php | 6++++++
Asrc/backend/PartKeepr/Util/OS/OperatingSystem.php | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Util/Serializable.php | 12++++++++++++
Asrc/backend/PartKeepr/Util/SerializableException.php | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Util/Singleton.php | 23+++++++++++++++++++++++
Asrc/backend/PartKeepr/Util/UtilService.php | 14++++++++++++++
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Auth/AuthService.php | 52----------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Category/AbstractCategory.php | 202-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Category/AbstractCategoryManager.php | 223-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Category/AbstractCategoryService.php | 145-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Category/Exceptions/CategoryNotFoundException.php | 16----------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/CronLogger/CronLogger.php | 59-----------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/CronLogger/CronLoggerManager.php | 87-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Distributor/Distributor.php | 232-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Distributor/DistributorManager.php | 108-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Distributor/DistributorService.php | 83-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Footprint/Footprint.php | 192-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Footprint/FootprintAttachment.php | 102-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Footprint/FootprintImage.php | 51---------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Footprint/FootprintManager.php | 158-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Footprint/FootprintService.php | 96-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/FootprintAttachment/FootprintAttachmentManager.php | 69---------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/FootprintAttachment/FootprintAttachmentService.php | 103-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/FootprintCategory/FootprintCategory.php | 15---------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/FootprintCategory/FootprintCategoryManager.php | 43-------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/FootprintCategory/FootprintCategoryService.php | 10----------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/FulltextSearch/FulltextSearch.php | 95-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Image/CachedImage.php | 100-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Image/Exceptions/InvalidImageTypeException.php | 12------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Image/Image.php | 254-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Logger/Logger.php | 122-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Manager/AbstractManager.php | 231-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Manager/Exceptions/EntityInUseException.php | 32--------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Manager/ManagerFilter.php | 197-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Manager/Sorter.php | 80-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Manufacturer/Manufacturer.php | 251-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Manufacturer/ManufacturerICLogo.php | 64----------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Manufacturer/ManufacturerManager.php | 105-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Manufacturer/ManufacturerService.php | 83-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/ManufacturerICLogo/ManufacturerICLogoManager.php | 66------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/ManufacturerICLogo/ManufacturerICLogoService.php | 102-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/Exceptions/CategoryNotAssignedException.php | 21---------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/Exceptions/StorageLocationNotAssignedException.php | 20--------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/Part.php | 640-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/PartAttachment.php | 129-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/PartDistributor.php | 194-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/PartFulltextSearch.php | 30------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/PartImage.php | 51---------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/PartManager.php | 442-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/PartManufacturer.php | 107-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/PartService.php | 259-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Part/PartUnit.php | 138-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/PartCategory/PartCategory.php | 15---------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/PartCategory/PartCategoryManager.php | 43-------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/PartCategory/PartCategoryService.php | 10----------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/PartDistributor/PartDistributorManager.php | 75---------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/PartDistributor/PartDistributorService.php | 85-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/PartKeepr.php | 435-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/PartKeeprVersion.php | 16----------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/PartParameter/PartParameter.php | 247-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/PartUnit/PartUnitManager.php | 107-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/PartUnit/PartUnitService.php | 73-------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Ping/PingService.php | 20--------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Project/Project.php | 162-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Project/ProjectAttachment.php | 102-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Project/ProjectManager.php | 34----------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Project/ProjectPart.php | 137-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Project/ProjectService.php | 65-----------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/ProjectAttachment/ProjectAttachmentManager.php | 69---------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/ProjectAttachment/ProjectAttachmentService.php | 103-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/ProjectReport/ProjectReportService.php | 98-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/REST/ApplicationController.php | 42------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/REST/Model.php | 71-----------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/REST/Request.php | 115-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/REST/Response.php | 21---------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Service/AdminService.php | 14--------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Service/AnonService.php | 6------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Service/Exceptions/ServiceException.php | 6------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Service/RestfulService.php | 10----------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Service/Service.php | 110-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Service/ServiceManager.php | 127-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Session/Exceptions/SessionNotFoundException.php | 11-----------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Session/Session.php | 60------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Session/SessionManager.php | 69---------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/AbstractSetup.php | 45---------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/ConfigFileSetup.php | 66------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/FootprintSetup.php | 156-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/ManufacturerSetup.php | 56--------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/Migration/PartDB/DistributorMigration.php | 34----------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/Migration/PartDB/FootprintMigration.php | 43-------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/Migration/PartDB/PartCategoryMigration.php | 50--------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/Migration/PartDB/PartDBMigration.php | 71-----------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/Migration/PartDB/PartMigration.php | 131-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/Migration/PartDB/StorageLocationMigration.php | 51---------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/MiscSettingsSetup.php | 70----------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/PartCategorySetup.php | 28----------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/PartUnitSetup.php | 36------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/SchemaSetup.php | 35-----------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/Setup.php | 177-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/SiPrefixSetup.php | 47-----------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/UnitSetup.php | 62--------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Setup/UserSetup.php | 32--------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/SiPrefix/SiPrefix.php | 92-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/SiPrefix/SiPrefixManager.php | 43-------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/SiPrefix/SiPrefixService.php | 27---------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Statistic/StatisticService.php | 149-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Statistic/StatisticSnapshot.php | 110-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Statistic/StatisticSnapshotManager.php | 39---------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Statistic/StatisticSnapshotUnit.php | 96-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Stock/StockEntry.php | 239-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Stock/StockManager.php | 67-------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Stock/StockService.php | 94-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/StorageLocation/Exceptions/StorageLocationNotFoundException.php | 13-------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/StorageLocation/StorageLocation.php | 106-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/StorageLocation/StorageLocationImage.php | 51---------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/StorageLocation/StorageLocationManager.php | 95-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/StorageLocation/StorageLocationService.php | 109-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/System/SystemInformationRecord.php | 56--------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/System/SystemService.php | 100-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/SystemNotice/SystemNotice.php | 142-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/SystemNotice/SystemNoticeManager.php | 56--------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/SystemNotice/SystemNoticeService.php | 84-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/TempFile/TempFileService.php | 73-------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/TempImage/TempImage.php | 20--------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/TempImage/TempImageService.php | 31-------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/TipOfTheDay/TipOfTheDay.php | 115-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/TipOfTheDay/TipOfTheDayHistory.php | 52----------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/TipOfTheDay/TipOfTheDayService.php | 87-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Unit/Unit.php | 131-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Unit/UnitManager.php | 100-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Unit/UnitService.php | 68--------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/UploadedFile/TempUploadedFile.php | 15---------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/UploadedFile/UploadedFile.php | 301-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/User/Exceptions/InvalidLoginDataException.php | 15---------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/User/Exceptions/UserAlreadyExistsException.php | 20--------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/User/Exceptions/UserDoesNotExistException.php | 18------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/User/User.php | 260-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/User/UserManager.php | 136-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/User/UserService.php | 102-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/UserPreference/Exceptions/UserPreferenceNotFoundException.php | 21---------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/UserPreference/UserPreference.php | 230-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/UserPreference/UserPreferenceService.php | 97-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/BaseEntity.php | 117-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/Configuration.php | 93-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/Deserializable.php | 11-----------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/Exceptions/EntityNotFoundException.php | 17-----------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/Exceptions/EntityNotPersistantException.php | 14--------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/Exceptions/OutOfRangeException.php | 6------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/OS/OperatingSystem.php | 94-------------------------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/Serializable.php | 12------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/SerializableException.php | 60------------------------------------------------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/Singleton.php | 23-----------------------
Dsrc/backend/de/RaumZeitLabor/PartKeepr/Util/UtilService.php | 14--------------
Msrc/frontend/file.php | 24++++++++++++------------
Msrc/frontend/image.php | 52++++++++++++++++++++++------------------------------
Msrc/frontend/index.php | 18+++++++++---------
Msrc/frontend/js/Components/Part/Editor/PartDistributorGrid.js | 13++++++++++---
Msrc/frontend/js/Components/Part/PartDisplay.js | 22+++++++++++++++++-----
Asrc/frontend/js/Components/Part/PartImageDisplay.js | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/frontend/js/Components/Part/PartStockWindow.js | 31++++++++++++++++++++++++-------
Msrc/frontend/js/Models/PartAttachment.js | 1+
Msrc/frontend/js/Models/PartDistributor.js | 20++++++++++----------
Msrc/frontend/js/Util/i18n.js | 5+++++
Msrc/frontend/rest.php | 12++++++------
Msrc/frontend/rss.php | 8++++----
Msrc/frontend/service.php | 10+++++-----
Msrc/frontend/upload.php | 12++++++------
Msrc/setup/setup.php | 24++++++++++++------------
Msrc/setup/tests/check-database-connectivity.php | 36++++++++++++++++++------------------
Mtesting/PartTest.php | 32+++++++++++++++++++++++++++++---
Mtesting/Service.php | 2+-
Mtesting/SetupDatabase.php | 30+++++++++++++++---------------
Mtesting/genman.php | 2+-
Mtests/Auth/UserTest.php | 4++--
Mtests/Logger/LoggerTest.php | 6+++---
Mtests/Part/PartServiceTest.php | 42+++++++++++++++++++++---------------------
Mtests/Service/ServiceTest.php | 8++++----
Mtests/User/UserTest.php | 14+++++++-------
Mtests/UserPreference/UserPreferenceTest.php | 24++++++++++++------------
Mtests/Util/ConfigurationTest.php | 4++--
Mtests/bootstrap.php | 10+++++-----
Mutil/classes/ExtJSFile.php | 2++
339 files changed, 14361 insertions(+), 13993 deletions(-)

diff --git a/build.properties b/build.properties @@ -1,3 +1,4 @@ extjs.path=3rdparty/extjs packagepath=/tmp/partkeepr-pkg -source.php=src/backend/de/RaumZeitLabor/PartKeepr- \ No newline at end of file +source.php=src/backend/PartKeepr +build.fast=1+ \ No newline at end of file diff --git a/build.xml b/build.xml @@ -14,6 +14,8 @@ <target name="clean"> <delete dir="reports" /> <delete dir="frontend" /> + <delete dir="setup" /> + <delete file="partkeepr.jsb3" /> </target> <!-- Re-generates the proxies needed by Doctrine2 --> @@ -90,12 +92,12 @@ <else> <copy overwrite="true" todir="setup/js/wizard"> <fileset dir="3rdparty/ext-wizard/Ext.ux.Wizard"> - <include name="**"/> + <include name="**" /> </fileset> </copy> </else> </if> - + <echo>Copying src/setup</echo> <if> <isset property="build.fast" /> @@ -150,7 +152,7 @@ </if> <echo>Copying Ext Statusbar</echo> - <mkdir dir="frontend/js/Ext.ux/statusbar"/> + <mkdir dir="frontend/js/Ext.ux/statusbar" /> <if> <isset property="build.fast" /> <then> @@ -224,7 +226,7 @@ <phingcall target="build-setup" /> </target> - <!-- Fast build uses rsync instead of standard file copy --> + <!-- Fast build uses rsync instead of standard file copy --> <target name="build-fast"> <condition property="build.fast"> <or> @@ -236,7 +238,7 @@ <fail unless="build.fast" message="Fast build only supported on Mac and Unix-like systems (rsync required)" /> <phingcall target="build" /> - </target> + </target> <!-- Creates a new PartKeepr release. @@ -327,7 +329,7 @@ <exec executable="sed"> <arg value="-i" /> <arg value="s/{V_GIT}/${partkeepr.version}/g" /> - <arg value="${packagepath}/src/backend/de/RaumZeitLabor/PartKeepr/PartKeeprVersion.php" /> + <arg value="${packagepath}/src/backend/PartKeepr/PartKeeprVersion.php" /> </exec> </target> @@ -338,7 +340,7 @@ <format property="build.time" pattern="partkeepr-nightly-%Y%m%d" /> </tstamp> - <property name="partkeepr.version" value="{build.time}-nightly" /> + <property name="partkeepr.version" value="${build.time}" /> <phingcall target="set-version" /> <zip destfile="${build.time}.zip" basedir="${packagepath}" prefix="partkeepr-nightly/" /> diff --git a/cli-config.php b/cli-config.php @@ -1,5 +1,5 @@ <?php -use de\RaumZeitLabor\PartKeepr\PartKeepr; +use PartKeepr\PartKeepr; $helpers = array( 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper(PartKeepr::getEM()->getConnection()), diff --git a/config-test.php b/config-test.php @@ -1,7 +1,7 @@ <?php -namespace de\RaumZeitLabor\PartKeepr; +namespace PartKeepr; -use de\RaumZeitLabor\PartKeepr\Util\Configuration; +use PartKeepr\Util\Configuration; Configuration::setOption("partkeepr.database.dbname", "partkeepr-test"); Configuration::setOption("partkeepr.database.driver", "pdo_sqlite"); diff --git a/config.php.template b/config.php.template @@ -1,7 +1,7 @@ <?php -namespace de\RaumZeitLabor\PartKeepr; +namespace PartKeepr; -use de\RaumZeitLabor\PartKeepr\Util\Configuration; +use PartKeepr\Util\Configuration; /** * Specifies the username for the database diff --git a/cronjobs/CheckForUpdates.php b/cronjobs/CheckForUpdates.php @@ -6,12 +6,12 @@ * @author felicitus * */ -namespace de\RaumZeitLabor\PartKeepr\Cronjobs; +namespace PartKeepr\Cronjobs; -include(__DIR__."/../src/backend/de/RaumZeitLabor/PartKeepr/PartKeepr.php"); +include(__DIR__."/../src/backend/PartKeepr/PartKeepr.php"); -use de\RaumZeitLabor\PartKeepr\PartKeepr, - de\RaumZeitLabor\PartKeepr\CronLogger\CronLoggerManager; +use PartKeepr\PartKeepr, + PartKeepr\CronLogger\CronLoggerManager; PartKeepr::initialize(); diff --git a/cronjobs/CreateRSSFeed.php b/cronjobs/CreateRSSFeed.php @@ -1,14 +1,14 @@ <?php -namespace de\RaumZeitLabor\PartKeepr\Cronjobs; +namespace PartKeepr\Cronjobs; -include(__DIR__."/../src/backend/de/RaumZeitLabor/PartKeepr/PartKeepr.php"); +include(__DIR__."/../src/backend/PartKeepr/PartKeepr.php"); -use de\RaumZeitLabor\PartKeepr\PartKeepr; -use de\RaumZeitLabor\PartKeepr\Util\Configuration; +use PartKeepr\PartKeepr, + PartKeepr\Util\Configuration; PartKeepr::initialize(); -$dql = PartKeepr::getEM()->createQuery("SELECT p FROM de\RaumZeitLabor\PartKeepr\Part\Part p ORDER BY p.createDate DESC"); +$dql = PartKeepr::getEM()->createQuery("SELECT p FROM PartKeepr\Part\Part p ORDER BY p.createDate DESC"); $dql->setMaxResults(40); $parts = $dql->getResult(); diff --git a/cronjobs/CreateStatisticSnapshot.php b/cronjobs/CreateStatisticSnapshot.php @@ -1,11 +1,11 @@ <?php -namespace de\RaumZeitLabor\PartKeepr\Cronjobs; +namespace PartKeepr\Cronjobs; -include(__DIR__."/../src/backend/de/RaumZeitLabor/PartKeepr/PartKeepr.php"); +include(__DIR__."/../src/backend/PartKeepr/PartKeepr.php"); -use de\RaumZeitLabor\PartKeepr\PartKeepr, - de\RaumZeitLabor\PartKeepr\Statistic\StatisticSnapshotManager, - de\RaumZeitLabor\PartKeepr\CronLogger\CronLoggerManager; +use PartKeepr\PartKeepr, + PartKeepr\Statistic\StatisticSnapshotManager, + PartKeepr\CronLogger\CronLoggerManager; PartKeepr::initialize(); StatisticSnapshotManager::getInstance()->createSnapshot(); diff --git a/cronjobs/UpdatePartCacheData.php b/cronjobs/UpdatePartCacheData.php @@ -1,15 +1,15 @@ <?php -namespace de\RaumZeitLabor\PartKeepr\Cronjobs; +namespace PartKeepr\Cronjobs; -include(__DIR__."/../src/backend/de/RaumZeitLabor/PartKeepr/PartKeepr.php"); +include(__DIR__."/../src/backend/PartKeepr/PartKeepr.php"); -use de\RaumZeitLabor\PartKeepr\PartKeepr, - de\RaumZeitLabor\PartKeepr\Statistic\StatisticSnapshotManager, - de\RaumZeitLabor\PartKeepr\CronLogger\CronLoggerManager; +use PartKeepr\PartKeepr, + PartKeepr\Statistic\StatisticSnapshotManager, + PartKeepr\CronLogger\CronLoggerManager; PartKeepr::initialize(); -$query = PartKeepr::getEM()->createQuery("SELECT p FROM de\RaumZeitLabor\PartKeepr\Part\Part p"); +$query = PartKeepr::getEM()->createQuery("SELECT p FROM PartKeepr\Part\Part p"); $result = $query->getResult(); $fc = 0; diff --git a/cronjobs/UpdateTipsOfTheDay.php b/cronjobs/UpdateTipsOfTheDay.php @@ -8,13 +8,13 @@ * @author felicitus * */ -namespace de\RaumZeitLabor\PartKeepr\Cronjobs; +namespace PartKeepr\Cronjobs; -include(__DIR__."/../src/backend/de/RaumZeitLabor/PartKeepr/PartKeepr.php"); +include(__DIR__."/../src/backend/PartKeepr/PartKeepr.php"); -use de\RaumZeitLabor\PartKeepr\PartKeepr, - de\RaumZeitLabor\PartKeepr\TipOfTheDay\TipOfTheDay, - de\RaumZeitLabor\PartKeepr\CronLogger\CronLoggerManager; +use PartKeepr\PartKeepr, + PartKeepr\TipOfTheDay\TipOfTheDay, + PartKeepr\CronLogger\CronLoggerManager; PartKeepr::initialize(); diff --git a/doctrine.php b/doctrine.php @@ -1,9 +1,9 @@ <?php -namespace de\RaumZeitLabor\PartKeepr\Foo; +namespace PartKeepr\Foo; -use de\RaumZeitLabor\PartKeepr\PartKeepr; +use PartKeepr\PartKeepr; -include("src/backend/de/RaumZeitLabor/PartKeepr/PartKeepr.php"); +include("src/backend/PartKeepr/PartKeepr.php"); PartKeepr::initialize(""); // Variable $helperSet is defined inside cli-config.php diff --git a/documentation/internals/NEW-RELEASE b/documentation/internals/NEW-RELEASE @@ -30,3 +30,5 @@ The following steps describe how a new PartKeepr release is being built: * Write a blog entry * Push out notifications on twitter + +* Change irc topic to reflect the current version+ \ No newline at end of file diff --git a/migrations.yml b/migrations.yml @@ -2,4 +2,4 @@ name: PartKeepr migrations_namespace: DoctrineMigrations table_name: SchemaVersions -migrations_directory: src/backend/de/RaumZeitLabor/PartKeepr/Versions- \ No newline at end of file +migrations_directory: src/backend/PartKeepr/Versions+ \ No newline at end of file diff --git a/scripts/UpdateCategoryPathCache.php b/scripts/UpdateCategoryPathCache.php @@ -4,14 +4,14 @@ * * This script needs to be called after changing the partkeepr.category.path_separator. */ -namespace de\RaumZeitLabor\PartKeepr\Scripts; +namespace PartKeepr\Scripts; -use de\RaumZeitLabor\PartKeepr\PartKeepr, - de\RaumZeitLabor\PartKeepr\PartCategory\PartCategoryManager, - de\RaumZeitLabor\PartKeepr\Util\Configuration, - de\RaumZeitLabor\PartKeepr\FootprintCategory\FootprintCategoryManager; +use PartKeepr\PartKeepr, + PartKeepr\PartCategory\PartCategoryManager, + PartKeepr\Util\Configuration, + PartKeepr\FootprintCategory\FootprintCategoryManager; -include("../src/backend/de/RaumZeitLabor/PartKeepr/PartKeepr.php"); +include("../src/backend/PartKeepr/PartKeepr.php"); PartKeepr::initialize(); diff --git a/src/backend/PartKeepr/Auth/AuthService.php b/src/backend/PartKeepr/Auth/AuthService.php @@ -0,0 +1,52 @@ +<?php +namespace PartKeepr\Auth; + +use PartKeepr\Service\AnonService, + PartKeepr\User\User, + PartKeepr\User\UserManager, + PartKeepr\User\Exceptions\InvalidLoginDataException, + PartKeepr\Session\SessionManager; + +class AuthService extends AnonService { + /** + * Logs in the given user. If the login was successful, + * a session is automatically started. + * + * @throws InvalidLoginDataException + */ + public function login () { + $this->requireParameter("username"); + $this->requireParameter("password"); + + /* Build a temporary user */ + $user = new User; + $user->setRawUsername($this->getParameter("username")); + $user->setHashedPassword($this->getParameter("password")); + + $authenticatedUser = UserManager::getInstance()->authenticate($user); + + if ($authenticatedUser !== false) { + /* Start Session */ + $session = SessionManager::getInstance()->startSession($authenticatedUser); + + $aPreferences = array(); + + foreach ($session->getUser()->getPreferences() as $result) { + $aPreferences[] = $result->serialize(); + } + + return array( + "sessionid" => $session->getSessionID(), + "username" => $this->getParameter("username"), + "admin" => $session->getUser()->isAdmin(), + "userPreferences" => array( + "response" => array( + "data" => $aPreferences + ))); + } else { + throw new InvalidLoginDataException(); + } + + + } +} diff --git a/src/backend/PartKeepr/Category/AbstractCategory.php b/src/backend/PartKeepr/Category/AbstractCategory.php @@ -0,0 +1,202 @@ +<?php +namespace PartKeepr\Category; + +use PartKeepr\Util\BaseEntity; +use PartKeepr\Util\Serializable; +use DoctrineExtensions\NestedSet\Node; + +/** + * @MappedSuperclass + * @Table(indexes={@index(columns={"lft"}),@index(columns={"rgt"})}) + * + * Represents an abstract category. This class isn't directly usable; you need to inherit it to take advantage of + * the AbstractCategoryManager. + * + * If you are interested on how NestedSets work, please read http://en.wikipedia.org/wiki/Nested_set_model + */ +class AbstractCategory extends BaseEntity implements Node, Serializable { + /** + * The "left" property of the nested set + * @Column(type="integer") + * @var integer + */ + private $lft; + + /** + * The "right" property of the nested set + * @Column(type="integer") + * @var integer + */ + private $rgt; + + /** + * The name of the category + * @Column(length=128) + * @var string + */ + private $name; + + /** + * The description of the category + * @Column(type="text",nullable=true) + * @var string + */ + private $description; + + /** + * Holds the parent node ID. Note that this + * is not a persistant value, but rather calculated + * or set on the fly. + * @var int + */ + private $parent; + + /** + * Holds the category path. + * + * @Column(type="text",nullable=true) + * + * @var string + */ + private $categoryPath; + + /** + * Sets the name of this category + * @param string $name The name to set + */ + public function setName ($name) { + $this->name = $name; + } + + /** + * Returns the name of this category + * @return string The category name + */ + public function getName () { + return $this->name; + } + + /** + * Sets the parent for this category. + * + * @param int $id The parent node + */ + public function setParent ($id) { + $this->parent = $id; + } + + /** + * Returns the parent node for this node. + * @return int The parent node + */ + public function getParent () { + return $this->parent; + } + + /** + * Sets the description for this category + * @param string $description The description of this category + */ + public function setDescription ($description) { + $this->description = $description; + } + + /** + * Returns the description of this category + * @return string The description + */ + public function getDescription () { + return $this->description; + } + + /** + * Returns the "left" value of the nested set. + * + * @return int The left value + * + * (non-PHPdoc) + * @see DoctrineExtensions\NestedSet.Node::getLeftValue() + */ + public function getLeftValue() { + return $this->lft; + } + + /** + * Sets the "left" value. + * + * @param $lft integer The left value + * (non-PHPdoc) + * @see DoctrineExtensions\NestedSet.Node::setLeftValue() + */ + public function setLeftValue($lft) { + $this->lft = $lft; + } + + /** + * Returns the "right" value of the nested set. + * + * @return int The right value + * + * (non-PHPdoc) + * @see DoctrineExtensions\NestedSet.Node::getRightValue() + */ + public function getRightValue() { + return $this->rgt; + } + + /** + * Sets the "right" value of the nested set + * + * @param $rgt int The right value + * + * (non-PHPdoc) + * @see DoctrineExtensions\NestedSet.Node::setRightValue() + */ + public function setRightValue($rgt) { + $this->rgt = $rgt; + } + + /** + * Serializes the entity. + */ + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "description" => $this->getDescription() + ); + } + + /** + * Returns a string representation of the current node. + * + * @return string The node name + * + * (non-PHPdoc) + * @see DoctrineExtensions\NestedSet.Node::__toString() + */ + public function __toString () { + return $this->getName(); + } + + /** + * Retrieves the category path + * + * @return string The category path + */ + public function getCategoryPath () { + return $this->categoryPath; + } + + /** + * Sets the category path. + * + * THIS IS ONLY TO BE CALLED FROM THE CATEGORYMANAGER! DON'T MESS WITH IT! + * + * @param string $categoryPath + */ + public function setCategoryPath ($categoryPath) { + $this->categoryPath = $categoryPath; + } + +} diff --git a/src/backend/PartKeepr/Category/AbstractCategoryManager.php b/src/backend/PartKeepr/Category/AbstractCategoryManager.php @@ -0,0 +1,222 @@ +<?php +namespace PartKeepr\Category; + +use PartKeepr\Util\Singleton, + PartKeepr\Category\Category, + PartKeepr\Util\SerializableException, + PartKeepr\Category\Exceptions\CategoryNotFoundException, + PartKeepr\PartKeepr, + PartKeepr\Util\Configuration, + DoctrineExtensions\NestedSet\Manager, + DoctrineExtensions\NestedSet\Config, + DoctrineExtensions\NestedSet\NodeWrapper; + +abstract class AbstractCategoryManager extends Singleton { + /** + * Holds the node manager + * @var object The node manager + */ + private $nodeManager; + + /** + * Holds the FQCN of the target entity + * @var PartKeepr\Category\AbstractCategory + */ + protected $categoryClass = "PartKeepr\Category\AbstractCategory"; + + /** + * Returns the node manager. Creates it if it doesn't exist. + * @todo Can this method be made private? + * @return Manager The node manager + */ + public function getNodeManager () { + if (!$this->nodeManager) { + $config = new Config(PartKeepr::getEM(), $this->categoryClass); + + $this->nodeManager = new Manager($config); + } + + return $this->nodeManager; + } + + /** + * Returns the child node id's for a given node id. + * @param integer $id The ID for which to retrieve the child nodes + * @return array An array of the children id's + * @todo Refactor this method name to indicate that it operates on IDs only. + */ + public function getChildNodes ($id) { + $category = $this->getCategory($id); + + $aData = array(); + + foreach ($category->getDescendants() as $cat) { + $aData[] = $cat->getNode()->getId(); + } + return $aData; + } + + /** + * Returns all categories. + * @return The category tree + */ + public function getAllCategories () { + return $this->getNodeManager()->fetchTree(1); + } + + /** + * Ensures that the root node exists. If not, this method creates it. + */ + public function ensureRootExists () { + /* Check if the root node exists */ + $rootNode = $this->getNodeManager()->fetchTree(1); + + if ($rootNode === null) { + $this->createRootNode(); + } + } + + /** + * Returns the root node for the category tree. + * @return The category root node + */ + public function getRootNode () { + return $this->getNodeManager()->fetchTree(1); + } + + /** + * Create the root node for the category tree. + */ + public function createRootNode () { + $rootNode = new $this->categoryClass(); + $rootNode->setName("Root Category"); + $rootNode->setDescription(""); + + $this->getNodeManager()->createRoot($rootNode); + } + + /** + * Adds a given category. + * @param Category $category The category to add to the tree + * @return Category the added category + */ + public function addCategory (AbstractCategory $category) { + $parent = $category->getParent(); + + if ($parent == 0) { + $parent = $this->getRootNode(); + } else { + $parent = PartKeepr::getEM()->find($this->categoryClass, $parent); + $parent = new NodeWrapper($parent, $this->getNodeManager()); + } + + $node = $parent->addChild($category); + + // Force category path update + $this->updateCategoryPaths($node); + + // Flush the category path changes + PartKeepr::getEM()->flush(); + + return $node; + } + + /** + * Deletes the given category ID. + * @param $id int The category id to delete + * @throws CategoryNotFoundException If the category wasn't found + */ + public function deleteCategory ($id) { + $this->getCategory($id)->delete(); + } + + /** + * Returns the category with the given ID. + * @param int $id The category id + * @throws CategoryNotFoundException If the category wasn't found + */ + public function getCategory ($id) { + $category = $this->loadCategoryById($id); + + return new NodeWrapper($category, $this->getNodeManager()); + } + + /** + * Returns the overall category count currently existing. + * @return int The amount of categories in the database + */ + public function getCategoryCount () { + $dql = "SELECT COUNT(c.id) FROM ".$this->categoryClass." c"; + + return PartKeepr::getEM()->createQuery($dql)->getSingleScalarResult(); + } + + /** + * Updates the category paths for a given node and all children. + * + * This method is usually called whenever a category is moved. + * + * @param NodeWrapper $startNode The node to start updating at + */ + public function updateCategoryPaths (NodeWrapper $startNode) { + $pathSeparator = Configuration::getOption("partkeepr.category.path_separator", " ➤ "); + + $startNode->getNode()->setCategoryPath($startNode->getPath($pathSeparator, true)); + + foreach ($startNode->getChildren() as $child) { + $this->updateCategoryPaths($child); + } + } + + /** + * Creates a category tree by an array of category names + * @param array $aCategoryNames + * @param Category A category where to start searching at + */ + public function createCategoryTreeByArray ($aCategoryNames, NodeWrapper $rootNode = null) { + if (count($aCategoryNames) == 0) { + return false; + } + + $categoryName = array_shift($aCategoryNames); + + if ($rootNode === null) { + $rootNode = $this->getRootNode(); + } + + $bFound = false; + foreach ($rootNode->getChildren() as $child) { + if ($child->getNode()->getName() == $categoryName) { + $bFound = true; + $category = $child->getNode(); + break; + } + } + + if ($bFound === false) { + $category = new $this->categoryClass(); + $category->setName($categoryName); + $category->setDescription(""); + $category->setParent($rootNode->getId()); + $this->addCategory($category); + } + + $result = $this->createCategoryTreeByArray($aCategoryNames,new NodeWrapper($category, $this->getNodeManager())); + + if ($result === false) { + return $category; + } else { + return $result; + } + } + + /** + * Loads a category by id + * @param string $id + */ + protected function loadCategoryById($id) { + $class = $this->categoryClass; + + return $class::loadById($id); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Category/AbstractCategoryService.php b/src/backend/PartKeepr/Category/AbstractCategoryService.php @@ -0,0 +1,145 @@ +<?php +namespace PartKeepr\Category; + +use PartKeepr\PartKeepr, + PartKeepr\Service\Service, + PartKeepr\Category\CategoryManager, + DoctrineExtensions\NestedSet\NodeWrapper; + + +abstract class AbstractCategoryService extends Service { + protected $categoryManagerClass = "PartKeepr\Category\AbstractCategoryManager"; + protected $categoryClass = "PartKeepr\Category\AbstractCategory"; + + /** + * Returns all categories found in the system. + * @return array A serialized tree + */ + public function getCategories () { + $categories = $this->getCategoryManager()->getAllCategories( + $this->getParameter("parent", 0)); + + return $this->serializeTree($categories); + } + + /** + * Moves the given category from one to another category. + */ + public function moveCategory () { + $this->requireParameter("category"); + $this->requireParameter("target"); + + $category = $this->getCategoryManager()->getCategory($this->getParameter("category")); + + if (intval($this->getParameter("target", 0)) !== 0) { + + $parent = $this->getCategoryManager()->getCategory($this->getParameter("target")); + + $category->moveAsLastChildOf($parent); + $this->getCategoryManager()->updateCategoryPaths($category); + + } + } + + /** + * Deletes the given category. + */ + public function deleteCategory () { + $this->requireParameter("id"); + + $this->getCategoryManager()->deleteCategory($this->getParameter("id")); + + return array("id" => $this->getParameter("id")); + } + + /** + * Updates the given category. + */ + public function update () { + $this->requireParameter("id"); + $this->requireParameter("name"); + + $category = $this->getCategoryManager()->getCategory($this->getParameter("id")); + + $category->getNode()->setName($this->getParameter("name")); + $category->getNode()->setDescription($this->getParameter("description", "")); + + PartKeepr::getEM()->persist($category->getNode()); + + $this->getCategoryManager()->updateCategoryPaths($category); + + return array("data" => $this->serializeCategory($category)); + } + + public function create () { + $this->requireParameter("name"); + $this->requireParameter("parent"); + + $category = new $this->categoryClass; + $category->setName($this->getParameter("name")); + $category->setDescription($this->getParameter("description", "")); + $category->setParent($this->getParameter("parent")); + + $category = $this->getCategoryManager()->addCategory($category); + + $this->getCategoryManager()->updateCategoryPaths($category); + + return array("data" => $this->serializeCategory($category)); + } + + public function destroy () { + return $this->deleteCategory(); + } + + private function serializeCategory ($category) { + $data = $category->getNode()->serialize(); + + if ($category->getParent() !== null) { + $data["parent"] = $category->getParent()->getNode()->getId(); + $data["parentName"] = $category->getParent()->getNode()->getName(); + } + + return $data; + } + + /** + * Returns all categories + */ + public function getAllCategories () { + return $this->serializeTree($this->getCategoryManager()->getAllCategories()); + } + + /** + * Serializes the given NodeWrapper as array including all children. + * @param NodeWrapper $node The category nodewrapper to serialize + * @throws \Exception + */ + public function serializeTree (NodeWrapper $node = null) { + if ($node == null) { + throw new \Exception("Node must not be null!"); + } + + $aData = $node->getNode()->serialize(); + + $aData["children"] = array(); + + if (count($node->getChildren()) == 0) { + $aData["leaf"] = true; + } else { + $aData["expanded"] = true; + } + + foreach ($node->getChildren() as $child) { + $aData["children"][] = $this->serializeTree($child); + + } + + return $aData; + } + + public function getCategoryManager () { + $categoryManager = $this->categoryManagerClass; + + return $categoryManager::getInstance(); + } +} diff --git a/src/backend/PartKeepr/Category/Exceptions/CategoryNotFoundException.php b/src/backend/PartKeepr/Category/Exceptions/CategoryNotFoundException.php @@ -0,0 +1,15 @@ +<?php +namespace PartKeepr\Category\Exceptions; + +use PartKeepr\Util\SerializableException, + PartKeepr\PartKeepr; + +/** + * Thrown when the requested category was not found. + */ +class CategoryNotFoundException extends SerializableException { + public function __construct ($id) { + parent::__construct(sprintf(PartKeepr::i18n("Category %d not found."), $id)); + } +} +?>+ \ No newline at end of file diff --git a/src/backend/PartKeepr/CronLogger/CronLogger.php b/src/backend/PartKeepr/CronLogger/CronLogger.php @@ -0,0 +1,58 @@ +<?php +namespace PartKeepr\CronLogger; + +use PartKeepr\UploadedFile\UploadedFile, + PartKeepr\Util\BaseEntity, + PartKeepr\Util\Serializable, + PartKeepr\Util\Deserializable; + +/** + * Holds a project attachment + * @Entity + **/ +class CronLogger extends BaseEntity { + /** + * @Column(type="datetime") + * @var \DateTime + */ + private $lastRunDate; + + /** + * @Column(type="string") + * @var string + */ + private $cronjob; + + /** + * Sets the last run date and time for this entry + * @param \DateTime $date The date and time + */ + public function setLastRunDate (\DateTime $date) { + $this->lastRunDate = $date; + } + + /** + * Returns the date and time for this entry + * + * @return \DateTime the date and time for this entry + */ + public function getLastRunDate () { + return $this->lastRunDate; + } + + /** + * Sets the cronjob for this entry + * @param string $cronjob the title for this entry + */ + public function setCronjob ($cronjob) { + $this->cronjob = $cronjob; + } + + /** + * Returns the cronjob for this entry + * @return string the title + */ + public function getCronjob () { + return $this->cronjob; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/CronLogger/CronLoggerManager.php b/src/backend/PartKeepr/CronLogger/CronLoggerManager.php @@ -0,0 +1,86 @@ +<?php +namespace PartKeepr\CronLogger; + +use PartKeepr\Manager\AbstractManager, + PartKeepr\PartKeepr; + +class CronLoggerManager extends AbstractManager { + /** + * Returns the FQCN for the target entity to operate on. + * @return string The FQCN, e.g. PartKeepr\Part + */ + public function getEntityName () { + return 'PartKeepr\CronLogger\CronLogger'; + } + + /** + * Returns all fields which need to appear in the getList ResultSet. + * @return array An array of all fields which should be returned + */ + public function getQueryFields () { + return array("id", "title", "date"); + } + + /** + * Returns the default sort field + * + * @return string The default sort field + */ + public function getDefaultSortField () { + return "date"; + } + + /** + * Marks a specific cronjob as ran + * @param string $cronjob The name of the cronjob + */ + public function markCronRun ($cronjob) { + $dql = "SELECT c FROM PartKeepr\CronLogger\CronLogger c WHERE c.cronjob = :cronjob"; + $query = PartKeepr::getEM()->createQuery($dql); + $query->setParameter("cronjob", $cronjob); + + try { + $result = $query->getSingleResult(); + } catch (\Exception $e) { + $result = new CronLogger(); + $result->setCronjob($cronjob); + PartKeepr::getEM()->persist($result); + } + + $result->setLastRunDate(new \DateTime()); + + PartKeepr::getEM()->flush(); + } + + /** + * Returns a list of all inactive cronjobs + * + * @param none + * @return array A string of cronjob names which aren't running + */ + public function getInactiveCronjobs () { + $dql = "SELECT c.cronjob FROM PartKeepr\CronLogger\CronLogger c WHERE c.cronjob = :cronjob"; + $dql .= " AND c.lastRunDate > :date"; + + $query = PartKeepr::getEM()->createQuery($dql); + + $date = new \DateTime(); + $date->sub(new \DateInterval('P1D')); + $query->setParameter("date", $date); + + $failedCronjobs = array(); + + foreach (PartKeepr::getRequiredCronjobs() as $cronjob) { + $query->setParameter("cronjob", $cronjob); + + try { + $query->getSingleResult(); + } catch (\Exception $e) { + $failedCronjobs[] = $cronjob; + } + + } + + return $failedCronjobs; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Distributor/Distributor.php b/src/backend/PartKeepr/Distributor/Distributor.php @@ -0,0 +1,231 @@ +<?php +namespace PartKeepr\Distributor; + +use PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable, + PartKeepr\Util\BaseEntity, + PartKeepr\PartKeepr; + +/** + * Represents a distributor + * @Entity **/ +class Distributor extends BaseEntity implements Serializable, Deserializable { + /** + * Holds the name of the distributor + * @Column(type="string",unique=true) + * @var string + */ + private $name; + + /** + * Holds the address of the distributor + * @Column(type="text",nullable=true) + * @var string + */ + private $address; + + /** + * Holds the URL of the distributor + * @Column(type="string",nullable=true) + * @var string + */ + private $url; + + /** + * Holds the phone number of the distributor + * @Column(type="string",nullable=true) + * @var string + */ + private $phone; + + /** + * Holds the fax number of the distributor + * @Column(type="string",nullable=true) + * @var string + */ + private $fax; + + /** + * Holds the email of the distributor + * @Column(type="string",nullable=true) + * @var string + */ + private $email; + + /** + * Holds a comment for the distributor + * @Column(type="text",nullable=true) + * @var string + */ + private $comment; + + /** + * Sets the name for the distributor + * + * @param string $name The distributor's name + */ + public function setName ($name) { + $this->name = $name; + } + + /** + * Returns the name of the distributor + * @return string The distributor's name + */ + public function getName () { + return $this->name; + } + + /** + * Sets the address of this distributor + * + * @param string $address The address of the distributor + */ + public function setAddress ($address) { + $this->address = $address; + } + + /** + * Returns the address of this distributor + * @return string The address of this distributor + */ + public function getAddress () { + return $this->address; + } + + /** + * Sets the phone number for this distributor + * + * @param string $phone The phone number of this distributor + */ + public function setPhone ($phone) { + $this->phone = $phone; + } + + /** + * Returns the phone number of this distributor + * @return string The phone number + */ + public function getPhone () { + return $this->phone; + } + + /** + * Sets the fax number for this distributor + * + * @param string $fax The fax number + */ + public function setFax ($fax) { + $this->fax = $fax; + } + + /** + * Returns the fax number for this distributor + * + * @return string $fax The fax number + */ + public function getFax () { + return $this->fax; + } + + /** + * Sets the comment for this distributor + * + * @param string $comment The comment for this distributor + */ + public function setComment ($comment) { + $this->comment = $comment; + } + + /** + * Returns the comment for this distributor + * + * @return string The comment + */ + public function getComment () { + return $this->comment; + } + + /** + * Sets the email for this distributor + * + * @param string $email The email for this distributor + */ + public function setEmail ($email) { + $this->email = $email; + } + + /** + * Returns the email for this distributor + * @return string The email + */ + public function getEmail () { + return $this->email; + } + + /** + * Sets the URL for this distributor + * + * @param string $url The URL for this distributor + */ + public function setURL ($url) { + $this->url = $url; + } + + /** + * Returns the URL for this distributor + * @return string The URL + */ + public function getURL () { + return $this->url; + } + + /** + * Returns the distributor in serialized form. + * @return array The serialized distributor + */ + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "url" => $this->getURL(), + "address" => $this->getAddress(), + "email" => $this->getEmail(), + "comment" => $this->getComment(), + "phone" => $this->getPhone(), + "fax" => $this->getFax() + ); + } + + /** + * Deserializes the distributor + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "name": + $this->setName($value); + break; + case "url": + $this->setURL($value); + break; + case "comment": + $this->setComment($value); + break; + case "fax": + $this->setFax($value); + break; + case "phone": + $this->setPhone($value); + break; + case "email": + $this->setEmail($value); + break; + case "address": + $this->setAddress($value); + break; + } + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Distributor/DistributorManager.php b/src/backend/PartKeepr/Distributor/DistributorManager.php @@ -0,0 +1,107 @@ +<?php +namespace PartKeepr\Distributor; + +use PartKeepr\Util\Singleton, + PartKeepr\Distributor\Distributor, + PartKeepr\PartKeepr, + PartKeepr\Category\CategoryManager, + PartKeepr\Distributor\Exceptions\DistributorNotFoundException; + +class DistributorManager extends Singleton { + /** + * Returns a list of distributors. + * + * @param int $start Start of the list, default 0 + * @param int $limit Number of users to list, default 10 + * @param string $sort The field to sort by, default "name" + * @param string $dir The direction to sort (ASC or DESC), default ASC + * @param string $filter A string to filter the distributor's name by, default empty + */ + public function getDistributors ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { + + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select("st.id, st.name, st.url, st.email, st.comment, st.address")->from("PartKeepr\Distributor\Distributor","st"); + + if ($filter != "") { + $qb = $qb->where("LOWER(st.name) LIKE :filter"); + $qb->setParameter("filter", "%".strtolower($filter)."%"); + } + + if ($limit > -1) { + $qb->setMaxResults($limit); + $qb->setFirstResult($start); + } + + $qb->orderBy("st.".$sort, $dir); + + $query = $qb->getQuery(); + + $result = $query->getResult(); + + $totalQueryBuilder = PartKeepr::getEM()->createQueryBuilder(); + $totalQueryBuilder->select("COUNT(st.id)")->from("PartKeepr\Distributor\Distributor","st"); + + + + if ($filter != "") { + $totalQueryBuilder = $totalQueryBuilder->where("LOWER(st.name) LIKE :filter"); + $totalQueryBuilder->setParameter("filter", "%".strtolower($filter)."%"); + } + + $totalQuery = $totalQueryBuilder->getQuery(); + + return array("data" => $result, "totalCount" => $totalQuery->getSingleScalarResult()); + } + + /** + * Returns a distributor by ID. + * + * @param int $id The distributor id + * @return Distributor The distributor + * @throws EntityNotFoundException If no distributor with the ID was found + */ + public function getDistributor ($id) { + return Distributor::loadById($id); + } + + /** + * Creates a new distributor with the given name. + * @param string $name The name of the distributor + * @return Distributor The new distributor object + */ + public function addDistributor ($name) { + $distributor = new Distributor(); + $distributor->setName($name); + + PartKeepr::getEM()->persist($distributor); + PartKeepr::getEM()->flush(); + + return $distributor; + } + + /** + * Deletes a distributor by id + * + * @param int $id The ID of the distributor to delete + */ + public function deleteDistributor ($id) { + $distributor = $this->getDistributor($id); + + PartKeepr::getEM()->remove($distributor); + PartKeepr::getEM()->flush(); + } + + /** + * Retrieves a distributor by its name. + * + * @param string $name The name of the distributor to retrieve + * @throws Doctrine\ORM\NoResultException If the distributor was not found + */ + public function getDistributorByName ($name) { + $dql = "SELECT d FROM PartKeepr\Distributor\Distributor d WHERE d.name = :name"; + $query = PartKeepr::getEM()->createQuery($dql); + $query->setParameter("name", $name); + + return $query->getSingleResult(); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Distributor/DistributorService.php b/src/backend/PartKeepr/Distributor/DistributorService.php @@ -0,0 +1,82 @@ +<?php +namespace PartKeepr\Distributor; +use PartKeepr\Service\RestfulService; + +use PartKeepr\Service\Service, + PartKeepr\Part\PartManager, + PartKeepr\Stock\StockEntry, + PartKeepr\PartKeepr, + PartKeepr\Session\SessionManager; + +class DistributorService extends Service implements RestfulService { + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::get() + */ + public function get () { + if ($this->hasParameter("id")) { + return array("data" => DistributorManager::getInstance()->getDistributor($this->getParameter("id"))->serialize()); + } else { + if ($this->hasParameter("sort")) { + $tmp = json_decode($this->getParameter("sort"), true); + + $aSortParams = $tmp[0]; + } else { + $aSortParams = array( + "property" => "name", + "direction" => "ASC"); + } + return DistributorManager::getInstance()->getDistributors( + $this->getParameter("start", $this->getParameter("start", 0)), + $this->getParameter("limit", $this->getParameter("limit", 25)), + $this->getParameter("sortby", $aSortParams["property"]), + $this->getParameter("dir", $aSortParams["direction"]), + $this->getParameter("query", "")); + } + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::create() + */ + public function create () { + $this->requireParameter("name"); + + $distributor = new Distributor; + $distributor->deserialize($this->getParameters()); + + PartKeepr::getEM()->persist($distributor); + PartKeepr::getEM()->flush(); + + return array("data" => $distributor->serialize()); + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::update() + */ + public function update () { + $this->requireParameter("id"); + $this->requireParameter("name"); + + $distributor = Distributor::loadById($this->getParameter("id")); + $distributor->deserialize($this->getParameters()); + + PartKeepr::getEM()->flush(); + + return array("data" => $distributor->serialize()); + + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::destroy() + */ + public function destroy () { + $this->requireParameter("id"); + + DistributorManager::getInstance()->deleteDistributor($this->getParameter("id")); + + return array("data" => null); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Footprint/Footprint.php b/src/backend/PartKeepr/Footprint/Footprint.php @@ -0,0 +1,192 @@ +<?php +namespace PartKeepr\Footprint; +use PartKeepr\Util\Deserializable; + +use PartKeepr\Util\Serializable; +use PartKeepr\FootprintCategory\FootprintCategory; +use PartKeepr\Util\BaseEntity; + +/** @Entity */ + +class Footprint extends BaseEntity implements Serializable, Deserializable { + /** + * Holds the footprint name + * @Column(length=64,unique=true) + * @var string + */ + private $name; + + /** + * Holds the footprint description + * @Column(type="text",nullable=true) + * @var string + */ + private $description; + + /** + * The category of the footprint + * @ManyToOne(targetEntity="PartKeepr\FootprintCategory\FootprintCategory") + * @var Category + */ + private $category; + + /** + * Holds the footprint image + * @OneToOne(targetEntity="PartKeepr\Footprint\FootprintImage",mappedBy="footprint",cascade={"persist", "remove"}) + * @var FootprintImage + */ + private $image; + + /** + * Holds the footprint attachments + * @OneToMany(targetEntity="PartKeepr\Footprint\FootprintAttachment",mappedBy="footprint",cascade={"persist", "remove"}) + * @var FootprintAttachment + */ + private $attachments; + + /** + * Constructs a new Footprint entity + */ + public function __construct () { + $this->attachments = new \Doctrine\Common\Collections\ArrayCollection(); + } + + /** + * Sets the name of this footprint + * + * @param string $name The footprint name + */ + public function setName ($name) { + $this->name = $name; + } + + /** + * Returns the name of this footprint + * @return string The name of this footprint + */ + public function getName () { + return $this->name; + } + + /** + * Sets the description of this footprint + * @param string $description The description + */ + public function setDescription ($description) { + $this->description = $description; + } + + /** + * Returns the description of this footprint + * @return string The description + */ + public function getDescription () { + return $this->description; + } + + /** + * Sets the category for this footprint + * @param \PartKeepr\FootprintCategory\FootprintCategory $category The category + */ + public function setCategory (FootprintCategory $category) { + $this->category = $category; + } + + /** + * Returns the category of this footprint + * @return FootprintCategory The footprint category + */ + public function getCategory () { + return $this->category; + } + + /** + * Sets the footprint image + * @param FootprintImage $image The footprint image + */ + public function setImage (FootprintImage $image) { + $this->image = $image; + $image->setFootprint($this); + } + + /** + * Returns the footprint image + * @return FootprintImage The footprint image + */ + public function getImage () { + return $this->image; + } + + /** + * Returns the attachments for this footprint + * @return ArrayCollection The attachments + */ + public function getAttachments () { + return $this->attachments; + } + + /** + * Serializes the footprint + * @return array the serialized footprint + */ + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "description" => $this->getDescription(), + "image_id" => is_object($this->getImage()) ? $this->getImage()->getId() : null, + "category" => is_object($this->getCategory()) ? $this->getCategory()->getId() : null, + "attachments" => $this->serializeChildren($this->getAttachments()) + ); + } + + /** + * Deserializes the footprint + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "name": + $this->setName($value); + break; + case "description": + $this->setDescription($value); + break; + case "image_id": + if ($value == "") { + echo "/** Breaking because of empty value */"; + break; } + + try { + $image = FootprintImage::loadById($value); + $this->setImage($image); + } catch (\Exception $e) { + if ($this->getImage()) { + // Image was not found, maybe a temporary image? + $this->getImage()->replaceFromTemporaryFile($value); + } else { + $image = FootprintImage::createFromTemporaryFile($value); + $this->setImage($image); + } + } + + break; + case "category": + try { + $category = FootprintCategory::loadById($value); + $this->setCategory($category); + } catch (\Exception $e) { + // Category was not found, do not change category. + } + break; + case "attachments": + $this->deserializeChildren($value, $this->getAttachments(), "PartKeepr\Footprint\FootprintAttachment"); + foreach ($this->getAttachments() as $attachment) { + $attachment->setFootprint($this); + } + break; + } + } + } +} diff --git a/src/backend/PartKeepr/Footprint/FootprintAttachment.php b/src/backend/PartKeepr/Footprint/FootprintAttachment.php @@ -0,0 +1,101 @@ +<?php +namespace PartKeepr\Footprint; + +use PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable, + PartKeepr\UploadedFile\UploadedFile; + +/** + * Holds a footprint attachment + * @Entity + **/ +class FootprintAttachment extends UploadedFile implements Serializable, Deserializable { + /** + * The description of this attachment + * @Column(type="text") + * @var string + */ + private $description; + + /** + * Creates a new footprint attachment + */ + public function __construct () { + parent::__construct(); + $this->setType("FootprintAttachment"); + } + /** + * The footprint object + * @ManyToOne(targetEntity="PartKeepr\Footprint\Footprint") + * @var Footprint + */ + private $footprint = null; + + /** + * Sets the footprint + * @param Footprint $footprint The footprint to set + */ + public function setFootprint (Footprint $footprint) { + $this->footprint = $footprint; + } + + /** + * Returns the footprint + * @return Footprint the footprint + */ + public function getFootprint () { + return $this->footprint; + } + + /** + * Sets the description for this attachment + * @param string $description The attachment description + */ + public function setDescription ($description) { + $this->description = $description; + } + + /** + * Returns the description for this attachment + * @return string The description + */ + public function getDescription () { + return $this->description; + } + + /** + * + * Serializes this footprint attachment + * @return array The serialized footprint attachment + */ + public function serialize () { + return array( + "id" => $this->getId(), + "footprint_id" => $this->getFootprint()->getId(), + "originalFilename" => $this->getOriginalFilename(), + "mimetype" => $this->getMimetype(), + "extension" => $this->getExtension(), + "size" => $this->getSize(), + "description" => $this->getDescription()); + } + + /** + * Deserializes the footprint attachment + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + if (array_key_exists("id", $parameters)) { + if (substr($parameters["id"], 0, 4) === "TMP:") { + $this->replaceFromTemporaryFile($parameters["id"]); + } + } + + foreach ($parameters as $key => $value) { + switch ($key) { + case "description": + $this->setDescription($value); + break; + } + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Footprint/FootprintImage.php b/src/backend/PartKeepr/Footprint/FootprintImage.php @@ -0,0 +1,50 @@ +<?php +namespace PartKeepr\Footprint; + +use PartKeepr\Util\Serializable, + PartKeepr\Image\Image; + +/** + * Holds a footprint image + * @Entity + **/ +class FootprintImage extends Image implements Serializable { + /** + * The footprint object + * @OneToOne(targetEntity="PartKeepr\Footprint\Footprint",inversedBy="image") + * @var Footprint + */ + private $footprint = null; + + /** + * Creates a new IC logo instance + */ + public function __construct () { + parent::__construct(Image::IMAGE_FOOTPRINT); + } + + /** + * Sets the footprint + * @param Footprint $footprint The footprint to set + */ + public function setFootprint (Footprint $footprint) { + $this->footprint = $footprint; + } + + /** + * Returns the footprint + * @return Footprint the footprint + */ + public function getFootprint () { + return $this->footprint; + } + + /** + * + * Serializes this ic logo + * @return array The serialized ic logo + */ + public function serialize () { + return array("id" => $this->getId(), "footprint_id" => $this->getFootprint()->getId()); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Footprint/FootprintManager.php b/src/backend/PartKeepr/Footprint/FootprintManager.php @@ -0,0 +1,158 @@ +<?php +namespace PartKeepr\Footprint; + +use PartKeepr\Util\Singleton, + PartKeepr\Util\SerializableException, + PartKeepr\Footprint\Footprint, + PartKeepr\PartKeepr, + PartKeepr\Util\Exceptions\EntityNotFoundException; + +class FootprintManager extends Singleton { + /** + * Returns a list of footprints. + * + * @param int $start Start of the list, default 0 + * @param int $limit Number of users to list, default 10 + * @param string $sort The field to sort by, default "name" + * @param string $dir The direction to sort (ASC or DESC), default ASC + * @param string $filter A string to filter the footprint's name by, default empty + */ + public function getFootprints ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { + + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select("f.id, f.name, f.description, im.id AS image_id, ca.id AS category")->from("PartKeepr\Footprint\Footprint","f") + ->leftJoin("f.image", "im") + ->leftJoin("f.category", "ca"); + + if ($filter != "") { + $qb = $qb->where("LOWER(f.name) LIKE :filter"); + $qb->setParameter("filter", "%".strtolower($filter)."%"); + } + + if ($limit > -1) { + $qb->setMaxResults($limit); + $qb->setFirstResult($start); + } + + $qb->orderBy("f.".$sort, $dir); + + $query = $qb->getQuery(); + + $result = $query->getResult(); + + $totalQueryBuilder = PartKeepr::getEM()->createQueryBuilder(); + $totalQueryBuilder->select("COUNT(f.id)")->from("PartKeepr\Footprint\Footprint","f")->leftJoin("f.image", "im"); + + + + if ($filter != "") { + $totalQueryBuilder = $totalQueryBuilder->where("LOWER(f.name) LIKE :filter"); + $totalQueryBuilder->setParameter("filter", "%".strtolower($filter)."%"); + } + + $totalQuery = $totalQueryBuilder->getQuery(); + + return array("data" => $result, "totalCount" => $totalQuery->getSingleScalarResult()); + } + + /** + * Creates a new footprint + * + * @param string $footprint The footprint's name + * @throws \PartKeepr\Util\SerializableException + * @throws PDOException + */ + public function addFootprint ($name) { + $fp = new Footprint(); + $fp->setName($name); + + // @todo Port the UniqueEntityValidator from Symfony2 to here. + try { + PartKeepr::getEM()->persist($fp); + PartKeepr::getEM()->flush(); + } catch (\PDOException $e) { + if ($e->getCode() == "23000") { + $exception = new SerializableException(sprintf(PartKeepr::i18n("Footprint %s already exists!"), $name)); + $exception->setDetail(sprintf(PartKeepr::i18n("You tried to add the footprint %s, but a footprint with the same name already exists."), $name)); + + throw $exception; + } else { + throw $e; + } + } + + return $fp; + } + + /** + * Deletes the footprint with the given id. + * + * @param int $id The footprint id to delete + * @throws \PartKeepr\Util\SerializableException + */ + public function deleteFootprint ($id) { + $footprint = Footprint::loadById($id); + + try { + PartKeepr::getEM()->remove($footprint); + PartKeepr::getEM()->flush(); + } catch (\PDOException $e) { + if ($e->getCode() == "23000") { + $exception = new SerializableException(sprintf(PartKeepr::i18n("Footprint %s is in use by some parts!"), $footprint->getName())); + $exception->setDetail(sprintf(PartKeepr::i18n("You tried to delete the footprint %s, but there are parts which use this footprint."), $footprint->getName())); + + throw $exception; + } + } + } + + /** + * Loads a footprint by id + * @param int $id The footprint id + */ + public function getFootprint ($id) { + return Footprint::loadById($id); + } + + /** + * Finds or creates a footprint by name. + * + * @param mixed $footprint Either the ID or the footprint's name to find + */ + public function getOrCreateFootprint ($footprint) { + try { + $footprint = Footprint::loadById($footprint); + return $footprint; + } catch (EntityNotFoundException $e) {} + + $dql = "SELECT f FROM PartKeepr\Footprint\Footprint f WHERE f.name = :name"; + $query = PartKeepr::getEM()->createQuery($dql); + $query->setParameter("name", $footprint); + + try { + $footprint = $query->getSingleResult(); + return $footprint; + } catch (\Exception $e) {} + + $fp = new Footprint(); + $fp->setName($footprint); + + PartKeepr::getEM()->persist($fp); + + return $fp; + } + + /** + * Retrieves a distributor by its name. + * + * @param string $name The name of the distributor to retrieve + * @throws Doctrine\ORM\NoResultException If the distributor was not found + */ + public function getFootprintByName ($name) { + $dql = "SELECT fp FROM PartKeepr\Footprint\Footprint fp WHERE fp.name = :name"; + $query = PartKeepr::getEM()->createQuery($dql); + $query->setParameter("name", $name); + + return $query->getSingleResult(); + } +} diff --git a/src/backend/PartKeepr/Footprint/FootprintService.php b/src/backend/PartKeepr/Footprint/FootprintService.php @@ -0,0 +1,96 @@ +<?php +namespace PartKeepr\Footprint; + +use PartKeepr\TempImage\TempImage, + PartKeepr\FootprintCategory\FootprintCategory, + PartKeepr\Service\Service, + PartKeepr\Service\RestfulService, + PartKeepr\PartKeepr, + PartKeepr\Footprint\FootprintManager; + +class FootprintService extends Service implements RestfulService { + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::get() + */ + public function get () { + if ($this->hasParameter("id")) { + return array("data" => FootprintManager::getInstance()->getFootprint($this->getParameter("id"))->serialize()); + } else { + if ($this->hasParameter("sort")) { + $tmp = json_decode($this->getParameter("sort"), true); + + $aSortParams = $tmp[0]; + } else { + $aSortParams = array( + "property" => "name", + "direction" => "ASC"); + } + return FootprintManager::getInstance()->getFootprints( + $this->getParameter("start", $this->getParameter("start", 0)), + $this->getParameter("limit", $this->getParameter("limit", 25)), + $this->getParameter("sortby", $aSortParams["property"]), + $this->getParameter("dir", $aSortParams["direction"]), + $this->getParameter("query", "")); + } + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::create() + */ + public function create () { + $this->requireParameter("name"); + + $footprint = new Footprint(); + $footprint->deserialize($this->getParameters()); + + PartKeepr::getEM()->persist($footprint); + + PartKeepr::getEM()->flush(); + + return array("data" => $footprint->serialize()); + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::update() + */ + public function update () { + $this->requireParameter("id"); + $this->requireParameter("name"); + $footprint = Footprint::loadById($this->getParameter("id")); + + $footprint->deserialize($this->getParameters()); + + PartKeepr::getEM()->flush(); + + return array("data" => $footprint->serialize()); + + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::destroy() + */ + public function destroy () { + $this->requireParameter("id"); + + FootprintManager::getInstance()->deleteFootprint($this->getParameter("id")); + + return array("data" => null); + } + + public function moveFootprint () { + $this->requireParameter("targetCategory"); + $this->requireParameter("id"); + + $footprint = Footprint::loadById($this->getParameter("id")); + $category = FootprintCategory::loadById($this->getParameter("targetCategory")); + + $footprint->setCategory($category); + + PartKeepr::getEM()->flush(); + } + +} diff --git a/src/backend/PartKeepr/FootprintAttachment/FootprintAttachmentManager.php b/src/backend/PartKeepr/FootprintAttachment/FootprintAttachmentManager.php @@ -0,0 +1,68 @@ +<?php +namespace PartKeepr\FootprintAttachment; + +use PartKeepr\Util\Singleton, + PartKeepr\Footprint\Footprint, + PartKeepr\PartKeepr; + +class FootprintAttachmentManager extends Singleton { + /** + * Returns a list of footprint attachments + * + * @param int $start Start of the list, default 0 + * @param int $limit Number of users to list, default 10 + * @param string $sort The field to sort by, default "name" + * @param string $dir The direction to sort (ASC or DESC), default ASC + * @param string $filter The footprint id + */ + public function getFootprintAttachments ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { + + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select("st")->from("PartKeepr\Footprint\FootprintAttachment","st") + ->leftJoin('st.footprint', "fp"); + + if ($filter != "") { + $footprint = Footprint::loadById($filter); + $qb = $qb->where("st.footprint = :footprint"); + $qb->setParameter("footprint", $footprint); + } + + if ($limit > -1) { + $qb->setMaxResults($limit); + $qb->setFirstResult($start); + } + + $qb->orderBy("st.".$sort, $dir); + + $query = $qb->getQuery(); + + $result = $query->getResult(); + + $totalQueryBuilder = PartKeepr::getEM()->createQueryBuilder(); + $totalQueryBuilder->select("COUNT(st.id)")->from("PartKeepr\Footprint\FootprintAttachment","st"); + + + + if ($filter != "") { + $totalQueryBuilder = $totalQueryBuilder->where("st.footprint = :footprint"); + $totalQueryBuilder->setParameter("footprint", $footprint); + } + + $totalQuery = $totalQueryBuilder->getQuery(); + + $aData = array(); + foreach ($result as $item) { + $aData[] = $item->serialize(); + } + return array("data" => $aData, "totalCount" => $totalQuery->getSingleScalarResult()); + } + + /** + * Returns a footprint attachment by id + * @param int $id The footprint attachment id + */ + public function getFootprintAttachment ($id) { + return FootprintAttachment::loadById($id); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/FootprintAttachment/FootprintAttachmentService.php b/src/backend/PartKeepr/FootprintAttachment/FootprintAttachmentService.php @@ -0,0 +1,102 @@ +<?php +namespace PartKeepr\FootprintAttachment; + +use PartKeepr\Footprint\FootprintAttachment, + PartKeepr\UploadedFile\TempUploadedFile, + PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\PartKeepr, + PartKeepr\Footprint\Footprint, + PartKeepr\Session\SessionManager; + +class FootprintAttachmentService extends Service implements RestfulService { + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::get() + */ + public function get () { + if ($this->hasParameter("id")) { + return FootprintAttachmentManager::getInstance()->getFootprintAttachment($this->getParameter("id"))->serialize(); + } else { + if ($this->hasParameter("sort")) { + $tmp = json_decode($this->getParameter("sort"), true); + + $aSortParams = $tmp[0]; + } else { + $aSortParams = array( + "property" => "id", + "direction" => "ASC"); + } + + $filter = ""; + + if ($this->hasParameter("filter")) { + $tmp = json_decode($this->getParameter("filter"), true); + + foreach ($tmp as $item) { + if (array_key_exists("property", $item)) { + if ($item["property"] == "footprint_id") { + if (array_key_exists("value", $item)) { + $filter = $item["value"]; + } + } + } + } + } + return FootprintAttachmentManager::getInstance()->getFootprintAttachments( + $this->getParameter("start", $this->getParameter("start", 0)), + $this->getParameter("limit", $this->getParameter("limit", 25)), + $this->getParameter("sortby", $aSortParams["property"]), + $this->getParameter("dir", $aSortParams["direction"]), + $filter); + } + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::create() + */ + public function create () { + $this->requireParameter("tmp_id"); + $this->requireParameter("footprint_id"); + + $tmpImage = TempUploadedFile::loadById($this->getParameter("tmp_id")); + + $file = new FootprintAttachment(); + + $footprint = Footprint::loadById($this->getParameter("footprint_id")); + + $file->setFootprint($footprint); + $file->replace($tmpImage->getFilename()); + $file->setOriginalFilename($tmpImage->getOriginalFilename()); + $file->setDescription($this->getParameter("description")); + PartKeepr::getEM()->persist($file); + PartKeepr::getEM()->flush(); + + return $file->serialize(); + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::update() + */ + public function update () { + + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::destroy() + */ + public function destroy () { + $this->requireParameter("id"); + + $file = FootprintAttachment::loadById($this->getParameter("id")); + + PartKeepr::getEM()->remove($file); + PartKeepr::getEM()->flush(); + + return array("data" => null); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/FootprintCategory/FootprintCategory.php b/src/backend/PartKeepr/FootprintCategory/FootprintCategory.php @@ -0,0 +1,14 @@ +<?php +namespace PartKeepr\FootprintCategory; + +use PartKeepr\Category\AbstractCategory; + +/** + * @Entity + * @Table(indexes={@index(columns={"lft"}),@index(columns={"rgt"})}) + * The entity for our footprint categories + * + */ +class FootprintCategory extends AbstractCategory { + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/FootprintCategory/FootprintCategoryManager.php b/src/backend/PartKeepr/FootprintCategory/FootprintCategoryManager.php @@ -0,0 +1,42 @@ +<?php +namespace PartKeepr\FootprintCategory; + +use PartKeepr\Category\AbstractCategoryManager; +use DoctrineExtensions\NestedSet\NodeWrapper; +use PartKeepr\Util\SerializableException; +use PartKeepr\PartKeepr; + +class FootprintCategoryManager extends AbstractCategoryManager { + protected $categoryClass = "PartKeepr\FootprintCategory\FootprintCategory"; + + /** + * Deletes the given category ID. + * @param $id int The category id to delete + * @throws CategoryNotFoundException If the category wasn't found + */ + public function deleteCategory ($id) { + $category = $this->getCategory($id); + + try { + + if ($category->hasChildren()) { + $exception = new SerializableException(sprintf(PartKeepr::i18n("Category '%s' contains other categories."), $category->getNode()->getName())); + $exception->setDetail(sprintf(PartKeepr::i18n("You tried to delete the category '%s', but it still contains other categories. Please move the categories or delete them first."), $category->getNode()->getName())); + + throw $exception; + } + + parent::deleteCategory($id); + } catch (\PDOException $e) { + if ($e->getCode() == "23000") { + $exception = new SerializableException(sprintf(PartKeepr::i18n("Category '%s' contains footprints."), $category->getNode()->getName())); + $exception->setDetail(sprintf(PartKeepr::i18n("You tried to delete the category '%s', but it still contains footprints. Please move the footprints to another category."), $category->getNode()->getName())); + + throw $exception; + } else { + throw $e; + } + } + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/FootprintCategory/FootprintCategoryService.php b/src/backend/PartKeepr/FootprintCategory/FootprintCategoryService.php @@ -0,0 +1,9 @@ +<?php +namespace PartKeepr\FootprintCategory; + +use PartKeepr\Category\AbstractCategoryService; + +class FootprintCategoryService extends AbstractCategoryService { + protected $categoryManagerClass = "PartKeepr\FootprintCategory\FootprintCategoryManager"; + protected $categoryClass = "PartKeepr\FootprintCategory\FootprintCategory"; +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/FulltextSearch/FulltextSearch.php b/src/backend/PartKeepr/FulltextSearch/FulltextSearch.php @@ -0,0 +1,94 @@ +<?php +namespace PartKeepr\FulltextSearch; + +use PartKeepr\PartKeepr; + +abstract class FulltextSearch { + + /** + * Creates a new fulltext search based on the given query. + * + * The query can contain different query strings, separated by space. The query returns only results which contain + * all query strings in any fields. + * + * If the query is "100 ohm", it would only consider entities which have both "100" and "ohm", no matter which field + * the value is in. + * + * @param string $query The query to search for + */ + public function __construct ($query) { + $this->query = explode(" ", $query); + } + + /** + * Fires the query and returns the results. + * + * @param none + * @return array An array with all IDs which match the query + */ + public function query () { + return $this->fallbackSearch(); + } + + /** + * A fallback search in case no fulltext search engine is available. This directly queries the database, which is + * slow. + */ + protected function fallbackSearch () { + $qb = PartKeepr::getEM()->createQueryBuilder(); + + $qb ->select("q.id") + ->from($this->getEntityName(), "q") + ->where("1=1"); + + $dqlChunks = array(); + + foreach ($this->query as $id => $queryString) { + $partDqlChunks = array(); + foreach ($this->getFields() as $field) { + $partDqlChunks[] = "LOWER(q." . $field.") LIKE :filter".$id; + } + + $dqlChunks[] = "(".implode(" OR ", $partDqlChunks).")"; + + $qb->setParameter("filter".$id, "%".str_replace("%", "\\%", strtolower($queryString))."%"); + } + + $qb->andWhere(implode(" AND " ,$dqlChunks)); + + $query = $qb->getQuery(); + + + + $result = $query->getArrayResult(); + + if (count($result) > 0) { + $results = array(); + + foreach ($result as $value) { + $results[] = $value["id"]; + } + + return $results; + } else { + return array(0); + } + } + + /** + * Returns the FQDN name of the entity. This needs to be overridden in child classes. + * + * @param none + * @return string the FQDN of the entity to query, e.g. PartKeepr\Part\Part + */ + abstract protected function getEntityName (); + + /** + * Returns the fields in which the fulltext search should search in. + * + * @param none + * @return array An array of field names, e.g. array("name", "description") + */ + abstract protected function getFields (); + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Image/CachedImage.php b/src/backend/PartKeepr/Image/CachedImage.php @@ -0,0 +1,99 @@ +<?php +namespace PartKeepr\Image; + +use PartKeepr\PartKeepr; + +/** + * Represtends a cached image. Cached images are used for scale/resize + * operations, so that the resize/scale operation doesn't need to be done + * every time a scaled/resized image is requested. + * + * @Entity + */ +class CachedImage { + /** + * Specifies the ID of the cached image. + * + * @var integer + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + **/ + private $id; + + /** + * Specifies the ID of the original image + * + * @var integer + * @Column(type="integer") + */ + private $originalId; + + /** + * Specifies the type of the original image. + * + * @var string + * @Column(type="string") + **/ + private $originalType; + + /** + * The cache filename of the image + * + * @var string + * @Column(type="string") + */ + private $cacheFile; + + /** + * Creates a new cache entry for a specific image. + * + * @param Image $image The image to cache + * @param string $cacheFile The file which holds the cached image + */ + public function __construct (Image $image, $cacheFile) { + $this->originalId = (int)$image->getId(); + $this->originalType = $image->getType(); + $this->cacheFile = $cacheFile; + } + + /** + * Returns the cache file + * @return string The cache file including path + */ + public function getCacheFile () { + return $this->cacheFile; + } + + /** + * Removes all caches for a specific image. This is usually called + * when a new version of an image is uploaded. + * + * Automatically calls "flush" on the entity manager. + * + * @param Image $image The image to invalidate + */ + public static function invalidate (Image $image) { + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select(array("c")) + ->from('PartKeepr\Image\CachedImage', 'c') + ->where("c.originalId = :id") + ->andWhere("c.originalType = :type") + ->setParameter("id", $image->getId()) + ->setParameter("type", $image->getType()); + + $query = $qb->getQuery(); + + $bImagesRemoved = false; + + foreach ($query->getResult() as $file) { + unlink($file->getCacheFile()); + PartKeepr::getEM()->remove($file); + $bImagesRemoved = true; + } + + if ($bImagesRemoved) { + PartKeepr::getEM()->flush(); + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Image/Exceptions/InvalidImageTypeException.php b/src/backend/PartKeepr/Image/Exceptions/InvalidImageTypeException.php @@ -0,0 +1,11 @@ +<?php +namespace PartKeepr\Image\Exceptions; + +/** + * Thrown if an invalid type was passed + */ +class InvalidImageTypeException extends \Exception { + public function __construct ($type) { + parent::__construct("The image type $type is unknown."); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Image/Image.php b/src/backend/PartKeepr/Image/Image.php @@ -0,0 +1,227 @@ +<?php +namespace PartKeepr\Image; + +use PartKeepr\PartKeepr, + PartKeepr\UploadedFile\UploadedFile, + PartKeepr\Util\Configuration, + PartKeepr\TempImage\TempImage, + PartKeepr\Image\Exceptions\InvalidImageTypeException; + +/** + * This is only a storage class; actual image rendering is done by the ImageRenderer. + * + * @MappedSuperclass + */ +abstract class Image extends UploadedFile implements RenderableImage { + const IMAGE_ICLOGO = "iclogo"; + const IMAGE_TEMP = "temp"; + const IMAGE_PART = "part"; + const IMAGE_STORAGELOCATION = "storagelocation"; + const IMAGE_FOOTPRINT = "footprint"; + + /** + * Holds the image renderer + * @var object + */ + private $renderer; + + /** + * Constructs a new image object. + * + * @param string $type The type for the image, one of the IMAGE_ constants. + */ + public function __construct ($type) { + parent::__construct(); + $this->setType($type); + } + + /** + * Sets the type of the image. Once the type is set, + * it may not be changed later. + * + * @param string $type The type for the image, one of the IMAGE_ constants. + * @throws InvalidImageTypeException + */ + protected function setType ($type) { + switch ($type) { + case Image::IMAGE_ICLOGO: + case Image::IMAGE_TEMP: + case Image::IMAGE_PART: + case Image::IMAGE_FOOTPRINT: + case Image::IMAGE_STORAGELOCATION: + parent::setType($type); + break; + default: + throw new InvalidImageTypeException($type); + } + } + + /** + * Returns the renderer for image manipulations + * @return object + */ + public function getRenderer () { + if (!$this->renderer instanceof ImageRenderer) { + $this->renderer = new ImageRenderer($this); + } + + return $this->renderer; + } + /** + * Replaces the current image with a new image. + * + * Automatically converts from one format to PNG, + * which is the default when dealing with images + * on the platform. + * + * @param string $path The path to the original image + */ + public function replace ($path) { + parent::replace($path); + + CachedImage::invalidate($this); + } + + /** + * Returns the full image filename including path. + * This is the image where all scale operations take place on. + * + * @param none + * @return string The full image filename including path. + */ + public function getFilename () { + return $this->getFilePath().$this->getPlainFilename().".png"; + } + + /** + * Returns the path to the image. May be overridden by + * subclasses. + * + * @param none + * @return string The path to the image + */ + public function getFilePath () { + return Configuration::getOption( + "partkeepr.images.path", + PartKeepr::getRootDirectory() . "/data/images/") . $this->getType() . "/"; + } + + /** + * Ensures that the image cache dir exists. + */ + public function ensureCachedirExists () { + if (!is_dir(Configuration::getOption("partkeepr.images.cache"))) { + mkdir(Configuration::getOption("partkeepr.images.cache"), 0777, true); + } + } + + /** + * Scales the image to fit within the given size. + * + * @param int $w The width + * @param int $h The height + * @param boolean $padding If true, pad the output image to the given size (transparent background). + * @return string The path to the scaled file + */ + public function fitWithin ($w, $h, $padding = false) { + $this->ensureCachedirExists(); + + if ($padding) { + $pd = "p"; + } else { + $pd = ""; + } + + $outputFile = Configuration::getOption("partkeepr.images.cache").md5($this->getFilename().$w."x".$h."fw".$pd).".png"; + + if (file_exists($outputFile)) { + return $outputFile; + } + + $this->getRenderer()->fitWithin($outputFile, $w, $h, $padding); + + $cachedImage = new CachedImage($this, $outputFile); + PartKeepr::getEM()->persist($cachedImage); + + return $outputFile; + } + + /** + * Convinience method for fitWithin. Automatically pads the image. + * + * @param int $w The width + * @param int $h The height + * @return string The path to the scaled file + */ + public function fitWithinPadding ($w, $h) { + return $this->fitWithin($w, $h); + } + + /** + * Scales the image to fit exactly within the given size. + * + * This method ensures that no blank space is in the output image, + * and that the output image is exactly the width and height specified. + * + * @param string $outputFile The output file + * @param int $w The width + * @param int $h The height + * @return string The path to the scaled file + */ + public function fitWithinExact ($w, $h) { + $this->ensureCachedirExists(); + + $outputFile = Configuration::getOption("partkeepr.images.cache").md5($this->getFilename().$w."x".$h."fwe").".png"; + + if (file_exists($outputFile)) { + return $outputFile; + } + + $this->getRenderer()->fitWithinExact($outputFile, $w, $h); + + $cachedImage = new CachedImage($this, $outputFile); + PartKeepr::getEM()->persist($cachedImage); + + return $outputFile; + } + + /** + * Scales the image to a specific width and height + * + * @param int $w The width + * @param int $h The height + * @return string The path to the scaled file + */ + public function scaleTo ($w, $h) { + $this->ensureCachedirExists(); + + $outputFile = Configuration::getOption("partkeepr.images.cache").md5($this->getFilename().$w."x".$h).".png"; + + if (file_exists($outputFile)) { + return $outputFile; + } + + $this->getRenderer()->scaleTo($outputFile, $w, $h); + + $cachedImage = new CachedImage($this, $outputFile); + PartKeepr::getEM()->persist($cachedImage); + + return $outputFile; + } + + /** + * Replaces the file with a given temporary file. + * @param string $id The temporary id (prefixed with TMP:) + */ + public function replaceFromTemporaryFile ($id) { + if (substr($id, 0, 4) === "TMP:") { + $tmpFileId = str_replace("TMP:", "", $id); + $tmpFile = TempImage::loadById($tmpFileId); + + $this->replace($tmpFile->getFilename()); + $this->setOriginalFilename($tmpFile->getOriginalFilename()); + + } + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Image/ImageRenderer.php b/src/backend/PartKeepr/Image/ImageRenderer.php @@ -0,0 +1,133 @@ +<?php +namespace PartKeepr\Image; + +use PartKeepr\Util\Configuration; + +class ImageRenderer { + private $image = null; + + public function __construct (RenderableImage $image) { + $this->image = $image; + } + + /** + * Renders and outputs the "image not found" image. + * + * Sends the header and immediately outputs the image. + * + * @param int $w Width of the image + * @param int $h Height of the image + */ + public static function outputRenderNotFoundImage ($w, $h) { + $image = imagecreate($w, $h); + $white = imagecolorallocate($image, 255,255,255); + $black = imagecolorallocate($image, 0,0,0); + + header("Content-Type: image/png"); + + $w = $_REQUEST["w"]-1; + $h = $_REQUEST["h"]-1; + imagefill($image, 0,0, $white); + + /* Draw the X */ + imageline($image, 0,0,$w,$h, $black); + imageline($image, $w,0,0,$h, $black); + imagepng($image); + } + + /** + * Scales the image to fit within the given size. + * + * @param string $outputFile The target file + * @param int $w The width + * @param int $h The height + * @param boolean $padding If true, pad the output image to the given size (transparent background). + * @return string The path to the scaled file + */ + public function fitWithin ($outputFile, $w, $h, $padding = false) { + $image = new \Imagick(); + $image->readImage($this->image->getFilename()); + + $sourceAspectRatio = $image->getImageWidth() / $image->getImageHeight(); + $targetAspectRatio = $w / $h; + + $filter = \Imagick::FILTER_UNDEFINED; + $blur = 1; + + $targetHeight = $h; + $targetWidth = $w; + + if ($sourceAspectRatio < $targetAspectRatio) { + $targetWidth = $h * $sourceAspectRatio; + $image->resizeImage($h * $sourceAspectRatio, $h, $filter, $blur); + } else { + $targetHeight = $w / $sourceAspectRatio; + $image->resizeImage($w, $w / $sourceAspectRatio, $filter, $blur); + } + + if ($padding) { + $posX = intval(($w - $targetWidth) / 2); + $posY = intval(($h - $targetHeight) / 2); + + $image->extentImage($w, $h,-$posX, -$posY); + } + + $image->writeImage($outputFile); + + return $outputFile; + } + + /** + * Scales the image to fit exactly within the given size. + * + * This method ensures that no blank space is in the output image, + * and that the output image is exactly the width and height specified. + * + * @param string $outputFile The output file + * @param int $w The width + * @param int $h The height + * @return string The path to the scaled file + */ + public function fitWithinExact ($outputFile, $w, $h) { + $image = new \Imagick(); + $image->readImage($this->image->getFilename()); + + $sourceAspectRatio = $image->getImageWidth() / $image->getImageHeight(); + $targetAspectRatio = $w / $h; + + $filter = \Imagick::FILTER_UNDEFINED; + $blur = 1; + + if ($sourceAspectRatio < $targetAspectRatio) { + $image->resizeImage($w, $w / $sourceAspectRatio, $filter, $blur); + } else { + $image->resizeImage($h * $sourceAspectRatio, $h, $filter, $blur); + } + + $offsetX = intval(($image->getImageWidth() - $w)/2); + $offsetY = intval(($image->getImageHeight() - $h)/2); + + $image = $image->getImageRegion($w, $h, $offsetX, $offsetY); + + $image->writeImage($outputFile); + + return $outputFile; + } + + /** + * Scales the image to a specific width and height + * + * @param string $outputFile The output file + * @param int $w The width + * @param int $h The height + * @return string The path to the scaled file + */ + public function scaleTo ($outputFile, $w, $h) { + $image = new \Imagick(); + $image->readImage($this->image->getFilename()); + $image->adaptiveResizeImage($w, $h); + $image->writeImage($outputFile); + + return $outputFile; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Image/RenderableImage.php b/src/backend/PartKeepr/Image/RenderableImage.php @@ -0,0 +1,16 @@ +<?php +namespace PartKeepr\Image; + +/** + * Interface for any image which needs to be rendered. + */ +interface RenderableImage { + /** + * Returns the full image filename including path. + * This is the image where all scale operations take place on. + * + * @param none + * @return string The full image filename including path. + */ + public function getFilename (); +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Logger/Logger.php b/src/backend/PartKeepr/Logger/Logger.php @@ -0,0 +1,121 @@ +<?php +namespace PartKeepr\Logger; + +/** + * This class implements a simple logging mechanism. + * + * Might be replaced with some more advanced logging framework, but it serves its purpose for now. + */ +use PartKeepr\Util\Configuration; + +class Logger { + /** + * Loglevel "debug" + * + * You want to use this loglevel for debugging messages. + * @var string + */ + const LOGLEVEL_DEBUG = "debug"; + + /** + * Loglevel "info" + * + * You want to use this loglevel for informational messages where the program flow can continue normally and no + * impacts occur. + * + * @var string + */ + const LOGLEVEL_INFO = "info"; + + /** + * Loglevel "warning" + * + * You want to use this loglevel for messages where program flow can continue normally, but a minor + * impact can occur. + * + * @var string + */ + const LOGLEVEL_WARNING = "warning"; + + /** + * Loglevel "error" + * + * You want to use this loglevel for messages where program flow could be interrupted and major impacts occur. + * + * @var string + */ + const LOGLEVEL_ERROR = "error"; + + /** + * Loglevel "critical" + * + * You want to use this loglevel for messages where program flow could be interrupted and major impacts occur. + * + * @var string + */ + const LOGLEVEL_CRITICAL = "critical"; + + /** + * Logs a message. + * + * @param string $message The message to log + * @param string $severity One of the Logger::LOGLEVEL_* constants + */ + public static function log ($message, $severity = Logger::LOGLEVEL_INFO) { + if (Configuration::getOption("partkeepr.logger.enable", false) === false) { + // No logging wanted + return; + } + + $logFormat = "[%-19s] [%-8s] %s"; + + $outputFile = Configuration::getOption("partkeepr.logger.outputfile", sys_get_temp_dir() . "/partkeepr.log"); + $fp = fopen($outputFile, "a"); + + $date = date("Y-m-d H:i:s"); + + fputs($fp, sprintf($logFormat, $date, $severity, $message)."\n"); + + fclose($fp); + } + + /** + * Logs a message with the Logger::LOGLEVEL_DEBUG severity. + * @param string $message + */ + public static function logDebug ($message) { + self::log($message, Logger::LOGLEVEL_DEBUG); + } + + /** + * Logs a message with the Logger::LOGLEVEL_INFO severity. + * @param string $message + */ + public static function logInfo ($message) { + self::log($message, Logger::LOGLEVEL_INFO); + } + + /** + * Logs a message with the Logger::LOGLEVEL_WARNING severity. + * @param string $message + */ + public static function logWarning ($message) { + self::log($message, Logger::LOGLEVEL_WARNING); + } + + /** + * Logs a message with the Logger::LOGLEVEL_ERROR severity. + * @param string $message + */ + public static function logError ($message) { + self::log($message, Logger::LOGLEVEL_ERROR); + } + + /** + * Logs a message with the Logger::LOGLEVEL_CRITICAL severity. + * @param string $message + */ + public static function logCritical ($message) { + self::log($message, Logger::LOGLEVEL_CRITICAL); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Manager/AbstractManager.php b/src/backend/PartKeepr/Manager/AbstractManager.php @@ -0,0 +1,230 @@ +<?php +namespace PartKeepr\Manager; + +use PartKeepr\Util\Singleton, + PartKeepr\PartKeepr, + Doctrine\ORM\QueryBuilder, + Doctrine\ORM\Query, + PartKeepr\Manager\Exceptions\EntityInUseException; + +/** + * The AbstractManager is a very basic helper which + * implements the mostly used operations on entities: + * + * - Create + * - Delete + * - List + * + */ +abstract class AbstractManager extends Singleton { + /** + * Returns the FQCN for the target entity to operate on. + * + * Needs to be implemented by the parent class. + * + * @return string The FQCN, e.g. PartKeepr\Part + */ + abstract public function getEntityName (); + + /** + * Returns all fields which need to appear in the getList ResultSet. + * + * This should include all fields required by the frontend. + * + * @return array An array of all fields which should be returned + */ + abstract public function getQueryFields (); + + /** + * Returns the default sort field + * + * @return string The default sort field + */ + abstract public function getDefaultSortField (); + + /** + * Loads an entity by id + * + * @param int $id The entity id + */ + public function getEntity ($id) { + return call_user_func($this->getEntityName()."::loadById", $id); + } + + /** + * Removes a specific entity from the system + * @param int $id + * @throws EntityInUseException Is thrown when the entity is in use + */ + public function deleteEntity ($id) { + $entity = $this->getEntity($id); + + try { + PartKeepr::getEM()->remove($entity); + PartKeepr::getEM()->flush(); + } catch (\PDOException $e) { + if ($e->getCode() == "23000") { + throw new EntityInUseException($entity); + } + } + } + + /** + * Creates a new entity from an array of parameters + * @param array $parameters An array which gets passed to the deserialize method + */ + public function createEntity ($parameters) { + $class = $this->getEntityName(); + $entity = new $class; + $entity->deserialize($parameters); + + PartKeepr::getEM()->persist($entity); + PartKeepr::getEM()->flush(); + + return $entity; + } + + /** + * Returns a list of entities. + * + * @param ManagerFilter $filter The filter settings for this query + */ + public function getList (ManagerFilter $filter) { + $qb = PartKeepr::getEM()->createQueryBuilder(); + + $qb->select("COUNT(q.id)")->from($this->getEntityName(),"q"); + + $this->applyFiltering($qb, $filter); + $this->applyCustomQuery($qb, $filter); + + $totalQuery = $qb->getQuery(); + + $this->applyResultFields($qb, $filter); + $this->applyPagination($qb, $filter); + $this->applySorting($qb, $filter); + + $query = $qb->getQuery(); + + return array("data" => $this->getResult($query), "totalCount" => $totalQuery->getSingleScalarResult()); + } + + /** + * Processes the result after it was retrieved. In the default configuration, it returns an array result, or + * if no query fields are specified, it tries to serialize all objects. + */ + protected function getResult (Query $query) { + if (count($this->getQueryFields()) == 0) { + $aSerializedResult = array(); + foreach ($query->getResult() as $result) { + $aSerializedResult[] = $result->serialize(); + } + + return $aSerializedResult; + } else { + return $query->getArrayResult(); + } + } + + /** + * Applies the result fields to the query. + * + * The result fields can be prefixed with the table alias or not; if not prefixed, it is assumed that we'll be + * using the specified entity FQCN. + * + * Note that the base name will be always "q", so avoid "q" as alias for joined tables. + * + * @param QueryBuilder $qb The query builder + * @param ManagerFilter $filter The query filter + */ + protected function applyResultFields (QueryBuilder $qb, ManagerFilter $filter) { + if (count($this->getQueryFields()) == 0) { + $qb->select("q"); + } else { + // Prepend a prefix to each field + $aQueryFields = array(); + foreach ($this->getQueryFields() as $field) { + + if (strpos($field, ".") === false) { + // The field is not prefixed, prepend the q. prefix + $aQueryFields[] = "q.".$field; + } else { + // Use as-is + $aQueryFields[] = $field; + } + } + + $qb->select($aQueryFields); + } + } + + /** + * Applies filtering to the query and calls back the custom filtering function, if required. + * + * @param QueryBuilder $qb The query builder + * @param ManagerFilter $filter The query filter + */ + protected function applyFiltering (QueryBuilder $qb, ManagerFilter $filter) { + if ($filter->getFilter() !== null && $filter->getFilterField() !== null) { + $aOrWhereFields = array(); + + if (is_array($filter->getFilterField())) { + foreach ($filter->getFilterField() as $field) { + $aOrWhereFields[] = "LOWER(q.".$field.") LIKE :filter"; + } + } else { + $aOrWhereFields[] = "LOWER(q.".$filter->getFilterField().") LIKE :filter"; + } + + foreach ($aOrWhereFields as $or) { + $qb->orWhere($or); + } + + $qb->setParameter("filter", "%".strtolower($filter->getFilter())."%"); + } + + if ($filter->getFilterCallback() !== null) { + call_user_func($filter->getFilterCallback(), $qb); + } + } + + + /** + * Applies a custom query to the QueryBuilder + * + * @param QueryBuilder $qb The query builder + * @param ManagerFilter $filter The query filter + */ + protected function applyCustomQuery (QueryBuilder $qb, ManagerFilter $filter) { + + } + + /** + * Applies pagination to the query + * + * @param QueryBuilder $qb The query builder + * @param ManagerFilter $filter The query filter + */ + protected function applyPagination (QueryBuilder $qb, ManagerFilter $filter) { + if ($filter->getStart() !== null && $filter->getLimit() !== null) { + $qb->setFirstResult($filter->getStart()); + } + + if ($filter->getLimit() !== null) { + $qb->setMaxResults($filter->getLimit()); + } + } + + /** + * Applies record sorting + * + * @param QueryBuilder $qb The query builder + * @param ManagerFilter $filter The query filter + */ + protected function applySorting (QueryBuilder $qb, ManagerFilter $filter) { + foreach ($filter->getSorters() as $sorter) { + if ($sorter->getSortField() !== null && $sorter->getSortField() != "q.") { + $qb->addOrderBy($sorter->getSortField(), $sorter->getSortDirection()); + } + } + } +} + \ No newline at end of file diff --git a/src/backend/PartKeepr/Manager/Exceptions/EntityInUseException.php b/src/backend/PartKeepr/Manager/Exceptions/EntityInUseException.php @@ -0,0 +1,31 @@ +<?php +namespace PartKeepr\Manager\Exceptions; + +/** + * This exception is thrown when an entity should be deleted, but is in use by other entities. + * @author felicitus + */ +class EntityInUseException extends \Exception { + + /** + * The entity + * @var BaseEntity + */ + private $entity; + + /** + * Constructs the exception + * @param BaseEntity $entity + */ + public function __construct (\PartKeepr\Util\BaseEntity $entity) { + parent::__construct(sprintf("Entity %s is referenced by other entities, can't delete", get_class($entity))); + $this->entity = $entity; + } + + /** + * Returns the entity which caused the error + */ + public function getEntity () { + return $this->entity; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Manager/ManagerFilter.php b/src/backend/PartKeepr/Manager/ManagerFilter.php @@ -0,0 +1,196 @@ +<?php +namespace PartKeepr\Manager; + +use PartKeepr\Service\Service; + +class ManagerFilter { + /** + * Specifies the record index at which to start + * @var integer + */ + protected $start = 0; + + /** + * Specifies the number of records to retrieve + * @var integer + */ + protected $limit = null; + + /** + * Specifies a string to filter for. Can either be a string + * or null if no filter is wanted + * @var string + */ + protected $filter = null; + + /** + * Specifies the field (or a list of fields) to apply the filter on + * @var mixed null if disabled, a string for a single field or an array of fields + */ + protected $filterField = null; + + /** + * Specifies the fields to sort by + * @var array + */ + protected $sorters = array(); + + /** + * A callback which is called when creating the filter + * @var function The callback + */ + protected $callback = null; + + /** + * Sets the start position + * @param int $start + */ + public function setStart ($start) { + $this->start = intval($start); + } + + /** + * Returns the start position + * @return int + */ + public function getStart () { + if ($this->start === null) { + return 0; + } else { + return $this->start; + } + } + + /** + * Sets the number of records to retrieve + * @param mixed $limit Either a positive integer, or null/-1 for no limit + */ + public function setLimit ($limit) { + if ($limit === null || $limit == -1) { + $this->limit = null; + } else { + $this->limit = intval($limit); + } + } + + /** + * Returns the number of records to retrieve + * @return int + */ + public function getLimit () { + return $this->limit; + } + + /** + * Sets the sorters for this filter. + * + * @param array $sorters An array of Sorter instances + */ + public function setSorters (array $sorters) { + // Make sure that each sorter is an instance of the Sorter class. + foreach ($sorters as $sorter) { + if (!($sorter instanceof Sorter)) { + throw new InvalidArgumentException("The passed sorters needs to be an array of Sorter instances"); + } + } + + $this->sorters = $sorters; + } + + /** + * Returns the sorters for this filter. + * @return array An array of Sorter instances + */ + public function getSorters () { + return $this->sorters; + } + + /** + * Sets the filter. Specify null if no filter is wanted. + * @param mixed $filter A string to filter for, or null to disable + */ + public function setFilter ($filter) { + $this->filter = $filter; + } + + /** + * Returns the filter. + * @return mixed Either a string to filter for, or null if disabled + */ + public function getFilter () { + return $this->filter; + } + + /** + * Sets the field(s) to filter for. + * + * If multiple fields are specified, they will be combined using an "OR" clause. + * + * @param mixed $field Either null to disable, a single string to specify a field, or an array of string fields + */ + public function setFilterField ($field) { + $this->filterField = $field; + } + + /** + * Returns the field(s) to filter for + * @return mixed See setFilterField + */ + public function getFilterField () { + return $this->filterField; + } + + /** + * Sets the filter callback + * @param function $callback A function which is called when creating a filter. The callback function receives the + * query builder as first argument. + */ + public function setFilterCallback ($callback) { + $this->callback = $callback; + } + + /** + * Returns the filter callback + * @return function The callback function + */ + public function getFilterCallback () { + return $this->callback; + } + + /** + * Constructs a new filter set. + * + * If a service is passed, the constructor automatically extracts the parameters from the service + * + * @todo Document which parameters we have + * + * @param Service $service A service to extract the information from, or null + */ + public function __construct (Service $service = null) { + if (is_object($service)) { + if ($service->hasParameter("start")) { + $this->setStart($service->getParameter("start", null)); + } + + if ($service->hasParameter("limit")) { + $this->setLimit($service->getParameter("limit", null)); + } + + if ($service->hasParameter("sort")) { + $tmp = json_decode($service->getParameter("sort"), true); + + $aSorters = array(); + + foreach ($tmp as $key => $sortParam) { + $aSorters[] = new Sorter("q.".$sortParam["property"], $sortParam["direction"]); + } + + $this->setSorters($aSorters); + } + + if ($service->hasParameter("query")) { + $this->setFilter($service->getParameter("query")); + } + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Manager/Sorter.php b/src/backend/PartKeepr/Manager/Sorter.php @@ -0,0 +1,79 @@ +<?php +namespace PartKeepr\Manager; + +/** + * Represents a sorter, which is used with the ManagerFilter class. + * + * This allows the developer to specify multiple sorters. One sorter contains a sort field and a sort direction. + */ +class Sorter { + /** + * The field to sort by + * @var string + */ + private $sortField = null; + + /** + * The direction to sort by + * @var string + */ + private $sortDirection = null; + + /** + * Constructs a new sorter. + * + * @param string $field The field to sort by + * @param string $direction The direction, either "asc" or "desc" + */ + public function __construct ($field = null, $direction = null) { + if ($field !== null) { + $this->setSortField($field); + } + + if ($direction !== null) { + $this->setSortDirection($direction); + } + } + + /** + * Sets the sort field for this sorter + * @param string $field The field to sort by + */ + public function setSortField ($field) { + $this->sortField = $field; + } + + /** + * Sets the sort direction for this sorter + * @param string $direction Either "asc" or "desc" + */ + public function setSortDirection ($direction) { + switch (strtolower($direction)) { + case "desc": + case "d": + $this->sortDirection = "desc"; + break; + case "asc": + case "a": + default: + $this->sortDirection = "asc"; + break; + } + } + + /** + * Returns the sort field for this sorter + * @return string The field name + */ + public function getSortField () { + return $this->sortField; + } + + /** + * Returns the sort order for this sorter + * @return string Either "asc" or "desc" + */ + public function getSortDirection () { + return $this->sortDirection; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Manufacturer/Manufacturer.php b/src/backend/PartKeepr/Manufacturer/Manufacturer.php @@ -0,0 +1,250 @@ +<?php +namespace PartKeepr\Manufacturer; + +use PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable, + PartKeepr\Util\BaseEntity, + PartKeepr\PartKeepr; + +/** + * Represents a manufacturer + * @Entity **/ +class Manufacturer extends BaseEntity implements Serializable, Deserializable { + /** + * The name of the manufacturer + * @Column(type="string",unique=true) + * @var string + */ + private $name; + + /** + * The address of the manufacturer + * @Column(type="text",nullable=true) + * @var string + */ + private $address; + + /** + * The URL + * @Column(type="string",nullable=true) + * @var string + */ + private $url; + + /** + * The email + * @Column(type="string",nullable=true) + * @var string + */ + private $email; + + /** + * The comment + * @Column(type="text",nullable=true) + * @var string + */ + private $comment; + + /** + * The phone number + * @Column(type="string",nullable=true) + * @var string + */ + private $phone; + + /** + * The fax number + * @Column(type="string",nullable=true) + * @var string + */ + private $fax; + + /** + * All ic logos of this manufacturer + * @OneToMany(targetEntity="PartKeepr\Manufacturer\ManufacturerICLogo",mappedBy="manufacturer",cascade={"persist", "remove"}) + */ + private $icLogos; + + /** + * Creates a new manufacturer instance + */ + public function __construct () { + $this->icLogos = new \Doctrine\Common\Collections\ArrayCollection(); + } + + /** + * Sets the name + * @param string $name The name + */ + public function setName ($name) { + $this->name = $name; + } + + /** + * Returns the name + * @return string The name + */ + public function getName () { + return $this->name; + } + + /** + * Sets the phone number + * @param string $phone The phone number + */ + public function setPhone ($phone) { + $this->phone = $phone; + } + + /** + * Returns the phone number + * @return string The phone number + */ + public function getPhone () { + return $this->phone; + } + + /** + * Sets the fax number + * @param string $fax The fax number + */ + public function setFax ($fax) { + $this->fax = $fax; + } + + /** + * Returns the fax number + * @return string The fax number + */ + public function getFax () { + return $this->fax; + } + + /** + * Sets the address + * @param string $address The address + */ + public function setAddress ($address) { + $this->address = $address; + } + + /** + * Returns the address + * @return string The address + */ + public function getAddress () { + return $this->address; + } + + /** + * Sets the comment + * @param string $comment The comment + */ + public function setComment ($comment) { + $this->comment = $comment; + } + + /** + * Returns the comment + * @return string The comment + */ + public function getComment () { + return $this->comment; + } + + /** + * Sets the email + * @param string $email The email + */ + public function setEmail ($email) { + $this->email = $email; + } + + /** + * Returns the email + * @return string The email + */ + public function getEmail () { + return $this->email; + } + + /** + * Sets the URL + * @param string $url The URL + */ + public function setURL ($url) { + $this->url = $url; + } + + /** + * Returns the URL + * @return string The url + */ + public function getURL () { + return $this->url; + } + + /** + * Returns the ic logos + * @return ArrayCollection The array with all ic logos + */ + public function getICLogos () { + return $this->icLogos; + } + + /** + * Returns this manufacturer in serialized form + * @return array The serialized manufacturer + */ + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "url" => $this->getURL(), + "address" => $this->getAddress(), + "email" => $this->getEmail(), + "comment" => $this->getComment(), + "phone" => $this->getPhone(), + "fax" => $this->getFax(), + "iclogos" => $this->serializeChildren($this->getICLogos()) + ); + } + + /** + * Deserializes the manufacturer + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "name": + $this->setName($value); + break; + case "url": + $this->setURL($value); + break; + case "comment": + $this->setComment($value); + break; + case "email": + $this->setEmail($value); + break; + case "fax": + $this->setFax($value); + break; + case "phone": + $this->setPhone($value); + break; + case "address": + $this->setAddress($value); + break; + case "iclogos": + $this->deserializeChildren($value, $this->getICLogos(), "PartKeepr\Manufacturer\ManufacturerICLogo"); + foreach ($this->getICLogos() as $iclogo) { + $iclogo->setManufacturer($this); + } + break; + } + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Manufacturer/ManufacturerICLogo.php b/src/backend/PartKeepr/Manufacturer/ManufacturerICLogo.php @@ -0,0 +1,63 @@ +<?php +namespace PartKeepr\Manufacturer; + +use PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable, + PartKeepr\Image\Image; + +/** + * Holds a manufacturer IC logo + * @Entity + **/ +class ManufacturerICLogo extends Image implements Serializable, Deserializable { + /** + * The manufacturer object + * @ManyToOne(targetEntity="PartKeepr\Manufacturer\Manufacturer") + * @var Manufacturer + */ + private $manufacturer = null; + + /** + * Creates a new IC logo instance + */ + public function __construct () { + parent::__construct(Image::IMAGE_ICLOGO); + } + + /** + * Sets the manufacturer + * @param Manufacturer $manufacturer The manufacturer to set + */ + public function setManufacturer (Manufacturer $manufacturer) { + $this->manufacturer = $manufacturer; + } + + /** + * Returns the manufacturer + * @return Manufacturer the manufacturer + */ + public function getManufacturer () { + return $this->manufacturer; + } + + /** + * + * Serializes this ic logo + * @return array The serialized ic logo + */ + public function serialize () { + return array("id" => $this->getId(), "manufacturer_id" => $this->getManufacturer()->getId()); + } + + /** + * Deserializes the iclogo + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + if (array_key_exists("id", $parameters)) { + if (substr($parameters["id"], 0, 4) === "TMP:") { + $this->replaceFromTemporaryFile($parameters["id"]); + } + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Manufacturer/ManufacturerManager.php b/src/backend/PartKeepr/Manufacturer/ManufacturerManager.php @@ -0,0 +1,104 @@ +<?php +namespace PartKeepr\Manufacturer; + +use PartKeepr\Util\Singleton, + PartKeepr\Manufacturer\Manufacturer, + PartKeepr\PartKeepr, + PartKeepr\Category\CategoryManager, + PartKeepr\Manufacturer\Exceptions\ManufacturerNotFoundException; + +class ManufacturerManager extends Singleton { + /** + * Returns a list of manufacturers. + * + * @param int $start Start of the list, default 0 + * @param int $limit Number of users to list, default 10 + * @param string $sort The field to sort by, default "name" + * @param string $dir The direction to sort (ASC or DESC), default ASC + * @param string $filter A string to filter the manufacturer's name by, default empty + */ + public function getManufacturers ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { + + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select("st.id, st.name, st.url, st.email, st.comment, st.address")->from("PartKeepr\Manufacturer\Manufacturer","st"); + + if ($filter != "") { + $qb = $qb->where("LOWER(st.name) LIKE :filter"); + $qb->setParameter("filter", "%".strtolower($filter)."%"); + } + + if ($limit > -1) { + $qb->setMaxResults($limit); + $qb->setFirstResult($start); + } + + $qb->orderBy("st.".$sort, $dir); + + $query = $qb->getQuery(); + + $result = $query->getResult(); + + $totalQueryBuilder = PartKeepr::getEM()->createQueryBuilder(); + $totalQueryBuilder->select("COUNT(st.id)")->from("PartKeepr\Manufacturer\Manufacturer","st"); + + + + if ($filter != "") { + $totalQueryBuilder = $totalQueryBuilder->where("LOWER(st.name) LIKE :filter"); + $totalQueryBuilder->setParameter("filter", "%".strtolower($filter)."%"); + } + + $totalQuery = $totalQueryBuilder->getQuery(); + + return array("data" => $result, "totalCount" => $totalQuery->getSingleScalarResult()); + } + + /** + * Adds a new manufacturer by name + * + * @param string $name The manufacturer name + */ + public function addManufacturer ($name) { + $manufacturer = new Manufacturer(); + $manufacturer->setName($name); + + PartKeepr::getEM()->persist($manufacturer); + PartKeepr::getEM()->flush(); + + return $manufacturer; + } + + /** + * Loads a manufacturer by id + * + * @param int $id The manufacturer id + */ + public function getManufacturer ($id) { + return Manufacturer::loadById($id); + } + + /** + * Deletes the manufacturer by id + * @param int $id The manufacturer's id + */ + public function deleteManufacturer ($id) { + $manufacturer = Manufacturer::loadById($id); + + PartKeepr::getEM()->remove($manufacturer); + PartKeepr::getEM()->flush(); + } + + /** + * Retrieves a manufacturer by its name. + * + * @param string $name The name of the manufacturer to retrieve + * @throws Doctrine\ORM\NoResultException If the manufacturer was not found + */ + public function getManufacturerByName ($name) { + $dql = "SELECT m FROM PartKeepr\Manufacturer\Manufacturer m WHERE m.name = :name"; + $query = PartKeepr::getEM()->createQuery($dql); + $query->setParameter("name", $name); + + return $query->getSingleResult(); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Manufacturer/ManufacturerService.php b/src/backend/PartKeepr/Manufacturer/ManufacturerService.php @@ -0,0 +1,82 @@ +<?php +namespace PartKeepr\Manufacturer; + +use PartKeepr\Service\RestfulService; + +use PartKeepr\Service\Service, + PartKeepr\Part\PartManager, + PartKeepr\Stock\StockEntry, + PartKeepr\PartKeepr, + PartKeepr\Session\SessionManager; + +class ManufacturerService extends Service implements RestfulService { + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::get() + */ + public function get () { + if ($this->hasParameter("id")) { + return array("data" => ManufacturerManager::getInstance()->getManufacturer($this->getParameter("id"))->serialize()); + } else { + if ($this->hasParameter("sort")) { + $tmp = json_decode($this->getParameter("sort"), true); + + $aSortParams = $tmp[0]; + } else { + $aSortParams = array( + "property" => "name", + "direction" => "ASC"); + } + return ManufacturerManager::getInstance()->getManufacturers( + $this->getParameter("start", $this->getParameter("start", 0)), + $this->getParameter("limit", $this->getParameter("limit", 25)), + $this->getParameter("sortby", $aSortParams["property"]), + $this->getParameter("dir", $aSortParams["direction"]), + $this->getParameter("query", "")); + } + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::create() + */ + public function create () { + $this->requireParameter("name"); + + $manufacturer = new Manufacturer; + $manufacturer->deserialize($this->getParameters()); + + PartKeepr::getEM()->persist($manufacturer); + PartKeepr::getEM()->flush(); + + return array("data" => $manufacturer->serialize()); + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::update() + */ + public function update () { + $this->requireParameter("id"); + $this->requireParameter("name"); + $manufacturer = ManufacturerManager::getInstance()->getManufacturer($this->getParameter("id")); + $manufacturer->deserialize($this->getParameters()); + + PartKeepr::getEM()->flush(); + + return array("data" => $manufacturer->serialize()); + + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::destroy() + */ + public function destroy () { + $this->requireParameter("id"); + + ManufacturerManager::getInstance()->deleteManufacturer($this->getParameter("id")); + + return array("data" => null); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/ManufacturerICLogo/ManufacturerICLogoManager.php b/src/backend/PartKeepr/ManufacturerICLogo/ManufacturerICLogoManager.php @@ -0,0 +1,65 @@ +<?php +namespace PartKeepr\ManufacturerICLogo; + +use PartKeepr\Util\Singleton, + PartKeepr\Manufacturer\Manufacturer, + PartKeepr\PartKeepr, + PartKeepr\Manufacturer\Exceptions\ManufacturerNotFoundException; + +class ManufacturerICLogoManager extends Singleton { + /** + * Returns a list of manufacturer ic logos. + * + * @param int $start Start of the list, default 0 + * @param int $limit Number of users to list, default 10 + * @param string $sort The field to sort by, default "name" + * @param string $dir The direction to sort (ASC or DESC), default ASC + * @param string $filter The manufacturer id + */ + public function getManufacturerICLogos ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { + + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select("st.id, maf.id AS manufacturer_id")->from("PartKeepr\Manufacturer\ManufacturerICLogo","st") + ->leftJoin('st.manufacturer', "maf"); + + if ($filter != "") { + $manufacturer = Manufacturer::loadById($filter); + $qb = $qb->where("st.manufacturer = :manufacturer"); + $qb->setParameter("manufacturer", $manufacturer); + } + + if ($limit > -1) { + $qb->setMaxResults($limit); + $qb->setFirstResult($start); + } + + $qb->orderBy("st.".$sort, $dir); + + $query = $qb->getQuery(); + + $result = $query->getResult(); + + $totalQueryBuilder = PartKeepr::getEM()->createQueryBuilder(); + $totalQueryBuilder->select("COUNT(st.id)")->from("PartKeepr\Manufacturer\ManufacturerICLogo","st"); + + + + if ($filter != "") { + $totalQueryBuilder = $totalQueryBuilder->where("st.manufacturer = :manufacturer"); + $totalQueryBuilder->setParameter("manufacturer", $manufacturer); + } + + $totalQuery = $totalQueryBuilder->getQuery(); + + return array("data" => $result, "totalCount" => $totalQuery->getSingleScalarResult()); + } + + /** + * Returns a manufacturer ic logo by id + * @param int $id The manufacturer ic logo id + */ + public function getManufacturerICLogo ($id) { + return ManufacturerICLogo::loadById($id); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/ManufacturerICLogo/ManufacturerICLogoService.php b/src/backend/PartKeepr/ManufacturerICLogo/ManufacturerICLogoService.php @@ -0,0 +1,101 @@ +<?php +namespace PartKeepr\ManufacturerICLogo; + +use PartKeepr\Manufacturer\ManufacturerICLogo, + PartKeepr\TempImage\TempImage, + PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\PartKeepr, + PartKeepr\Manufacturer\Manufacturer, + PartKeepr\Session\SessionManager; + +class ManufacturerICLogoService extends Service implements RestfulService { + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::get() + */ + public function get () { + if ($this->hasParameter("id")) { + return ManufacturerICLogoManager::getInstance()->getManufacturerICLogo($this->getParameter("id"))->serialize(); + } else { + if ($this->hasParameter("sort")) { + $tmp = json_decode($this->getParameter("sort"), true); + + $aSortParams = $tmp[0]; + } else { + $aSortParams = array( + "property" => "id", + "direction" => "ASC"); + } + + $filter = ""; + + if ($this->hasParameter("filter")) { + $tmp = json_decode($this->getParameter("filter"), true); + + foreach ($tmp as $item) { + if (array_key_exists("property", $item)) { + if ($item["property"] == "manufacturer_id") { + if (array_key_exists("value", $item)) { + $filter = $item["value"]; + } + } + } + } + } + return ManufacturerICLogoManager::getInstance()->getManufacturerICLogos( + $this->getParameter("start", $this->getParameter("start", 0)), + $this->getParameter("limit", $this->getParameter("limit", 25)), + $this->getParameter("sortby", $aSortParams["property"]), + $this->getParameter("dir", $aSortParams["direction"]), + $filter); + } + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::create() + */ + public function create () { + $this->requireParameter("tmp_id"); + $this->requireParameter("manufacturer_id"); + + $tmpImage = TempImage::loadById($this->getParameter("tmp_id")); + + $image = new ManufacturerICLogo(); + + $manufacturer = Manufacturer::loadById($this->getParameter("manufacturer_id")); + + $image->setManufacturer($manufacturer); + $image->replace($tmpImage->getFilename()); + $image->setOriginalFilename($tmpImage->getOriginalFilename()); + PartKeepr::getEM()->persist($image); + PartKeepr::getEM()->flush(); + + return $image->serialize(); + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::update() + */ + public function update () { + + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::destroy() + */ + public function destroy () { + $this->requireParameter("id"); + + $logo = ManufacturerICLogo::loadById($this->getParameter("id")); + + PartKeepr::getEM()->remove($logo); + PartKeepr::getEM()->flush(); + + return array("data" => null); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Part/Exceptions/CategoryNotAssignedException.php b/src/backend/PartKeepr/Part/Exceptions/CategoryNotAssignedException.php @@ -0,0 +1,20 @@ +<?php +namespace PartKeepr\Part\Exceptions; + +use PartKeepr\PartKeepr, + PartKeepr\Util\SerializableException, + PartKeepr\Part\Part; + +/** + * This exception is thrown when a part hasn't got a category assigned + */ +class CategoryNotAssignedException extends SerializableException { + + /** + * Constructs the exception + * @param BaseEntity $entity + */ + public function __construct (Part $part) { + parent::__construct(PartKeepr::i18n("Part %s has no category assigned", $part)); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Part/Exceptions/StorageLocationNotAssignedException.php b/src/backend/PartKeepr/Part/Exceptions/StorageLocationNotAssignedException.php @@ -0,0 +1,19 @@ +<?php +namespace PartKeepr\Part\Exceptions; + +use PartKeepr\PartKeepr, + PartKeepr\Util\SerializableException; + +/** + * This exception is thrown when a part hasn't got a storage location assigned + */ +class StorageLocationNotAssignedException extends SerializableException { + + /** + * Constructs the exception + * @param BaseEntity $entity + */ + public function __construct () { + parent::__construct(PartKeepr::i18n("No storage location assigned")); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Part/Part.php b/src/backend/PartKeepr/Part/Part.php @@ -0,0 +1,640 @@ +<?php +namespace PartKeepr\Part; + +use PartKeepr\StorageLocation\StorageLocation, + PartKeepr\Footprint\Footprint, + PartKeepr\PartCategory\PartCategoryManager, + PartKeepr\Util\Deserializable, + PartKeepr\PartCategory\PartCategory, + PartKeepr\Util\Serializable, + PartKeepr\Util\BaseEntity, + PartKeepr\PartKeepr, + PartKeepr\Part\Exceptions\CategoryNotAssignedException, + PartKeepr\Util\Exceptions\OutOfRangeException, + PartKeepr\Part\Exceptions\StorageLocationNotAssignedException; + + +/** + * Represents a part in the database. The heart of our project. Handle with care! + * + * @Entity @HasLifecycleCallbacks + */ +class Part extends BaseEntity implements Serializable, Deserializable { + /** + * The category of the part + * @ManyToOne(targetEntity="PartKeepr\PartCategory\PartCategory") + * @var Category + */ + private $category; + + /** + * The part's name + * @Column + * @var string + */ + private $name; + + /** + * The part's short description + * @Column(type="string",nullable=true) + * @var string + */ + private $description; + + /** + * The footprint of this part + * @ManyToOne(targetEntity="PartKeepr\Footprint\Footprint") + * @var Footprint + */ + private $footprint; + + /** + * The unit in which the part's "amount" is calculated. This is necessary to count parts + * in "pieces", "meters" or "grams". + * @ManyToOne(targetEntity="PartKeepr\Part\PartUnit") + * @var PartUnit + */ + private $partUnit; + + /** + * Defines the storage location of this part + * @ManyToOne(targetEntity="PartKeepr\StorageLocation\StorageLocation") + * @var StorageLocation + */ + private $storageLocation; + + /** + * Holds the manufacturers which can manufacture this part + * @OneToMany(targetEntity="PartKeepr\Part\PartManufacturer",mappedBy="part",cascade={"persist", "remove"}) + * @var ArrayCollection + */ + private $manufacturers; + + /** + * Holds the distributors from where we can buy the part + * @OneToMany(targetEntity="PartKeepr\Part\PartDistributor",mappedBy="part",cascade={"persist", "remove"}) + * @var ArrayCollection + */ + private $distributors; + + /** + * Holds the part images + * @OneToMany(targetEntity="PartKeepr\Part\PartImage",mappedBy="part",cascade={"persist", "remove"}) + * @var PartImage + */ + private $images; + + /** + * Holds the part attachments + * @OneToMany(targetEntity="PartKeepr\Part\PartAttachment",mappedBy="part",cascade={"persist", "remove"}) + * @var PartAttachment + */ + private $attachments; + + /** + * The comment for this part + * @Column(type="text") + */ + private $comment = ""; + + /** + * The stock level. Note that this is a cached value, because it makes our summary queries easier. + * @todo It would be nice if we could get rid of that. + * @Column(type="integer") + * @var integer + */ + private $stockLevel = 0; + + /** + * The minimum stock level for this part. If we run out of this part (e.g. stockLevel < minStockLevel), + * we can see that in the system and re-order parts. + * + * @Column(type="integer") + * @var integer + */ + private $minStockLevel = 0; + + /** + * The average price for the part. Note that this is a cached value. + * + * @todo It would be nice if we could get rid of that + * @Column(type="decimal",precision=13,scale=4,nullable=true) + * @var float + */ + private $averagePrice = null; + + /** + * The stock level history + * @OneToMany(targetEntity="PartKeepr\Stock\StockEntry",mappedBy="part",cascade={"persist", "remove"}) + * @var ArrayCollection + */ + private $stockLevels; + + /** + * The parameters for this part + * @OneToMany(targetEntity="PartKeepr\PartParameter\PartParameter",mappedBy="part",cascade={"persist", "remove"}) + * @var ArrayCollection + */ + private $parameters; + + /** + * The part status for this part + * @Column(type="string",nullable=true) + * @var string + */ + private $status; + + /** + * Defines if the part needs review + * @Column(type="boolean") + * @var boolean + */ + private $needsReview; + + /** + * The create date+time for this part + * @Column(type="datetime",nullable=true) + * @var \DateTime + */ + private $createDate; + + /** + * @OneToMany(targetEntity="PartKeepr\Project\Project", mappedBy="part") + **/ + private $projects; + + /** + * The internal part number + * @Column(type="string",nullable=true) + * @var string + */ + private $internalPartNumber; + + public function __construct () { + $this->distributors = new \Doctrine\Common\Collections\ArrayCollection(); + $this->manufacturers = new \Doctrine\Common\Collections\ArrayCollection(); + $this->parameters = new \Doctrine\Common\Collections\ArrayCollection(); + $this->images = new \Doctrine\Common\Collections\ArrayCollection(); + $this->attachments = new \Doctrine\Common\Collections\ArrayCollection(); + $this->setCreateDate(new \DateTime()); + $this->setReviewFlag(false); + } + + /** + * Sets the name for this part + * @param string $name The part's name + */ + public function setName ($name) { + $this->name = $name; + } + + /** + * Returns the name of this part + * @return string The part name + */ + public function getName () { + return $this->name; + } + + /** + * Sets the internal part number for this part + * @param string $partnumber + */ + public function setInternalPartNumber ($partNumber) { + $this->internalPartNumber = $partNumber; + } + + /** + * Returns the internal part number for this part + * @return string the internal part number + */ + public function getInternalPartNumber () { + return $this->internalPartNumber; + } + + /** + * Sets the description for this part + * @param string $description The part's short description + */ + public function setDescription ($description) { + $this->description = $description; + } + + /** + * Returns the short description of this part + * @return string The part description + */ + public function getDescription () { + return $this->description; + } + + /** + * Sets the part unit + * + * @param PartUnit $partUnit The part unit object to set + * @return nothing + */ + public function setPartUnit (PartUnit $partUnit) { + $this->partUnit = $partUnit; + } + + /** + * Returns the part unit + * + * @param none + * @return PartUnit The part unit object + */ + public function getPartUnit () { + return $this->partUnit; + } + + /** + * Sets the average price for this unit + * @todo Is this actually used? + * @param float $price The price to set + */ + public function setAveragePrice ($price) { + $this->averagePrice = $price; + } + + /** + * Updates the internal stock level from the stock history + */ + public function updateStockLevel () { + $this->stockLevel = $this->getStockLevel(); + } + + /** + * Sets the review flag + * @param boolean $bReview True if the part needs review, false otherwise + */ + public function setReviewFlag ($bReview) { + $this->needsReview = $bReview; + } + + /** + * Returns the review flag + * @return boolean True if the part needs review, false otherwise + */ + public function getReviewFlag () { + return $this->needsReview; + } + + /** + * Set the minimum stock level for this part + * + * Only positive values are allowed. + * + * @param int $minStockLevel A minimum stock level, only values >= 0 are allowed. + * @throws \PartKeepr\Util\Exceptions\OutOfRangeException If the passed stock level is not in range (>=0) + */ + public function setMinStockLevel ($minStockLevel) { + $minStockLevel = intval($minStockLevel); + + if ($minStockLevel < 0) { + $exception = new OutOfRangeException(PartKeepr::i18n("Minimum Stock Level is out of range")); + $exception->setDetail(PartKeepr::i18n("The minimum stock level must be 0 or higher")); + throw $exception; + } + $this->minStockLevel = $minStockLevel; + } + + /** + * Sets the category for this part + * @param \PartKeepr\PartCategory\PartCategory $category The category + */ + public function setCategory (PartCategory $category) { + $this->category = $category; + } + + /** + * Returns the assigned category + * @return \PartKeepr\PartCategory\PartCategory + */ + public function getCategory () { + return $this->category; + } + + /** + * Returns all projects this part is used + * @return ArrayCollection the projects + */ + public function getProjects () { + return $this->projects; + } + + /** + * Sets the storage location for this part + * @param \PartKeepr\StorageLocation\StorageLocation $storageLocation The storage location + */ + public function setStorageLocation (StorageLocation $storageLocation) { + $this->storageLocation = $storageLocation; + } + + /** + * Returns the storage location for this part + * @return \PartKeepr\StorageLocation\StorageLocation $storageLocation The storage location + */ + public function getStorageLocation () { + return $this->storageLocation; + } + + /** + * Sets the footprint for this part + * @param \PartKeepr\Footprint\Footprint $footprint The footprint to set + */ + public function setFootprint (Footprint $footprint = null) { + $this->footprint = $footprint; + } + + /** + * Sets the comment for this part + * @param string $comment The comment for this part + */ + public function setComment ($comment) { + $this->comment = $comment; + } + + /** + * Returns the comment for this part + * @return string The comment + */ + public function getComment () { + return $this->comment; + } + + /** + * Returns the distributors array + * @return ArrayCollection the distributors + */ + public function getDistributors () { + return $this->distributors; + } + + /** + * Returns the part images array + * @return ArrayCollection the part images + */ + public function getImages () { + return $this->images; + } + + /** + * Returns the part attachments array + * @return ArrayCollection the part attachments + */ + public function getAttachments () { + return $this->attachments; + } + + /** + * Returns the manufacturers array + * @return ArrayCollection the manufactuers + */ + public function getManufacturers () { + return $this->manufacturers; + } + + /** + * Returns the parameters assigned to this part + * @return array An array of PartParameter objects + */ + public function getParameters () { + return $this->parameters; + } + + /** + * Returns the stock level of this part. This is a realtime function which + * actually creates a query over the StockEntry table. + * @return int The stock level + */ + public function getStockLevel () { + $query = PartKeepr::getEM()->createQuery("SELECT SUM(s.stockLevel) FROM PartKeepr\Stock\StockEntry s WHERE s.part = :part"); + $query->setParameter("part", $this); + + return $query->getSingleScalarResult(); + } + + /** + * Sets the create date for this part + * @param \DateTime $dateTime The create date+time + */ + private function setCreateDate (\DateTime $dateTime) { + $this->createDate = $dateTime; + } + + /** + * Returns the create date + * @return \DateTime The create date+time + */ + public function getCreateDate () { + return $this->createDate; + } + + /** + * Sets the status for this part. A status is any string describing the status, + * e.g. "new", "used", "broken" etc. + * @param string $status The status + */ + public function setStatus ($status) { + $this->status = $status; + } + + /** + * Returns the status for this part. + * @return string The status + */ + public function getStatus () { + return $this->status; + } + + public function updateCacheData () { + $this->updateStockLevel(); + $this->updatePrice(); + } + + /** + * Updates the average price for a part + */ + public function updatePrice () { + $query = PartKeepr::getEM()->createQuery("SELECT SUM(se.price*se.stockLevel) / SUM(se.stockLevel) FROM PartKeepr\Stock\StockEntry se WHERE se.part = :part AND se.stockLevel > 0"); + $query->setParameter("part", $this); + $val = $query->getSingleScalarResult(); + + $query = PartKeepr::getEM()->createQuery('UPDATE PartKeepr\Part\Part p SET p.averagePrice = :val WHERE p = :part'); + $query->setParameter("val", $val); + $query->setParameter("part", $this); + $query->execute(); + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Util.Serializable::serialize() + */ + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "description" => $this->getDescription(), + "comment" => $this->getComment(), + "stockLevel" => $this->getStockLevel(), + "footprint" => is_object($this->footprint) ? $this->footprint->getId() : null, + "minStockLevel" => $this->minStockLevel, + "status" => $this->getStatus(), + "storageLocation" => is_object($this->storageLocation) ? $this->storageLocation->getId() : null, + "category" => is_object($this->category) ? $this->category->getId() : null, + "partUnit" => is_object($this->partUnit) ? $this->getPartUnit()->getId() : null, + "manufacturers" => $this->serializeChildren($this->getManufacturers()), + "distributors" => $this->serializeChildren($this->getDistributors()), + "images" => $this->serializeChildren($this->getImages()), + "attachments" => $this->serializeChildren($this->getAttachments()), + "parameters" => $this->serializeChildren($this->getParameters()), + "createDate" => $this->getCreateDate()->format("Y-m-d H:i:s"), + "needsReview" => $this->getReviewFlag(), + "internalPartNumber" => $this->getInternalPartNumber(), + // Additional things we serialize to make displaying stuff in the frontend easier + "categoryName" => is_object($this->category) ? $this->category->getName() : null, + "footprintName" => is_object($this->footprint) ? $this->footprint->getName() : null, + "storageLocationName" => is_object($this->storageLocation) ? $this->storageLocation->getName() : null + ); + } + + /** + * Deserializes the part + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "name": + $this->setName($value); + break; + case "description": + $this->setDescription($value); + break; + case "comment": + $this->setComment($value); + break; + case "internalPartNumber": + $this->setInternalPartNumber($value); + break; + case "footprint": + if ($value === 0) { + $this->setFootprint(null); + } else { + try { + $footprint = Footprint::loadById($value); + $this->setFootprint($footprint); + } catch (\Exception $e) { + // No footprint was found. Ignore it. + } + } + break; + case "minStockLevel": + $this->setMinStockLevel($value); + break; + case "partUnit": + $partUnit = PartUnit::loadById($value); + $this->setPartUnit($partUnit); + break; + case "category": + $category = PartCategory::loadById($value); + $this->setCategory($category); + break; + case "status": + $this->setStatus($value); + break; + case "storageLocation": + $storageLocation = StorageLocation::loadById($value); + $this->setStorageLocation($storageLocation); + break; + case "manufacturers": + $this->deserializeChildren($value, $this->getManufacturers(), "PartKeepr\Part\PartManufacturer"); + foreach ($this->getManufacturers() as $manufacturer) { + $manufacturer->setPart($this); + } + break; + case "distributors": + $this->deserializeChildren($value, $this->getDistributors(), "PartKeepr\Part\PartDistributor"); + foreach ($this->getDistributors() as $distributor) { + $distributor->setPart($this); + } + break; + case "parameters": + $this->deserializeChildren($value, $this->getParameters(), "PartKeepr\PartParameter\PartParameter"); + foreach ($this->getParameters() as $parameter) { + $parameter->setPart($this); + } + break; + case "needsReview": + $this->setReviewFlag($value); + break; + case "attachments": + $this->deserializeChildren($value, $this->getAttachments(), "PartKeepr\Part\PartAttachment"); + foreach ($this->getAttachments() as $attachment) { + $attachment->setPart($this); + } + break; + } + } + } + + /** + * Checks if the part category is set. + * + * @throws CategoryNotAssignedException + */ + private function checkCategoryConsistency () { + if ($this->getCategory() === null) { + throw new CategoryNotAssignedException($this); + } + } + + /** + * Checks if the part storage location is set. + * + * @throws StorageLocationNotAssignedException + */ + private function checkStorageLocationConsistency () { + if ($this->getStorageLocation() === null) { + throw new StorageLocationNotAssignedException(); + } + } + + /** + * Checks if the requirements for database persisting are given. + * + * @throws CategoryNotAssignedException Thrown if no category is set + * @throws StorageLocationNotAssignedException Thrown if no storage location is set + * + * @PrePersist + */ + public function onPrePersist () { + $this->checkCategoryConsistency(); + $this->checkStorageLocationConsistency(); + } + + /** + * + * Checks if the requirements for database persisting are given. + * + * For a list of exceptions, see + * @see PartKeepr\Part.Part::onPrePersist() + * + * @PreUpdate */ + public function onPreUpdate () { + $this->checkCategoryConsistency(); + $this->checkStorageLocationConsistency(); + } + + /** + * Returns a string representation of the part + * + * @param none + * @return string The name and the ID of the part + */ + public function __toString () { + return $this->getName() . " (".$this->getId().")"; + } + +} diff --git a/src/backend/PartKeepr/Part/PartAttachment.php b/src/backend/PartKeepr/Part/PartAttachment.php @@ -0,0 +1,129 @@ +<?php +namespace PartKeepr\Part; + +use PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable, + PartKeepr\UploadedFile\UploadedFile; + +/** + * Holds a part attachment + * @Entity + **/ +class PartAttachment extends UploadedFile implements Serializable, Deserializable { + /** + * The description of this attachment + * @Column(type="text") + * @var string + */ + private $description; + + /** + * Creates a new part attachment + */ + public function __construct () { + parent::__construct(); + $this->setType("PartAttachment"); + } + + /** + * The part object + * @ManyToOne(targetEntity="PartKeepr\Part\Part") + * @var Part + */ + private $part = null; + + /** + * Sets the part + * @param Part $part The part to set + */ + public function setPart (Part $part) { + $this->part = $part; + } + + /** + * Returns the part + * @return Part the part + */ + public function getPart () { + return $this->part; + } + + /** + * Sets the description for this attachment + * @param string $description The attachment description + */ + public function setDescription ($description) { + $this->description = $description; + } + + /** + * Returns the description for this attachment + * @return string The description + */ + public function getDescription () { + return $this->description; + } + + /** + * + * Serializes this part attachment + * @return array The serialized part attachment + */ + public function serialize () { + return array( + "id" => $this->getId(), + "part_id" => $this->getPart()->getId(), + "originalFilename" => $this->getOriginalFilename(), + "mimetype" => $this->getMimetype(), + "extension" => $this->getExtension(), + "size" => $this->getSize(), + "description" => $this->getDescription(), + "image" => $this->isImage()); + } + + /** + * Deserializes the part attachment + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + if (array_key_exists("id", $parameters)) { + if (substr($parameters["id"], 0, 4) === "TMP:") { + $this->replaceFromTemporaryFile($parameters["id"]); + } + } + + foreach ($parameters as $key => $value) { + switch ($key) { + case "description": + $this->setDescription($value); + break; + } + } + } + + /** + * Returns if the attachment is an image or not. + * + * Ths method uses ImageMagick to find out if this is an image. Limitations apply; if ImageMagick doesn't support + * the image format, this method would return false, even if it is an image. + * + * @return True if the attachment is an image, false otherwise + */ + public function isImage () { + /** + * Special case: Check if it's a PDF. If yes, return immediately. + * This is because ImageMagick outputs warning messages for malformed PDF files, and halts the execution + * of the script for several seconds. DO NOT REMOVE! + */ + if ($this->getMimeType() == "application/pdf") { + return false; + } + + try { + $im = new \Imagick($this->getFilename()); + return true; + } catch (\ImagickException $e) { + return false; + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Part/PartDistributor.php b/src/backend/PartKeepr/Part/PartDistributor.php @@ -0,0 +1,220 @@ +<?php +namespace PartKeepr\Part; + +use PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable, + PartKeepr\Util\BaseEntity, + PartKeepr\PartKeepr, + PartKeepr\Distributor\Distributor; + +/** + * This class represents the link between a part and a distributor. + * @Entity **/ +class PartDistributor extends BaseEntity implements Serializable, Deserializable { + /** + * @ManyToOne(targetEntity="PartKeepr\Part\Part") + */ + private $part; + + /** + * @ManyToOne(targetEntity="PartKeepr\Distributor\Distributor") + */ + private $distributor; + + /** + * The order number for the part and distributor + * @Column(type="string",nullable=true) + * @var string + */ + private $orderNumber; + + /** + * Defines the packaging unit when ordering a part. Some items can't be ordered in a quantity of just one at + * certain manufacturers. + * + * @Column(type="integer") + * @var integer + */ + private $packagingUnit; + + /** + * Specifies the price of the part. Note that the price + * needs to be per item, not per packaging unit. + * + * @Column(type="decimal",precision=13,scale=4,nullable=true) + * @var float + */ + private $price; + + /** + * The distributor's SKU (stock keeping unit) for the part. Used with barcodes. + * @Column(type="string",nullable=true) + * @var string + */ + private $sku; + + /** + * Cretes a new part->distributor link. Initializes the packaging unit with a quantity of "1". + * + * @param Part $part The part + * @param Distributor $distributor The distributor + */ + public function __construct () { + $this->setPackagingUnit(1); + } + + /** + * Sets the packaging unit for a specific distributor. + * + * For example, some distributors only sell resistors in packs of 100, so you can't order just one. We use the + * packagingUnit to calculate how many pieces will be delivered once ordered. So if your stock level falls below + * the minimum (example: you would need to order 10 resistors), we suggest that you only order one resistor pack + * instead of 10. + * + * @param int $packagingUnit The amount of items in one package + * @throws \PartKeepr\Part\OutOfRangeException When the packaging unit is less than 1 + */ + public function setPackagingUnit ($packagingUnit) { + $packagingUnit = intval($packagingUnit); + + if ($packagingUnit < 1) { + $exception = new OutOfRangeException(PartKeepr::i18n("Packaging Unit is out of range")); + $exception->setDetail(PartKeepr::i18n("The packaging unit must be 1 or higher")); + throw $exception; + } + + $this->packagingUnit = $packagingUnit; + } + + /** + * Returns the packaging unit + * @return int The packaging unit + */ + public function getPackagingUnit () { + return $this->packagingUnit; + } + + /** + * Sets the part + * @param Part $part The part + */ + public function setPart (Part $part) { + $this->part = $part; + } + + /** + * Returns the part + * @return Part The part + */ + public function getPart () { + return $this->part; + } + + /** + * Sets the distributor + * @param Distributor $distributor The distributor + */ + public function setDistributor (Distributor $distributor) { + $this->distributor = $distributor; + } + + /** + * Returns the distributor + * @return Distributor The distributor + */ + public function getDistributor () { + return $this->distributor; + } + + /** + * Sets the order number + * @param string $orderNumber The order number + */ + public function setOrderNumber ($orderNumber) { + $this->orderNumber = $orderNumber; + } + + /** + * Returns the order number + * @return string The order number + */ + public function getOrderNumber () { + return $this->orderNumber; + } + + /** + * Sets the price + * @param float $price + */ + public function setPrice ($price) { + echo "/** price set to ".$price." **/"; + $this->price = $price; + } + + /** + * Returns the price + */ + public function getPrice () { + return $this->price; + } + + /** + * Sets the SKU (stock keeping unit) + * @param string $sku The SKU + */ + public function setSKU ($sku) { + $this->sku = $sku; + } + + /** + * Returns the SKU (stock keeping unit) + * @return string The SKU + */ + public function getSKU () { + return $this->sku; + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Util.Serializable::serialize() + */ + public function serialize () { + return array( + "id" => $this->getId(), + "orderNumber" => $this->getOrderNumber(), + "distributor_id" => $this->getDistributor()->getId(), + "distributor_name" => $this->getDistributor()->getName(), + "part_id" => $this->getPart()->getId(), + "part_name" => $this->getPart()->getName(), + "packagingUnit" => $this->getPackagingUnit(), + "price" => $this->getPrice(), + "sku" => $this->getSKU()); + } + + /** + * Deserializes the part manufacturer + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "distributor_id": + $distributor = Distributor::loadById($value); + $this->setDistributor($distributor); + break; + case "orderNumber": + $this->setOrderNumber($value); + break; + case "packagingUnit": + $this->setPackagingUnit($value); + break; + case "price": + $this->setPrice($value); + break; + case "sku": + $this->setSKU($value); + break; + } + } + } +} diff --git a/src/backend/PartKeepr/Part/PartFulltextSearch.php b/src/backend/PartKeepr/Part/PartFulltextSearch.php @@ -0,0 +1,29 @@ +<?php +namespace PartKeepr\Part; + +use PartKeepr\FulltextSearch\FulltextSearch; + +/** + * Implements the part fulltext search + */ +class PartFulltextSearch extends FulltextSearch { + /** + * Returns the FQDN of the part entity + * + * (non-PHPdoc) + * @see PartKeepr\FulltextSearch.FulltextSearch::getEntityName() + */ + protected function getEntityName () { + return 'PartKeepr\Part\Part'; + } + + /** + * Returns the fields to be searched in + * + * (non-PHPdoc) + * @see PartKeepr\FulltextSearch.FulltextSearch::getFields() + */ + protected function getFields () { + return array("comment", "name", "description", "internalPartNumber"); + } +} diff --git a/src/backend/PartKeepr/Part/PartImage.php b/src/backend/PartKeepr/Part/PartImage.php @@ -0,0 +1,50 @@ +<?php +namespace PartKeepr\Part; + +use PartKeepr\Util\Serializable, + PartKeepr\Image\Image; + +/** + * Holds a part image + * @Entity + **/ +class PartImage extends Image implements Serializable { + /** + * The part object + * @ManyToOne(targetEntity="PartKeepr\Part\Part") + * @var Part + */ + private $part = null; + + /** + * Creates a new part image instance + */ + public function __construct () { + parent::__construct(Image::IMAGE_PART); + } + + /** + * Sets the part + * @param Part $part The part to set + */ + public function setPart (Part $part) { + $this->part = $part; + } + + /** + * Returns the part + * @return Part the part + */ + public function getPart () { + return $this->part; + } + + /** + * + * Serializes this part image + * @return array The serialized part imaage + */ + public function serialize () { + return array("id" => $this->getId(), "part_id" => $this->getPart()->getId()); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Part/PartManager.php b/src/backend/PartKeepr/Part/PartManager.php @@ -0,0 +1,441 @@ +<?php +namespace PartKeepr\Part; + +use PartKeepr\Logger\Logger; + +use PartKeepr\UploadedFile\TempUploadedFile, + PartKeepr\Manager\ManagerFilter, + Doctrine\ORM\QueryBuilder, + PartKeepr\PartParameter\PartParameter, + PartKeepr\Manager\AbstractManager, + PartKeepr\Unit\Unit, + PartKeepr\SiPrefix\SiPrefix, + PartKeepr\Part\PartDistributor, + PartKeepr\Part\PartManufacturer, + PartKeepr\StorageLocation\StorageLocation, + PartKeepr\StorageLocation\StorageLocationManager, + PartKeepr\Part\Part, + Doctrine\ORM\Query, + PartKeepr\PartUnit\PartUnitManager, + PartKeepr\Distributor\Distributor, + PartKeepr\Manufacturer\Manufacturer, + PartKeepr\Footprint\FootprintManager, + PartKeepr\Session\SessionManager, + PartKeepr\Stock\StockEntry, + PartKeepr\Util\Singleton, + PartKeepr\Footprint\Footprint, + PartKeepr\PartKeepr, + PartKeepr\PartCategory\PartCategoryManager, + PartKeepr\Manufacturer\ManufacturerManager; + +class PartManager extends AbstractManager { + + /** + * Returns the FQCN for the target entity to operate on. + * @return string The FQCN, e.g. PartKeepr\Part + */ + public function getEntityName () { + return 'PartKeepr\Part\Part'; + } + + /** + * Returns all fields which need to appear in the getList ResultSet. + * @return array An array of all fields which should be returned + */ + public function getQueryFields () { + return array("name", "description", "averagePrice", "status", "needsReview", "createDate", "id", "stockLevel", + "minStockLevel", "comment", "st.id AS storageLocation_id", "c.categoryPath AS categoryPath", + "st.name as storageLocationName", "f.id AS footprint_id", "f.name AS footprintName", + "c.id AS category", "c.name AS categoryName", "pu.id AS partUnit", "pu.name AS partUnitName", + "pu.is_default AS partUnitDefault" + ); + } + + /** + * Returns the default sort field + * + * @return string The default sort field + */ + public function getDefaultSortField () { + return "dateTime"; + } + + /** + * Appends various join tables to the result set + * + * (non-PHPdoc) + * @see PartKeepr\Manager.AbstractManager::applyCustomQuery() + */ + protected function applyCustomQuery (QueryBuilder $qb, ManagerFilter $filter) { + /** + * Pull in additional tables + */ + $qb ->join("q.storageLocation", "st") + ->leftJoin("q.footprint", "f") + ->join("q.category", "c") + ->leftJoin("q.partUnit", "pu"); + + // Apply special handling for non-direct fields in relations, where the frontend has no idea about. + foreach ($filter->getSorters() as $sorter) { + switch ($sorter->getSortField()) { + case "q.categoryPath": + $sorter->setSortField("c.categoryPath"); + break; + case "q.storageLocationName": + $sorter->setSortField("st.name"); + break; + case "q.footprintName": + $sorter->setSortField("f.name"); + break; + default: + break; + } + } + + } + + /** + * Processes the result after it was retrieved. In the default configuration, it returns an array result, or + * if no query fields are specified, it tries to serialize all objects. + */ + protected function getResult (Query $query) { + $result = parent::getResult($query); + + /* Add attachment counts to the result set and re-format the date */ + foreach ($result as $key => $item) { + $dql = "SELECT COUNT(pa) FROM PartKeepr\Part\PartAttachment pa WHERE pa.part = :part"; + $query = PartKeepr::getEM()->createQuery($dql); + $query->setParameter("part", $item["id"]); + + $result[$key]["attachmentCount"] = $query->getSingleScalarResult(); + + $result[$key]["createDate"] = $result[$key]["createDate"]->format("Y-m-d H:i:s"); + } + + foreach ($result as $key => $item) { + $dql = "SELECT pr.name FROM PartKeepr\Project\Project pr JOIN pr.parts ppart WHERE ppart.part = :part"; + + $query = PartKeepr::getEM()->createQuery($dql); + $query->setParameter("part", $item["id"]); + + $projectNames = array(); + foreach ($query->getArrayResult() as $project) { + $projectNames[] = $project["name"]; + } + + $result[$key]["projects"] = implode(", ", $projectNames); + + } + + foreach ($result as $key => $item) { + $part = Part::loadById($item["id"]); + $result[$key]["attachments"] = $part->serializeChildren($part->getAttachments()); + } + + return $result; + } + + public function addOrUpdatePart ($aParameters) { + + if (!array_key_exists("quantity", $aParameters)) { + $aParameters["quantity"] = 0; + } + + if ($aParameters["part"] !== null) { + try { + $part = $this->getPart($aParameters["part"]); + } catch (\Exception $e) { + $part = new Part(); + $user = SessionManager::getCurrentSession()->getUser(); + + $stock = new StockEntry($part, $aParameters["quantity"], $user); + PartKeepr::getEM()->persist($stock); + } + } else { + $part = new Part(); + + $user = SessionManager::getCurrentSession()->getUser(); + + $stock = new StockEntry($part, $aParameters["quantity"], $user); + PartKeepr::getEM()->persist($stock); + } + + if (array_key_exists("name", $aParameters)) { + $part->setName($aParameters["name"]); + } + + if (array_key_exists("description", $aParameters)) { + $part->setDescription($aParameters["description"]); + } + + if (array_key_exists("minstock", $aParameters)) { + $part->setMinStockLevel($aParameters["minstock"]); + } + + if (array_key_exists("comment", $aParameters)) { + $part->setComment($aParameters["comment"]); + } + + if (array_key_exists("footprint", $aParameters)) { + + if ($aParameters["footprint"] === null) { + $part->setFootprint(null); + } else { + $footprint = FootprintManager::getInstance()->getOrCreateFootprint($aParameters["footprint"]); + $part->setFootprint($footprint); + } + } + + if (array_key_exists("storagelocation", $aParameters)) { + $storageLocation = StorageLocationManager::getInstance()->getOrCreateStorageLocation($aParameters["storagelocation"]); + $part->setStorageLocation($storageLocation); + } + + if (array_key_exists("category", $aParameters)) { + $category = PartCategoryManager::getInstance()->getCategory($aParameters["category"]); + $part->setCategory($category->getNode()); + } + + /* Process linked changes */ + if (array_key_exists("distributorChanges", $aParameters)) { + if (is_array($aParameters["distributorChanges"])) { + $this->processDistributorChanges($part, $aParameters["distributorChanges"]); + } + } + + if (array_key_exists("manufacturerChanges", $aParameters)) { + if (is_array($aParameters["manufacturerChanges"])) { + $this->processManufacturerChanges($part, $aParameters["manufacturerChanges"]); + } + } + + if (array_key_exists("parameterChanges", $aParameters)) { + if (is_array($aParameters["parameterChanges"])) { + $this->processParameterChanges($part, $aParameters["parameterChanges"]); + } + } + + if (array_key_exists("attachmentChanges", $aParameters)) { + if (is_array($aParameters["attachmentChanges"])) { + $this->processAttachmentChanges($part, $aParameters["attachmentChanges"]); + } + } + + if (array_key_exists("partUnit", $aParameters)) { + if ($aParameters["partUnit"] === null || $aParameters["partUnit"] === 0) { + $part->setPartUnit(null); + } else { + $part->setPartUnit(PartUnitManager::getInstance()->getPartUnit($aParameters["partUnit"])); + } + } + + + PartKeepr::getEM()->persist($part); + PartKeepr::getEM()->flush(); + + } + + private function processParameterChanges (Part $part, Array $data) { + if (array_key_exists("updates", $data)) { + foreach ($data["updates"] as $record) { + foreach ($part->getParameters() as $partParameter) { + if ($partParameter->getId() == $record["id"]) { + $partParameter->setName($record["name"]); + $partParameter->setDescription($record["description"]); + $partParameter->setValue($record["value"]); + $partParameter->setSiPrefix(SiPrefix::loadById($record["siprefix_id"])); + $partParameter->setUnit(Unit::loadById($record["unit_id"])); + break; + } + } + } + } + + if (array_key_exists("removals", $data)) { + foreach ($data["removals"] as $record) { + foreach ($part->getParameters() as $partParameter) { + if ($partParameter->getId() == $record["id"]) { + PartKeepr::getEM()->remove($partParameter); + $part->getParameters()->removeElement($partParameter); + break; + } + } + } + } + + if (array_key_exists("inserts", $data)) { + foreach ($data["inserts"] as $record) { + $partParameter = new PartParameter(); + $partParameter->setPart($part); + + $partParameter->setName($record["name"]); + $partParameter->setDescription($record["description"]); + $partParameter->setValue($record["value"]); + $partParameter->setSiPrefix(SiPrefix::loadById($record["siprefix_id"])); + $partParameter->setUnit(Unit::loadById($record["unit_id"])); + + $part->getParameters()->add($partParameter); + } + } + } + + private function processDistributorChanges (Part $part, Array $data) { + if (array_key_exists("updates", $data)) { + foreach ($data["updates"] as $record) { + foreach ($part->getDistributors() as $partDistributor) { + if ($partDistributor->getId() == $record["id"]) { + $partDistributor->setOrderNumber($record["orderNumber"]); + $partDistributor->setDistributor(Distributor::loadById($record["distributor_id"])); + $partDistributor->setPackagingUnit($record["packagingUnit"]); + break; + } + } + } + } + + if (array_key_exists("removals", $data)) { + foreach ($data["removals"] as $record) { + foreach ($part->getDistributors() as $partDistributor) { + if ($partDistributor->getId() == $record["id"]) { + PartKeepr::getEM()->remove($partDistributor); + $part->getDistributors()->removeElement($partDistributor); + break; + } + } + } + } + + if (array_key_exists("inserts", $data)) { + foreach ($data["inserts"] as $record) { + $distributor = new PartDistributor($part, Distributor::loadById($record["distributor_id"])); + $distributor->setOrderNumber($record["orderNumber"]); + $distributor->setPackagingUnit($record["packagingUnit"]); + + $part->getDistributors()->add($distributor); + } + } + } + + private function processManufacturerChanges (Part $part, Array $data) { + if (array_key_exists("updates", $data)) { + foreach ($data["updates"] as $record) { + foreach ($part->getManufacturers() as $partManufacturer) { + if ($partManufacturer->getId() == $record["id"]) { + $partManufacturer->setPartNumber($record["partNumber"]); + $partManufacturer->setManufacturer(Manufacturer::loadById($record["manufacturer_id"])); + break; + } + } + } + } + + if (array_key_exists("removals", $data)) { + foreach ($data["removals"] as $record) { + foreach ($part->getManufacturers() as $partManufacturer) { + if ($partManufacturer->getId() == $record["id"]) { + PartKeepr::getEM()->remove($partManufacturer); + $part->getManufacturers()->removeElement($partManufacturer); + break; + } + } + } + } + + if (array_key_exists("inserts", $data)) { + foreach ($data["inserts"] as $record) { + $manufacturer = new PartManufacturer($part, Manufacturer::loadById($record["manufacturer_id"])); + $manufacturer->setPartNumber($record["partNumber"]); + + $part->getManufacturers()->add($manufacturer); + } + } + } + + private function processAttachmentChanges (Part $part, Array $data) { + if (array_key_exists("updates", $data)) { + foreach ($data["updates"] as $record) { + foreach ($part->getAttachments() as $partAttachment) { + if ($partAttachment->getId() == $record["id"]) { + $partAttachment->setDescription($record["description"]); + break; + } + } + } + } + + if (array_key_exists("removals", $data)) { + foreach ($data["removals"] as $record) { + foreach ($part->getAttachments() as $partAttachment) { + if ($partAttachment->getId() == $record["id"]) { + PartKeepr::getEM()->remove($partAttachment); + $part->getAttachments()->removeElement($partAttachment); + break; + } + } + } + } + + if (array_key_exists("inserts", $data)) { + foreach ($data["inserts"] as $record) { + $attachment = new PartAttachment(); + $attachment->setPart($part); + $attachment->setDescription($record["description"]); + + $file = TempUploadedFile::loadById($record["tmp_id"]); + + $attachment->replace($file->getFilename()); + $attachment->setOriginalFilename($file->getOriginalFilename()); + + $part->getAttachments()->add($attachment); + } + } + } + + public function deletePart ($id) { + $part = PartManager::getInstance()->getPart($id); + + PartKeepr::getEM()->remove($part); + PartKeepr::getEM()->flush(); + } + + public function getPart ($id) { + $part = PartKeepr::getEM()->find("PartKeepr\Part\Part", $id); + + return $part; + } + + /** + * Returns the overall part count currently existing. + * @param boolean $withPrice Only consider parts with a price + * @return int The amount of parts in the database + */ + public function getPartCount ($withPrice = false) { + $dql = "SELECT COUNT(p.id) FROM PartKeepr\Part\Part p"; + + if ($withPrice === true) { + $dql .= " WHERE p.averagePrice IS NOT NULL"; + } + + return PartKeepr::getEM()->createQuery($dql)->getSingleScalarResult(); + } + + /** + * Returns the total price for all parts. Only parts with a price are calculated. + * @return float The total price + */ + public function getTotalPrice () { + $dql = "SELECT SUM(p.averagePrice * p.stockLevel) FROM PartKeepr\Part\Part p"; + + return PartKeepr::getEM()->createQuery($dql)->getSingleScalarResult(); + } + + /** + * Returns the average price for all parts. Only parts with a price are calculated. + * @return float The average price + */ + public function getAveragePrice () { + $dql = "SELECT AVG(p.averagePrice) FROM PartKeepr\Part\Part p"; + + return PartKeepr::getEM()->createQuery($dql)->getSingleScalarResult(); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Part/PartManufacturer.php b/src/backend/PartKeepr/Part/PartManufacturer.php @@ -0,0 +1,106 @@ +<?php +namespace PartKeepr\Part; + +use PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable, + PartKeepr\Util\BaseEntity, + PartKeepr\PartKeepr, + PartKeepr\Manufacturer\Manufacturer; + +/** @Entity **/ +class PartManufacturer extends BaseEntity implements Serializable, Deserializable { + /** + * @ManyToOne(targetEntity="PartKeepr\Part\Part") + */ + private $part; + + /** + * @ManyToOne(targetEntity="PartKeepr\Manufacturer\Manufacturer") + */ + private $manufacturer; + + /** + * @Column(type="string",nullable=true) + * Enter description here ... + * @var unknown_type + */ + private $partNumber; + + /** + * Sets the part which belongs to this manufacturer entry + * @param Part $part + */ + public function setPart (Part $part) { + $this->part = $part; + } + + /** + * Returns the part which belongs to this manufacturer entry + */ + public function getPart () { + return $this->part; + } + + /** + * Sets the manufacturer which belongs to this entry + * @param Manufacturer $manufacturer + */ + public function setManufacturer (Manufacturer $manufacturer) { + $this->manufacturer = $manufacturer; + } + + /** + * Returns the manufacturer which belongs to this part + */ + public function getManufacturer () { + return $this->manufacturer; + } + + /** + * Sets the manufacturer-specific part number + * @param string $partNumber + */ + public function setPartNumber ($partNumber) { + $this->partNumber = $partNumber; + } + + /** + * Returns the manufacturer-specific part number + * @return string The part number + */ + public function getPartNumber () { + return $this->partNumber; + } + + /** + * Returns the data of this object in a serialized form. + * @return array The result array + */ + public function serialize () { + return array( + "id" => $this->getId(), + "partNumber" => $this->getPartNumber(), + "manufacturer_id" => $this->getManufacturer()->getId(), + "manufacturer_name" => $this->getManufacturer()->getName(), + "part_id" => $this->getPart()->getId(), + "part_name" => $this->getPart()->getName()); + } + + /** + * Deserializes the part manufacturer + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "manufacturer_id": + $manufacturer = Manufacturer::loadById($value); + $this->setManufacturer($manufacturer); + break; + case "partNumber": + $this->setPartNumber($value); + break; + } + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Part/PartService.php b/src/backend/PartKeepr/Part/PartService.php @@ -0,0 +1,262 @@ +<?php +namespace PartKeepr\Part; + +use PartKeepr\User\User, + PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\Manager\ManagerFilter, + PartKeepr\Part\PartManager, + PartKeepr\Stock\StockEntry, + PartKeepr\PartKeepr, + PartKeepr\PartCategory\PartCategory, + PartKeepr\PartCategory\PartCategoryManager, + PartKeepr\Session\SessionManager; + +class PartService extends Service implements RestfulService { + public function get () { + if ($this->hasParameter("id")) { + return array("data" => PartManager::getInstance()->getPart($this->getParameter("id"))->serialize()); + } else { + + $filter = new ManagerFilter($this); + $filter->setFilterCallback(array($this, "filterCallback")); + + return PartManager::getInstance()->getList($filter); + } + } + + /** + * Advanced filtering for the list + * @param QueryBuilder The $queryBuilder + */ + public function filterCallback ($queryBuilder) { + + /** + * Applies text-based filtering + */ + if ($this->hasParameter("query") && $this->getParameter("query") != "") { + + $fulltextSearch = new PartFulltextSearch($this->getParameter("query")); + $fulltextSearchResults = $fulltextSearch->query(); + + $queryBuilder->andWhere("q.id IN (".implode(",", $fulltextSearchResults).")"); + + } + + /** + * Applies filtering by the storage location name + */ + if ($this->getParameter("storageLocation") !== null) { + $queryBuilder->andWhere("st.name = :storageLocation"); + $queryBuilder->setParameter("storageLocation", $this->getParameter("storageLocation")); + } + + /** + * Filter by the category id and set the category mode + * + */ + $category = intval($this->getParameter("category", 0)); + + if ($category !== 0) { + /* Fetch all children */ + if ($this->getParameter("categoryScope") == "selected") { + $queryBuilder->andWhere("q.category = :category"); + $queryBuilder->setParameter("category", $category); + } else { + $childs = PartCategoryManager::getInstance()->getChildNodes($category); + $childs[] = $category; + $queryBuilder->andWhere("q.category IN (".implode(",", $childs).")"); + } + } + + /** + * Filter by the stock mode + */ + switch ($this->getParameter("stockMode")) { + case "all": + break; + case "zero": + $queryBuilder->andWhere("q.stockLevel = 0"); + break; + case "nonzero": + $queryBuilder->andWhere("q.stockLevel > 0"); + break; + case "below": + $queryBuilder->andWhere("q.stockLevel < q.minStockLevel"); + break; + } + + /** + * Query by the distributor's order number + */ + if ($this->getParameter("distributorOrderNumber")) { + $queryBuilder->leftJoin("q.distributors", "di"); + $queryBuilder->andWhere("LOWER(di.orderNumber) LIKE :orderNumber"); + $queryBuilder->setParameter("orderNumber", "%".strtolower($this->getParameter("distributorOrderNumber"))."%"); + } + + /** + * Filter by the price + */ + if ($this->getParameter("withoutPrice") === true || $this->getParameter("withoutPrice") === "true") { + $queryBuilder->andWhere("q.averagePrice IS NULL"); + } + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::create() + */ + public function create () { + $entity = PartManager::getInstance()->createEntity($this->getParameters()); + + if ($this->getParameter("initialStockLevel") > 0) { + try { + $user = User::loadById($this->getParameter("initialStockLevelUser")); + } catch (\Exception $e) { + $user = SessionManager::getCurrentSession()->getUser(); + } + + $stock = new StockEntry($entity, intval($this->getParameter("initialStockLevel")), $user); + + if ($this->getParameter("initialStockLevelPricePerItem") == true) { + $price = floatval($this->getParameter("initialStockLevelPrice")); + } else { + $price = floatval($this->getParameter("initialStockLevelPrice")) / $this->getParameter("initialStockLevel"); + } + + if ($price != 0) { + $stock->setPrice($price); + } + + PartKeepr::getEM()->persist($stock); + PartKeepr::getEM()->flush(); + + $entity->updateStockLevel(); + PartKeepr::getEM()->flush(); + } + + return array("data" => $entity->serialize()); + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::update() + */ + public function update () { + $entity = PartManager::getInstance()->getEntity($this->getParameter("id")); + $entity->deserialize($this->getParameters()); + + PartKeepr::getEM()->flush(); + + return array("data" => $entity->serialize()); + } + + + public function destroy () { + throw new \Exception("Not yet implemented"); + } + + public function getPartParameterNames () { + $dql = "SELECT pp.name FROM PartKeepr\PartParameter\PartParameter pp GROUP BY pp.name"; + $query = PartKeepr::getEM()->createQuery($dql); + + return array("data" => $query->getArrayResult()); + } + + public function movePart () { + $this->requireParameter("targetCategory"); + + if ($this->getParameter("parts", false) !== false) { + /* We are moving multiple parts */ + foreach ($this->getParameter("parts") as $part) { + $part = Part::loadById($part); + $category = PartCategory::loadById($this->getParameter("targetCategory")); + + $part->setCategory($category); + } + } else { + $part = Part::loadById($this->getParameter("part")); + $category = PartCategory::loadById($this->getParameter("targetCategory")); + + $part->setCategory($category); + + } + + PartKeepr::getEM()->flush(); + } + + public function addStock () { + $part = PartManager::getInstance()->getPart($this->getParameter("part")); + + $user = SessionManager::getCurrentSession()->getUser(); + + $stock = new StockEntry($part, intval($this->getParameter("stock")), $user); + + $price = floatval($this->getParameter("price")); + + if ($price != 0) { + $stock->setPrice($price); + } + + if ($this->hasParameter("comment")) { + $stock->setComment($this->getParameter("comment")); + } + + PartKeepr::getEM()->persist($stock); + PartKeepr::getEM()->flush(); + + $part->updateStockLevel(); + + PartKeepr::getEM()->flush(); + + return array("data" => $part->serialize()); + } + + public function deleteStock () { + $part = PartManager::getInstance()->getPart($this->getParameter("part")); + + $user = SessionManager::getCurrentSession()->getUser(); + + $stock = new StockEntry($part, 0-intval($this->getParameter("stock")), $user); + + PartKeepr::getEM()->persist($stock); + PartKeepr::getEM()->flush(); + + $part->updateStockLevel(); + + PartKeepr::getEM()->flush(); + + return array("data" => $part->serialize()); + } + + public function massDeleteStock () { + $data = $this->getParameter("removals"); + + $updateStockLevels = array(); + + foreach ($data as $item) { + $part = PartManager::getInstance()->getPart($item["part"]); + $user = SessionManager::getCurrentSession()->getUser(); + + $stock = new StockEntry($part, 0-intval($item["amount"]), $user); + $stock->setComment($item["comment"]); + PartKeepr::getEM()->persist($stock); + + $updateStockLevels[$item["part"]] = $part; + } + + PartKeepr::getEM()->flush(); + + foreach ($updateStockLevels as $part) { + $part->updateStockLevel(); + } + + PartKeepr::getEM()->flush(); + return array(); + } + + public function deletePart () { + PartManager::getInstance()->deletePart($this->getParameter("part")); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Part/PartUnit.php b/src/backend/PartKeepr/Part/PartUnit.php @@ -0,0 +1,137 @@ +<?php +namespace PartKeepr\Part; + +use PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable, + PartKeepr\Util\BaseEntity, + PartKeepr\PartKeepr, + PartKeepr\Util\Exceptions\OutOfRangeException; + + +/** @Entity **/ +class PartUnit extends BaseEntity implements Serializable, Deserializable { + /** + * Defines the name of the unit + * @Column + * @var string + */ + private $name; + + /** + * Defines the short name of the unit + * @Column + * @var string + */ + private $shortName; + + /** + * Defines if the unit is default or not. + * + * @Column(type="boolean") + * @var boolean + */ + private $is_default; + + /** + * @OneToMany(targetEntity="PartKeepr\Part\Part",mappedBy="partUnit") + */ + private $parts; + + + /** + * Creates a new part unit. + * + * Sets the default to false. + */ + public function __construct () { + $this->setDefault(false); + } + + /** + * Sets the name for this unit + * @param string $name The name for this unit + * @return nothing + */ + public function setName ($name) { + $this->name = $name; + } + + /** + * Returns the name for this unit + * @param none + * @return string The name for this unit + */ + public function getName () { + return $this->name; + } + + /** + * Sets the short name for this unit. + * + * Short names are used for list views (e.g. if your unit name is "metres", your short name could be "m") + * @param string $shortName The short name + * @return nothing + */ + public function setShortName ($shortName) { + $this->shortName = $shortName; + } + + /** + * Returns the short name for this unit + * @param none + * @return string The short name for this unit + */ + public function getShortName () { + return $this->shortName; + } + + /** + * Defines if the unit is default or not. + * @param boolean $default True if the unit is default, false otherwise + */ + public function setDefault ($default) { + $this->is_default = (bool)$default; + } + + /** + * Returns if the unit is default or not + * @param none + * @return boolean True if the unit is default, false for not + */ + public function getDefault () { + return $this->is_default; + } + + /** + * Serializes the object and returns it as array, suitable + * to process via json_encode. + * @param none + * @return array An array containing the object information + */ + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "shortName" => $this->getShortName(), + "default" => $this->getDefault() + ); + } + + /** + * Deserializes the manufacturer + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "name": + $this->setName($value); + break; + case "shortName": + $this->setShortName($value); + break; + } + } + } +} + + \ No newline at end of file diff --git a/src/backend/PartKeepr/PartCategory/PartCategory.php b/src/backend/PartKeepr/PartCategory/PartCategory.php @@ -0,0 +1,14 @@ +<?php +namespace PartKeepr\PartCategory; + +use PartKeepr\Category\AbstractCategory; + +/** + * @Entity + * @Table(indexes={@index(columns={"lft"}),@index(columns={"rgt"})}) + * The entity for our part categories + * + */ +class PartCategory extends AbstractCategory { + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/PartCategory/PartCategoryManager.php b/src/backend/PartKeepr/PartCategory/PartCategoryManager.php @@ -0,0 +1,42 @@ +<?php +namespace PartKeepr\PartCategory; + +use PartKeepr\Category\AbstractCategoryManager; +use DoctrineExtensions\NestedSet\NodeWrapper; +use PartKeepr\Util\SerializableException; +use PartKeepr\PartKeepr; + +class PartCategoryManager extends AbstractCategoryManager { + protected $categoryClass = "PartKeepr\PartCategory\PartCategory"; + + /** + * Deletes the given category ID. + * @param $id int The category id to delete + * @throws CategoryNotFoundException If the category wasn't found + */ + public function deleteCategory ($id) { + $category = $this->getCategory($id); + + try { + + if ($category->hasChildren()) { + $exception = new SerializableException(sprintf(PartKeepr::i18n("Category '%s' contains other categories."), $category->getNode()->getName())); + $exception->setDetail(sprintf(PartKeepr::i18n("You tried to delete the category '%s', but it still contains other categories. Please move the categories or delete them first."), $category->getNode()->getName())); + + throw $exception; + } + + parent::deleteCategory($id); + } catch (\PDOException $e) { + if ($e->getCode() == "23000") { + $exception = new SerializableException(sprintf(PartKeepr::i18n("Category '%s' contains parts."), $category->getNode()->getName())); + $exception->setDetail(sprintf(PartKeepr::i18n("You tried to delete the category '%s', but it still contains parts. Please move the parts to another category."), $category->getNode()->getName())); + + throw $exception; + } else { + throw $e; + } + } + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/PartCategory/PartCategoryService.php b/src/backend/PartKeepr/PartCategory/PartCategoryService.php @@ -0,0 +1,9 @@ +<?php +namespace PartKeepr\PartCategory; + +use PartKeepr\Category\AbstractCategoryService; + +class PartCategoryService extends AbstractCategoryService { + protected $categoryManagerClass = "PartKeepr\PartCategory\PartCategoryManager"; + protected $categoryClass = "PartKeepr\PartCategory\PartCategory"; +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/PartDistributor/PartDistributorManager.php b/src/backend/PartKeepr/PartDistributor/PartDistributorManager.php @@ -0,0 +1,74 @@ +<?php +namespace PartKeepr\PartDistributor; + +use PartKeepr\Util\Singleton, + PartKeepr\Manufacturer\Manufacturer, + PartKeepr\PartKeepr, + PartKeepr\Manufacturer\Exceptions\ManufacturerNotFoundException; + +class PartDistributorManager extends Singleton { + public function getPartDistributors ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { + + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select("pd.orderNumber, part.id AS part_id, dist.id AS distributor_id")->from("PartKeepr\Part\PartDistributor","pd") + ->leftJoin('pd.distributor', "dist") + ->leftJoin("pd.part", "part"); + + /*if ($filter != "") { + $manufacturer = Manufacturer::loadById($filter); + $qb = $qb->where("st.manufacturer = :manufacturer"); + $qb->setParameter("manufacturer", $manufacturer); + }*/ + + if ($limit > -1) { + $qb->setMaxResults($limit); + $qb->setFirstResult($start); + } + + $qb->orderBy("pd.".$sort, $dir); + + $query = $qb->getQuery(); + + $result = $query->getResult(); + + $totalQueryBuilder = PartKeepr::getEM()->createQueryBuilder(); + $totalQueryBuilder->select("COUNT(pd.id)")->from("PartKeepr\Part\PartDistributor","pd"); + + + /* + if ($filter != "") { + $totalQueryBuilder = $totalQueryBuilder->where("st.manufacturer = :manufacturer"); + $totalQueryBuilder->setParameter("manufacturer", $manufacturer); + }*/ + + $totalQuery = $totalQueryBuilder->getQuery(); + + return array("data" => $result, "totalCount" => $totalQuery->getSingleScalarResult()); + } + + public function getPartDistributor ($id) { + $partDistributor = PartKeepr::getEM()->find("PartKeepr\Part\PartDistributor", $id); + + if ($partDistributor) { + return $partDistributor; + } else { + throw new PartDistributorNotFoundException(); + } + } + + public function addPartDistributor ($orderNumber) { + $partDistributor = new PartDistributor(); + $partDistributor->setName($orderNumber); + + PartKeepr::getEM()->persist($partDistributor); + PartKeepr::getEM()->flush(); + + return $partDistributor; + } + public function deletePartDistributor ($id) { + $manufacturer = $this->getManufacturer($id); + + PartKeepr::getEM()->remove($manufacturer); + PartKeepr::getEM()->flush(); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/PartDistributor/PartDistributorService.php b/src/backend/PartKeepr/PartDistributor/PartDistributorService.php @@ -0,0 +1,84 @@ +<?php +namespace PartKeepr\ManufacturerICLogo; + +use PartKeepr\Manufacturer\ManufacturerICLogo, + PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\PartKeepr, + PartKeepr\Manufacturer\Manufacturer, + PartKeepr\Session\SessionManager; + +class PartDistributorService extends Service implements RestfulService { + public function get () { + if ($this->hasParameter("id")) { + return PartDistributorManager::getInstance()->getManufacturerICLogo($this->getParameter("id"))->serialize(); + } else { + if ($this->hasParameter("sort")) { + $tmp = json_decode($this->getParameter("sort"), true); + + $aSortParams = $tmp[0]; + } else { + $aSortParams = array( + "property" => "id", + "direction" => "ASC"); + } + + $filter = ""; + + if ($this->hasParameter("filter")) { + $tmp = json_decode($this->getParameter("filter"), true); + + foreach ($tmp as $item) { + if (array_key_exists("property", $item)) { + if ($item["property"] == "manufacturer_id") { + if (array_key_exists("value", $item)) { + $filter = $item["value"]; + } + } + } + } + } + // @todo This seems wrong?!? + return ManufacturerICLogoManager::getInstance()->getManufacturerICLogos( + $this->getParameter("start", $this->getParameter("start", 0)), + $this->getParameter("limit", $this->getParameter("limit", 25)), + $this->getParameter("sortby", $aSortParams["property"]), + $this->getParameter("dir", $aSortParams["direction"]), + $filter); + } + } + + public function create () { + $this->requireParameter("tmp_id"); + $this->requireParameter("manufacturer_id"); + + $tmpImage = TempImage::loadById($this->getParameter("tmp_id")); + + $image = new ManufacturerICLogo(); + + $manufacturer = Manufacturer::loadById($this->getParameter("manufacturer_id")); + + $image->setManufacturer($manufacturer); + $image->replace($tmpImage->getFilename()); + PartKeepr::getEM()->persist($image); + PartKeepr::getEM()->flush(); + + return $image->serialize(); + } + + public function update () { + + } + + public function destroy () { + $this->requireParameter("id"); + + $logo = ManufacturerICLogo::loadById($this->getParameter("id")); + + PartKeepr::getEM()->remove($logo); + PartKeepr::getEM()->flush(); + + return array("data" => null); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/PartKeepr.php b/src/backend/PartKeepr/PartKeepr.php @@ -0,0 +1,434 @@ +<?php +namespace PartKeepr; + +use Doctrine\Common\ClassLoader, + PartKeepr\SystemNotice\SystemNoticeManager, + Doctrine\ORM\Configuration, + Doctrine\ORM\EntityManager, + PartKeepr\Util\Configuration as PartKeeprConfiguration; + + + +class PartKeepr { + /** + * + * Contains the doctrine entity manager. + * @var Doctrine\ORM\EntityManager + */ + private static $entityManager = null; + + /** + * Initializes the PartKeepr system + * + * You *need* to call this method before doing anything else. + * + * An environment is used to load a different configuration file. + * Usually, you don't need to pass anything here. + * + * @param $environment string The environment to use, null otherwise. + * @return nothing + */ + public static function initialize ($environment = null) { + self::initializeClassLoaders(); + self::initializeConfig($environment); + self::initializeDoctrine(); + } + + /** + * Initializes the doctrine class loader and sets up the + * directories. + * + * @param none + * @return nothing + */ + public static function initializeClassLoaders() { + require_once 'Doctrine/Common/ClassLoader.php'; + + + $classLoader = new ClassLoader('PartKeepr', self::getRootDirectory() . "/src/backend"); + $classLoader->register(); + + $classLoader = new ClassLoader('Doctrine\ORM'); + $classLoader->register(); + + $classLoader = new ClassLoader("Doctrine\DBAL\Migrations", self::getRootDirectory() ."/3rdparty/doctrine-migrations/lib"); + $classLoader->register(); + + $classLoader = new ClassLoader('Doctrine\DBAL'); + $classLoader->register(); + + $classLoader = new ClassLoader('Doctrine\Common'); + $classLoader->register(); + + $classLoader = new ClassLoader('Symfony', 'Doctrine'); + $classLoader->register(); + + $classLoader = new ClassLoader("DoctrineExtensions\NestedSet", self::getRootDirectory() ."/3rdparty/doctrine2-nestedset/lib"); + $classLoader->register(); + + + } + + /** + * Returns an array of all cronjobs which are required for proper execution of PartKeepr. + * + * @return Array The filenames of each cronjob which is required + */ + public static function getRequiredCronjobs () { + return array( + "CreateStatisticSnapshot.php", + "UpdatePartCacheData.php", + "UpdateTipsOfTheDay.php", + "CheckForUpdates.php" + ); + } + + /** + * Initializes the configuration for a given environment. + * + * An environment is used to load a different configuration file. + * + * Usually, you don't need to pass anything here. + * + * + * @param $environment string The environment to use, null otherwise. + * @return nothing + */ + public static function initializeConfig ($environment = null) { + if ($environment != null) { + include(self::getRootDirectory()."/config-$environment.php"); + } else { + include(self::getRootDirectory()."/config.php"); + } + + // Check if the files path is set. If not, fall back to <partkeepr-root>/data/ + if (PartKeeprConfiguration::getOption("partkeepr.files.path", null) === null) { + + PartKeeprConfiguration::setOption("partkeepr.files.path", + PartKeepr::getRootDirectory() . "/data/"); + } + + // Check if the image path is set. If not, fall back to <configured-files-directory>/images/ + if (PartKeeprConfiguration::getOption("partkeepr.images.path", null) === null) { + + PartKeeprConfiguration::setOption("partkeepr.images.path", + PartKeeprConfiguration::getOption("partkeepr.files.path") . "images/"); + } + + // Check if the image cache path is set. If not, fall back to <configured-images-directory>/images/ + if (PartKeeprConfiguration::getOption("partkeepr.images.cache", null) === null) { + + PartKeeprConfiguration::setOption("partkeepr.images.cache", + PartKeeprConfiguration::getOption("partkeepr.images.path") . "cache/"); + + } + + } + + /** + * Checks against the versions at partkeepr.org. + * + * If a newer version was found, create a system notice entry. + */ + public static function doVersionCheck () { + + $data = file_get_contents("http://www.partkeepr.org/versions.json"); + $versions = json_decode($data, true); + + if (PartKeeprVersion::PARTKEEPR_VERSION == "{V_GIT}") { return; } + + if (version_compare(PartKeepr::getVersion(), $versions[0]["version"], '<')) { + + SystemNoticeManager::getInstance()->createUniqueSystemNotice( + "PARTKEEPR_VERSION_".$versions[0]["version"], + sprintf(PartKeepr::i18n("New PartKeepr Version %s available"), $versions[0]["version"]), + sprintf(PartKeepr::i18n("PartKeepr Version %s changelog:"), $versions[0]["version"]) . "\n\n". + $versions[0]["changelog"] + ); + + } + } + + /** + * Initializes the doctrine framework and + * sets all required configuration options. + * + * @param none + * @return nothing + */ + public static function initializeDoctrine () { + $config = new Configuration; + + $driverImpl = $config->newDefaultAnnotationDriver( + array(__DIR__) + ); + $config->setMetadataDriverImpl($driverImpl); + + $connectionOptions = PartKeepr::createConnectionOptionsFromConfig(); + + if (extension_loaded("apc")) { + $cache = new \Doctrine\Common\Cache\ApcCache(); + } else { + $cache = new \Doctrine\Common\Cache\ArrayCache(); + } + + $config->setMetadataCacheImpl($cache); + + $config->setQueryCacheImpl($cache); + + $config->setProxyDir(self::getRootDirectory() . '/data/proxies'); + $config->setProxyNamespace('Proxies'); + $config->setEntityNamespaces(self::getEntityClasses()); + $config->setAutoGenerateProxyClasses(false); + + if (PartKeeprConfiguration::getOption("partkeepr.database.echo_sql_log", false) === true) { + $logger = new \Doctrine\DBAL\Logging\EchoSQLLogger(); + $config->setSQLLogger($logger); + } + + self::$entityManager = EntityManager::create($connectionOptions, $config); + } + + public static function createConnectionOptionsFromConfig () { + $connectionOptions = array(); + + $driver = PartKeeprConfiguration::getOption("partkeepr.database.driver"); + + switch ($driver) { + case "pdo_mysql": + case "pdo_pgsql": + case "pdo_oci": + case "oci8": + case "pdo_sqlsrv": + $connectionOptions["driver"] = $driver; + $connectionOptions["dbname"] = PartKeeprConfiguration::getOption("partkeepr.database.dbname", "partkeepr"); + $connectionOptions["user"] = PartKeeprConfiguration::getOption("partkeepr.database.username", "partkeepr"); + $connectionOptions["password"] = PartKeeprConfiguration::getOption("partkeepr.database.password", "partkeepr"); + $connectionOptions["charset"] = "utf8"; + /** + * Compatibility with older configuration files. We check for the key "hostname" as well as "host". + */ + if (PartKeeprConfiguration::getOption("partkeepr.database.hostname", null) !== null) { + $connectionOptions["host"] = PartKeeprConfiguration::getOption("partkeepr.database.hostname"); + } else { + $connectionOptions["host"] = PartKeeprConfiguration::getOption("partkeepr.database.host", "localhost"); + } + + + if (PartKeeprConfiguration::getOption("partkeepr.database.port") !== null) { + $connectionOptions["port"] = PartKeeprConfiguration::getOption("partkeepr.database.port"); + } + + if (PartKeeprConfiguration::getOption("partkeepr.database.mysql_socket", null) !== null) { + $connectionOptions["unix_socket"] = PartKeeprConfiguration::getOption("partkeepr.database.mysql_socket"); + } + break; + case "pdo_sqlite": + $connectionOptions["driver"] = $driver; + $connectionOptions["user"] = PartKeeprConfiguration::getOption("partkeepr.database.username", "partkeepr"); + $connectionOptions["password"] = PartKeeprConfiguration::getOption("partkeepr.database.password", "partkeepr"); + $connectionOptions["path"] = PartKeeprConfiguration::getOption("partkeepr.database.sqlite_path", PartKeepr::getRootDirectory() . "/data/partkeepr.sqlite"); + break; + default: + throw new \Exception(sprintf("Unknown driver %s", $driver)); + } + + return $connectionOptions; + } + + /** + * Returns the EntityManager. Shortcut for getEntityManager(). + * @return \Doctrine\ORM\EntityManager The EntityManager + */ + public static function getEM () { + return self::getEntityManager(); + } + + public static function getRootDirectory () { + return dirname(dirname(dirname(__DIR__))); + } + + /** + * Returns the EntityManager. + * @return Doctrine\ORM\EntityManager The EntityManager + */ + public static function getEntityManager () { + if (!self::$entityManager instanceof EntityManager) { + throw new Exception("No EntityManager found. Make sure you called initializeDoctrine() or initialize()."); + } + return self::$entityManager; + } + + /** + * Returns the class metadata for all entity classes + * @return array an array of class metadata objects + */ + public static function getClassMetaData () { + $classes = self::getEntityClasses(); + + $aClasses = array(); + + foreach ($classes as $class) { + $aClasses[] = PartKeepr::getEM()->getClassMetadata($class); + } + + return $aClasses; + } + + /** + * Returns a list of all classes we use for entities. + * @return array An array of strings with all class names + */ + public static function getEntityClasses () { + return array( + 'PartKeepr\User\User', + 'PartKeepr\Session\Session', + + 'PartKeepr\Footprint\Footprint', + 'PartKeepr\Footprint\FootprintImage', + 'PartKeepr\Footprint\FootprintAttachment', + 'PartKeepr\FootprintCategory\FootprintCategory', + + 'PartKeepr\Part\Part', + 'PartKeepr\Part\PartUnit', + 'PartKeepr\Part\PartManufacturer', + 'PartKeepr\Part\PartDistributor', + 'PartKeepr\Part\PartImage', + 'PartKeepr\Part\PartAttachment', + 'PartKeepr\PartCategory\PartCategory', + + 'PartKeepr\Project\Project', + 'PartKeepr\Project\ProjectPart', + 'PartKeepr\Project\ProjectAttachment', + + 'PartKeepr\StorageLocation\StorageLocation', + 'PartKeepr\StorageLocation\StorageLocationImage', + + 'PartKeepr\Stock\StockEntry', + + 'PartKeepr\Manufacturer\Manufacturer', + 'PartKeepr\Manufacturer\ManufacturerICLogo', + + 'PartKeepr\Distributor\Distributor', + + 'PartKeepr\Image\Image', + 'PartKeepr\Image\CachedImage', + 'PartKeepr\TempImage\TempImage', + + 'PartKeepr\UploadedFile\TempUploadedFile', + + 'PartKeepr\Statistic\StatisticSnapshot', + 'PartKeepr\Statistic\StatisticSnapshotUnit', + 'PartKeepr\SiPrefix\SiPrefix', + 'PartKeepr\Unit\Unit', + 'PartKeepr\PartParameter\PartParameter', + + 'PartKeepr\TipOfTheDay\TipOfTheDay', + 'PartKeepr\TipOfTheDay\TipOfTheDayHistory', + 'PartKeepr\UserPreference\UserPreference', + 'PartKeepr\SystemNotice\SystemNotice', + 'PartKeepr\CronLogger\CronLogger' + + ); + } + + /** + * Formats a message and applies internationalization. + * + * This method accepts sprintf-like parameters, which are appended after the $string parameter. + * + * @param $string string The string to internationalize + * @todo stub + */ + public static function i18n ($string) { + if (func_num_args() > 1) { + $args = func_get_args(); + array_shift($args); + + return vsprintf($string, $args); + } else { + return $string; + } + } + + /** + * Returns a new GUID. + * @return string The new GUID + */ + public static function createGUIDv4() { + return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + + // 32 bits for "time_low" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + + // 16 bits for "time_mid" + mt_rand(0, 0xffff), + + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + mt_rand(0, 0x0fff) | 0x4000, + + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + mt_rand(0, 0x3fff) | 0x8000, + + // 48 bits for "node" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } + + /** + * Returns the current PartKeepr version. + * @return string The PartKeepr Version + */ + public static function getVersion () { + if (PartKeeprVersion::PARTKEEPR_VERSION == "{V_GIT}") { + return "GIT development version"; + } + return PartKeeprVersion::PARTKEEPR_VERSION; + } + + /** + * This is a re-implementation of gettype(). + * + * The PHP documentation states that the "gettype" return values will change in the future, so we need + * to make sure we don't get bitten by the change. + * + * @param mixed $var + * @return string The type + */ + public static function getType($var) + { + if (is_array($var)) return "array"; + if (is_bool($var)) return "boolean"; + if (is_float($var)) return "float"; + if (is_int($var)) return "integer"; + if (is_null($var)) return "NULL"; + if (is_numeric($var)) return "numeric"; + if (is_object($var)) return "object"; + if (is_resource($var)) return "resource"; + if (is_string($var)) return "string"; + return "unknown type"; + } + + /** + * Returns the effective size from a human-readable byte format. + * + * Example: + * getBytesFromHumanReadable("1M") will return 1048576. + * + * @param string $size_str The byte + * @return int The bytes + */ + public static function getBytesFromHumanReadable ($size_str) + { + switch (substr ($size_str, -1)) + { + case 'M': case 'm': return (int)$size_str * 1048576; + case 'K': case 'k': return (int)$size_str * 1024; + case 'G': case 'g': return (int)$size_str * 1073741824; + default: return $size_str; + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/PartKeeprVersion.php b/src/backend/PartKeepr/PartKeeprVersion.php @@ -0,0 +1,15 @@ +<?php +namespace PartKeepr; + +class PartKeeprVersion { + /** + * Holds the PartKeepr Version. + * + * If {V_GIT}, then the function will return 'GIT Development Version'. + * {V_GIT} will be replaced by the build script with the actual version. + * + * The reason why we have a separate class for the version constant is that + * we can easily replace it from scripts. + */ + const PARTKEEPR_VERSION = '{V_GIT}'; +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/PartParameter/PartParameter.php b/src/backend/PartKeepr/PartParameter/PartParameter.php @@ -0,0 +1,247 @@ +<?php +namespace PartKeepr\PartParameter; + +use PartKeepr\PartKeepr, +PartKeepr\Util\Exceptions\OutOfRangeException, +PartKeepr\Unit\Unit, +PartKeepr\Part\Part, +PartKeepr\SiPrefix\SiPrefix; + + +/** + * This object represents a parameter. Each parameter can have an unit (defined by the class "Unit") associated with + * a numeric value. + * + * @Entity @HasLifecycleCallbacks + **/ +class PartParameter { + /** + * @Id @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + * @var integer + */ + private $id; + + /** + * @ManyToOne(targetEntity="PartKeepr\Part\Part") + * The part this parameter is bound to + * @var Part + */ + private $part; + + /** + * The name of the parameter (e.g. Resistance, Voltage) + * @Column(type="string") + * @var string + */ + private $name; + + /** + * A description for this parameter + * @Column(type="string") + * @var string + */ + private $description; + + /** + * The unit for this type. May be null. + * + * @ManyToOne(targetEntity="PartKeepr\Unit\Unit") + * @var Unit + */ + private $unit; + + /** + * The value of the unit. Together with the prefix, it becomes the actual value. + * + * Example: If you have 10µ, the value field will contain "10", the prefix object is linked to the SiPrefix + * representing "µ" and the rawValue field will contain 0.000001 + * @Column(type="float") + * @var float + */ + private $value; + + /** + * The SiPrefix of the unit + * @ManyToOne(targetEntity="PartKeepr\SiPrefix\SiPrefix") + * @var object + */ + private $siPrefix; + + /** + * The raw value of the unit. + * @Column(type="float") + * @var float + */ + private $rawValue; + + /** + * Sets the name for this parameter + * @param string $name The name + */ + public function setName ($name) { + $this->name = $name; + } + + /** + * Returns the name for this parameter + * @return string The name for this parameter + */ + public function getName () { + return $this->name; + } + + /** + * Sets the description for this parameter + * @param string $description The description + */ + public function setDescription ($description) { + $this->description = $description; + } + + /** + * Returns the description + * @return string The description + */ + public function getDescription () { + return $this->description; + } + + /** + * Sets the unit + * @param Unit $unit The unit to set + */ + public function setUnit (Unit $unit = null) { + $this->unit = $unit; + } + + /** + * Returns the unit + * @return Unit the unit + */ + public function getUnit () { + return $this->unit; + } + + /** + * Sets the part + * @param Part $part The part to set + */ + public function setPart (Part $part) { + $this->part = $part; + } + + /** + * Returns the part + * @return Part the part + */ + public function getPart () { + return $this->part; + } + + /** + * Sets the value + * @param float $value The value to set + */ + public function setValue ($value) { + $this->value = $value; + + $this->recalculateRawValue(); + } + + /** + * Returns the value + * @return float The value + */ + public function getValue () { + return $this->value; + } + + /** + * Sets the si prefix for this parameter + * @param SiPrefix $prefix The prefix to set, or null + */ + public function setSiPrefix (SiPrefix $prefix = null) { + $this->siPrefix = $prefix; + + $this->recalculateRawValue(); + } + + /** + * Returns the si prefix for this parameter + * @return SiPrefix the si prefix or null + */ + public function getSiPrefix () { + return $this->siPrefix; + } + + /** + * Returns the ID for this object. + * @param none + * @return int The ID for this object + */ + public function getId () { + return $this->id; + } + + private function recalculateRawValue () { + if (is_object($this->getSiPrefix())) { + $power = $this->getSiPrefix()->getPower(); + } else { + $power = 0; + } + + $this->rawValue = $this->getValue() * pow(10, $power); + } + + /** + * Returns the data of this object in a serialized form. + * @return array The result array + */ + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "description" => $this->getDescription(), + "value" => $this->getValue(), + "part_id" => $this->getPart()->getId(), + "siprefix_id" => is_object($this->getSiPrefix()) ? $this->getSiPrefix()->getId() : null, + "prefixedValue" => array( + /* We duplicate most data because of strange ExtJS stuff... */ + "value" => $this->getValue(), + "power" => is_object($this->getSiPrefix()) ? $this->getSiPrefix()->getPower() : 0, + "symbol" => is_object($this->getSiPrefix()) ? $this->getSiPrefix()->getSymbol() : "", + "siprefix_id" => is_object($this->getSiPrefix()) ? $this->getSiPrefix()->getId() : null + ), + "unit_id" => is_object($this->getUnit()) ? $this->getUnit()->getId() : null + ); + } + + /** + * Deserializes the part parameter + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "name": + $this->setName($value); + break; + case "description": + $this->setDescription($value); + break; + case "value": + $this->setValue($value); + break; + case "siprefix_id": + $prefix = SiPrefix::loadById($value); + $this->setSiPrefix($prefix); + break; + case "unit_id": + $unit = Unit::loadById($value); + $this->setUnit($unit); + break; + } + } + } +} diff --git a/src/backend/PartKeepr/PartUnit/PartUnitManager.php b/src/backend/PartKeepr/PartUnit/PartUnitManager.php @@ -0,0 +1,106 @@ +<?php +namespace PartKeepr\PartUnit; + +use PartKeepr\Util\Singleton, + PartKeepr\Part\PartUnit, + PartKeepr\PartKeepr, + PartKeepr\Category\CategoryManager, + PartKeepr\PartUnit\Exceptions\PartUnitNotFoundException; + +class PartUnitManager extends Singleton { + public function getPartUnits ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { + + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select("st.id, st.name, st.shortName, st.is_default AS default")->from("PartKeepr\Part\PartUnit","st"); + + if ($filter != "") { + $qb = $qb->where("LOWER(st.name) LIKE :filter"); + $qb->setParameter("filter", "%".strtolower($filter)."%"); + } + + if ($limit > -1) { + $qb->setMaxResults($limit); + $qb->setFirstResult($start); + } + + $qb->orderBy("st.".$sort, $dir); + + $query = $qb->getQuery(); + + $result = $query->getResult(); + + foreach ($result as $key => $row) { + foreach ($row as $rowkey => $column) { + if ($rowkey == "default") { + if ($column == 0) { + $result[$key][$rowkey] = false; + } else { + $result[$key][$rowkey] = true; + } + } + } + } + + $totalQueryBuilder = PartKeepr::getEM()->createQueryBuilder(); + $totalQueryBuilder->select("COUNT(st.id)")->from("PartKeepr\Part\PartUnit","st"); + + + + if ($filter != "") { + $totalQueryBuilder = $totalQueryBuilder->where("LOWER(st.name) LIKE :filter"); + $totalQueryBuilder->setParameter("filter", "%".strtolower($filter)."%"); + } + + $totalQuery = $totalQueryBuilder->getQuery(); + + return array("data" => $result, "totalCount" => $totalQuery->getSingleScalarResult()); + } + + public function getPartUnit ($id) { + $partUnit = PartKeepr::getEM()->find("PartKeepr\Part\PartUnit", $id); + + if ($partUnit) { + return $partUnit; + } else { + throw new PartUnitNotFoundException(); + } + } + + public function deletePartUnit ($id) { + $partUnit = $this->getPartUnit($id); + + PartKeepr::getEM()->remove($partUnit); + PartKeepr::getEM()->flush(); + } + + /** + * Returns the default part unit for this system + * + * @param none + * @return PartUnit The default part unit for this system + */ + public function getDefaultPartUnit () { + $dql = 'SELECT pu FROM PartKeepr\Part\PartUnit pu WHERE pu.is_default = :default'; + return PartKeepr::getEM()->createQuery($dql)->setParameter("default", true)->getSingleResult(); + } + + public function setDefaultPartUnit ($id) { + PartKeepr::getEM()->beginTransaction(); + + $dql = 'UPDATE PartKeepr\Part\PartUnit pu SET pu.is_default = :default WHERE pu.id = :id'; + PartKeepr::getEM()->createQuery($dql)->setParameter("id", $id)->setParameter("default", true, \PDO::PARAM_BOOL)->execute(); + + $dql = 'UPDATE PartKeepr\Part\PartUnit pu SET pu.is_default = :default WHERE pu.id != :id'; + PartKeepr::getEM()->createQuery($dql)->setParameter("id", $id)->setParameter("default", false, \PDO::PARAM_BOOL)->execute(); + + PartKeepr::getEM()->commit(); + } + + public function getUnitCounts () { + $dql = 'SELECT SUM(p.stockLevel) AS stockLevel, pu.id AS puid FROM PartKeepr\Part\PartUnit pu LEFT JOIN pu.parts p GROUP BY pu.id'; + + $result = PartKeepr::getEM()->createQuery($dql)->getResult(); + + return $result; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/PartUnit/PartUnitService.php b/src/backend/PartKeepr/PartUnit/PartUnitService.php @@ -0,0 +1,72 @@ +<?php +namespace PartKeepr\PartUnit; +use PartKeepr\Service\RestfulService; + +use PartKeepr\Service\Service, + PartKeepr\PartKeepr, + PartKeepr\Part\PartUnit, + PartKeepr\Session\SessionManager; + +class PartUnitService extends Service implements RestfulService { + public function get () { + if ($this->hasParameter("id")) { + return array("data" => PartUnitManager::getInstance()->getPartUnit($this->getParameter("id"))->serialize()); + } else { + if ($this->hasParameter("sort")) { + $tmp = json_decode($this->getParameter("sort"), true); + + $aSortParams = $tmp[0]; + } else { + $aSortParams = array( + "property" => "name", + "direction" => "ASC"); + } + return PartUnitManager::getInstance()->getPartUnits( + $this->getParameter("start", $this->getParameter("start", 0)), + $this->getParameter("limit", $this->getParameter("limit", 25)), + $this->getParameter("sortby", $aSortParams["property"]), + $this->getParameter("dir", $aSortParams["direction"]), + $this->getParameter("query", "")); + } + } + + public function create () { + $this->requireParameter("name"); + + $partUnit = new PartUnit; + $partUnit->deserialize($this->getParameters()); + + PartKeepr::getEM()->persist($partUnit); + PartKeepr::getEM()->flush(); + + return array("data" => $partUnit->serialize()); + } + + public function update () { + $this->requireParameter("id"); + $this->requireParameter("name"); + + $partUnit = PartUnitManager::getInstance()->getPartUnit($this->getParameter("id")); + $partUnit->deserialize($this->getParameters()); + PartKeepr::getEM()->flush(); + + return array("data" => $partUnit->serialize()); + + } + + public function destroy () { + $this->requireParameter("id"); + + PartUnitManager::getInstance()->deletePartUnit($this->getParameter("id")); + + return array("data" => null); + } + + public function setDefault () { + $this->requireParameter("id"); + + $partUnit = PartUnitManager::getInstance()->setDefaultPartUnit($this->getParameter("id")); + + return array("data" => null); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Ping/PingService.php b/src/backend/PartKeepr/Ping/PingService.php @@ -0,0 +1,19 @@ +<?php +namespace PartKeepr\Ping; +use PartKeepr\Service\AnonService; + +use PartKeepr\Service\Service, + PartKeepr\PartKeepr; + +class PingService extends AnonService { + /** + * Simple test call to verify if the service layer is reachable. + * + * This is used for the PartKeeprMobile client to verify if the URL + * is entered correctly. + */ + public function ping () { + return "pong"; + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Project/Project.php b/src/backend/PartKeepr/Project/Project.php @@ -0,0 +1,161 @@ +<?php +namespace PartKeepr\Project; + +use PartKeepr\User\User, + PartKeepr\Util\Serializable, + PartKeepr\Util\Deserializable, + PartKeepr\Util\BaseEntity; + +/** + * Represents a part in the database. The heart of our project. Handle with care! + * @Entity **/ +class Project extends BaseEntity implements Serializable, Deserializable { + /** + * Specifies the name of the project + * @Column(type="string") + */ + private $name; + + /** + * Specifies the user this project belongs to + * @ManyToOne(targetEntity="PartKeepr\User\User") + */ + private $user; + + /** + * Holds the parts needed for this project + * @OneToMany(targetEntity="PartKeepr\Project\ProjectPart",mappedBy="project",cascade={"persist", "remove"}) + * @var ArrayCollection + */ + private $parts; + + /** + * Holds the description of this project + * @Column(type="string",nullable=true) + * @var string + */ + private $description; + + /** + * Holds the project attachments + * @OneToMany(targetEntity="PartKeepr\Project\ProjectAttachment",mappedBy="project",cascade={"persist", "remove"}) + * @var ProjectAttachment + */ + private $attachments; + + + /** + * Constructs a new project + */ + public function __construct () { + $this->parts = new \Doctrine\Common\Collections\ArrayCollection(); + $this->attachments = new \Doctrine\Common\Collections\ArrayCollection(); + } + + /** + * Sets the user for this project + * @param User $user + */ + public function setUser (User $user) { + $this->user = $user; + } + + /** + * Gets the user for this project + * @return User + */ + public function getUser () { + return $this->user; + } + + /** + * Sets the name for this project + * @param string $name + */ + public function setName ($name) { + $this->name = $name; + } + + /** + * Returns the name of this project + */ + public function getName () { + return $this->name; + } + + /** + * Sets the description of this project + * @param string $description The description to set + */ + public function setDescription ($description) { + $this->description = $description; + } + + /** + * Returns the description of this project + * @return string The description + */ + public function getDescription () { + return $this->description; + } + + /** + * Returns the parts array + * @return ArrayCollection An array of ProjectPart objects + */ + public function getParts () { + return $this->parts; + } + + /** + * Returns the attachments for this project + * @return ArrayCollection The attachments + */ + public function getAttachments () { + return $this->attachments; + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Util.Serializable::serialize() + */ + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "description" => $this->getDescription(), + "parts" => $this->serializeChildren($this->getParts()), + "attachments" => $this->serializeChildren($this->getAttachments()) + ); + } + + /** + * Deserializes the project + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "name": + $this->setName($value); + break; + case "description": + $this->setDescription($value); + break; + case "parts": + $this->deserializeChildren($value, $this->getParts(), "PartKeepr\Project\ProjectPart"); + foreach ($this->getParts() as $part) { + $part->setProject($this); + } + break; + case "attachments": + $this->deserializeChildren($value, $this->getAttachments(), "PartKeepr\Project\ProjectAttachment"); + foreach ($this->getAttachments() as $attachment) { + $attachment->setProject($this); + } + break; + } + } + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Project/ProjectAttachment.php b/src/backend/PartKeepr/Project/ProjectAttachment.php @@ -0,0 +1,101 @@ +<?php +namespace PartKeepr\Project; + +use PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable, + PartKeepr\UploadedFile\UploadedFile; + +/** + * Holds a project attachment + * @Entity + **/ +class ProjectAttachment extends UploadedFile implements Serializable, Deserializable { + /** + * The description of this attachment + * @Column(type="text") + * @var string + */ + private $description; + + /** + * Creates a new project attachment + */ + public function __construct () { + parent::__construct(); + $this->setType("ProjectAttachment"); + } + /** + * The project object + * @ManyToOne(targetEntity="PartKeepr\Project\Project") + * @var Project + */ + private $project = null; + + /** + * Sets the project + * @param Project $project The project to set + */ + public function setProject (Project $project) { + $this->project = $project; + } + + /** + * Returns the roject + * @return Project the project + */ + public function getProject () { + return $this->project; + } + + /** + * Sets the description for this attachment + * @param string $description The attachment description + */ + public function setDescription ($description) { + $this->description = $description; + } + + /** + * Returns the description for this attachment + * @return string The description + */ + public function getDescription () { + return $this->description; + } + + /** + * + * Serializes this project attachment + * @return array The serialized project attachment + */ + public function serialize () { + return array( + "id" => $this->getId(), + "project_id" => $this->getProject()->getId(), + "originalFilename" => $this->getOriginalFilename(), + "mimetype" => $this->getMimetype(), + "extension" => $this->getExtension(), + "size" => $this->getSize(), + "description" => $this->getDescription()); + } + + /** + * Deserializes the project attachment + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + if (array_key_exists("id", $parameters)) { + if (substr($parameters["id"], 0, 4) === "TMP:") { + $this->replaceFromTemporaryFile($parameters["id"]); + } + } + + foreach ($parameters as $key => $value) { + switch ($key) { + case "description": + $this->setDescription($value); + break; + } + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Project/ProjectManager.php b/src/backend/PartKeepr/Project/ProjectManager.php @@ -0,0 +1,33 @@ +<?php +namespace PartKeepr\Project; + +use PartKeepr\Manager\AbstractManager, + PartKeepr\Project\Project, + PartKeepr\PartKeepr; + +class ProjectManager extends AbstractManager { + /** + * Returns the FQCN for the target entity to operate on. + * @return string The FQCN, e.g. PartKeepr\Part + */ + public function getEntityName () { + return 'PartKeepr\Project\Project'; + } + + /** + * Returns all fields which need to appear in the getList ResultSet. + * @return array An array of all fields which should be returned + */ + public function getQueryFields () { + return array("id", "name", "description"); + } + + /** + * Returns the default sort field + * + * @return string The default sort field + */ + public function getDefaultSortField () { + return "name"; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Project/ProjectPart.php b/src/backend/PartKeepr/Project/ProjectPart.php @@ -0,0 +1,136 @@ +<?php +namespace PartKeepr\Project; + +use PartKeepr\Part\Part, + PartKeepr\Util\Serializable, + PartKeepr\Util\Deserializable, + PartKeepr\Util\BaseEntity; + +/** + * Represents a part in the database. The heart of our project. Handle with care! + * @Entity **/ +class ProjectPart extends BaseEntity implements Serializable, Deserializable { + /** + * @ManyToOne(targetEntity="PartKeepr\Part\Part") + */ + private $part; + + /** + * Specifies the amount of parts + * @Column(type="integer") + */ + private $quantity; + + /** + * Specifies the project which belongs to this project part + * @ManyToOne(targetEntity="PartKeepr\Project\Project") + */ + private $project; + + /** + * Specifies the remarks for this entry + * @Column(type="string",nullable=true) + */ + private $remarks; + + /** + * Sets the part which belongs to this entry + * @param Part $part + */ + public function setPart (Part $part) { + $this->part = $part; + } + + /** + * Returns the part which belongs to this entry + * @return Part + */ + public function getPart () { + return $this->part; + } + + /** + * Sets the quantity for this entry + * @param int $quantity + */ + public function setQuantity ($quantity) { + $this->quantity = intval($quantity); + } + + /** + * Returns the quantity for this project + * @return int the amount of parts needed + */ + public function getQuantity () { + return $this->quantity; + } + + /** + * Sets the project assigned to this entry + * @param Project $project + */ + public function setProject (Project $project) { + $this->project = $project; + } + + /** + * Returns the project assigned to this entry + * @return Project + */ + public function getProject () { + return $this->project; + } + + /** + * Sets the remarks for this entry + * @param string $remarks + */ + public function setRemarks ($remarks) { + $this->remarks = $remarks; + } + + /** + * Returns the remarks for this entry + * @return string + */ + public function getRemarks () { + return $this->remarks; + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Util.Serializable::serialize() + */ + public function serialize () { + return array( + "id" => $this->getId(), + "quantity" => $this->getQuantity(), + "part_id" => is_object($this->getPart()) ? $this->getPart()->getId() : 0, + "part_name" => is_object($this->getPart()) ? $this->getPart()->getName() : 0, + "project_id" => $this->getProject()->getId(), + "remarks" => $this->getRemarks() + ); + } + + /** + * Deserializes the project + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "remarks": + $this->setRemarks($value); + break; + case "quantity": + $this->setQuantity($value); + break; + case "part_id": + $part = Part::loadById($value); + $this->setPart($part); + break; + } + } + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Project/ProjectService.php b/src/backend/PartKeepr/Project/ProjectService.php @@ -0,0 +1,64 @@ +<?php +namespace PartKeepr\Project; + +use PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\Project\ProjectManager, + PartKeepr\PartKeepr, + PartKeepr\Manager\ManagerFilter; + +class ProjectService extends Service implements RestfulService { + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::get() + */ + public function get () { + if ($this->hasParameter("id")) { + return array("data" => ProjectManager::getInstance()->getEntity($this->getParameter("id"))->serialize()); + } else { + $parameters = new ManagerFilter($this); + $parameters->setFilterField("name"); + return ProjectManager::getInstance()->getList($parameters); + } + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::create() + */ + public function create () { + $this->requireParameter("name"); + + $entity = ProjectManager::getInstance()->createEntity($this->getParameters()); + + return array("data" => $entity->serialize()); + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::update() + */ + public function update () { + $this->requireParameter("id"); + $this->requireParameter("name"); + $entity = ProjectManager::getInstance()->getEntity($this->getParameter("id")); + $entity->deserialize($this->getParameters()); + + PartKeepr::getEM()->flush(); + + return array("data" => $entity->serialize()); + + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::destroy() + */ + public function destroy () { + $this->requireParameter("id"); + + ProjectManager::getInstance()->deleteEntity($this->getParameter("id")); + + return array("data" => null); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/ProjectAttachment/ProjectAttachmentManager.php b/src/backend/PartKeepr/ProjectAttachment/ProjectAttachmentManager.php @@ -0,0 +1,68 @@ +<?php +namespace PartKeepr\ProjectAttachment; + +use PartKeepr\Util\Singleton, + PartKeepr\Project\Project, + PartKeepr\PartKeepr; + +class ProjectAttachmentManager extends Singleton { + /** + * Returns a list of project attachments + * + * @param int $start Start of the list, default 0 + * @param int $limit Number of users to list, default 10 + * @param string $sort The field to sort by, default "name" + * @param string $dir The direction to sort (ASC or DESC), default ASC + * @param string $filter The project id + */ + public function getProjectAttachments ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { + + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select("st")->from("PartKeepr\Project\ProjectAttachment","st") + ->leftJoin('st.project', "fp"); + + if ($filter != "") { + $project = Project::loadById($filter); + $qb = $qb->where("st.project = :project"); + $qb->setParameter("project", $project); + } + + if ($limit > -1) { + $qb->setMaxResults($limit); + $qb->setFirstResult($start); + } + + $qb->orderBy("st.".$sort, $dir); + + $query = $qb->getQuery(); + + $result = $query->getResult(); + + $totalQueryBuilder = PartKeepr::getEM()->createQueryBuilder(); + $totalQueryBuilder->select("COUNT(st.id)")->from("PartKeepr\Project\ProjectAttachment","st"); + + + + if ($filter != "") { + $totalQueryBuilder = $totalQueryBuilder->where("st.project = :project"); + $totalQueryBuilder->setParameter("project", $project); + } + + $totalQuery = $totalQueryBuilder->getQuery(); + + $aData = array(); + foreach ($result as $item) { + $aData[] = $item->serialize(); + } + return array("data" => $aData, "totalCount" => $totalQuery->getSingleScalarResult()); + } + + /** + * Returns a project attachment by id + * @param int $id The project attachment id + */ + public function getProjectAttachment ($id) { + return ProjectAttachment::loadById($id); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/ProjectAttachment/ProjectAttachmentService.php b/src/backend/PartKeepr/ProjectAttachment/ProjectAttachmentService.php @@ -0,0 +1,102 @@ +<?php +namespace PartKeepr\ProjectAttachment; + +use PartKeepr\Project\ProjectAttachment, + PartKeepr\UploadedFile\TempUploadedFile, + PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\PartKeepr, + PartKeepr\Project\Project, + PartKeepr\Session\SessionManager; + +class ProjectAttachmentService extends Service implements RestfulService { + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::get() + */ + public function get () { + if ($this->hasParameter("id")) { + return ProjectAttachmentManager::getInstance()->getProjectAttachment($this->getParameter("id"))->serialize(); + } else { + if ($this->hasParameter("sort")) { + $tmp = json_decode($this->getParameter("sort"), true); + + $aSortParams = $tmp[0]; + } else { + $aSortParams = array( + "property" => "id", + "direction" => "ASC"); + } + + $filter = ""; + + if ($this->hasParameter("filter")) { + $tmp = json_decode($this->getParameter("filter"), true); + + foreach ($tmp as $item) { + if (array_key_exists("property", $item)) { + if ($item["property"] == "project_id") { + if (array_key_exists("value", $item)) { + $filter = $item["value"]; + } + } + } + } + } + return ProjectAttachmentManager::getInstance()->getProjectAttachments( + $this->getParameter("start", $this->getParameter("start", 0)), + $this->getParameter("limit", $this->getParameter("limit", 25)), + $this->getParameter("sortby", $aSortParams["property"]), + $this->getParameter("dir", $aSortParams["direction"]), + $filter); + } + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::create() + */ + public function create () { + $this->requireParameter("tmp_id"); + $this->requireParameter("project_id"); + + $tmpImage = TempUploadedFile::loadById($this->getParameter("tmp_id")); + + $file = new ProjectAttachment(); + + $project = Project::loadById($this->getParameter("project_id")); + + $file->setProject($project); + $file->replace($tmpImage->getFilename()); + $file->setOriginalFilename($tmpImage->getOriginalFilename()); + $file->setDescription($this->getParameter("description")); + PartKeepr::getEM()->persist($file); + PartKeepr::getEM()->flush(); + + return $file->serialize(); + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::update() + */ + public function update () { + + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::destroy() + */ + public function destroy () { + $this->requireParameter("id"); + + $file = ProjectAttachment::loadById($this->getParameter("id")); + + PartKeepr::getEM()->remove($file); + PartKeepr::getEM()->flush(); + + return array("data" => null); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/ProjectReport/ProjectReportService.php b/src/backend/PartKeepr/ProjectReport/ProjectReportService.php @@ -0,0 +1,97 @@ +<?php +namespace PartKeepr\ProjectReport; + +use PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\Project\ProjectManager, + PartKeepr\PartKeepr, + PartKeepr\Part\Part, + PartKeepr\Manager\ManagerFilter; + +class ProjectReportService extends Service implements RestfulService { + /** + * Returns a project report. + * + * The input format is an array with the following keys per entry: + * - project: The project ID + * - amount: Specifies how many copies of the project need to be reported + * + * The output format is an array which contains the following keys: + * - quantity: The overall quantity of parts needed (for a specific part) + * - part: The serialized part entity + * - storageLocation_name: The storage location name + * - available: The overall amount of available parts + * - sum_order: Always set to 0 because calculation happens in the frontend + * + * @see PartKeepr\Service.RestfulService::get() + */ + public function get () { + $reports = json_decode($this->getParameter("reports"), true); + + $aPartResults = array(); + + // Loop over all reports and calculate the overall quantities + foreach ($reports as $report) { + $dql = "SELECT pp.quantity, pro.name AS projectname, pp.remarks, p.id FROM "; + $dql .= "PartKeepr\Project\ProjectPart pp JOIN pp.part p "; + $dql .= "JOIN pp.project pro WHERE pp.project = :project"; + + $query = PartKeepr::getEM()->createQuery($dql); + $query->setParameter("project", $report["project"]); + + foreach ($query->getArrayResult() as $result) { + $part = Part::loadById($result["id"]); + + if (array_key_exists($result["id"], $aPartResults)) { + // Only update the quantity of the part + $aPartResults[$result["id"]]["quantity"] += $result["quantity"] * $report["amount"]; + $aPartResults[$result["id"]]["projects"][] = $result["projectname"]; + + if ($result["remarks"] != "") { + $aPartResults[$result["id"]]["remarks"][] = $result["projectname"]. ": " .$result["remarks"]; + } + } else { + // Create a full resultset + $aPartResults[$result["id"]] = array( + "quantity" => $result["quantity"] * $report["amount"], + "part" => array("response" => array("totalCount" => 1, "data" => $part->serialize())), + "storageLocation_name" => $part->getStorageLocation()->getName(), + "available" => $part->getStockLevel(), + "sum_order" => 0, + "projects" => array($result["projectname"]), + "remarks" => array() + ); + + if ($result["remarks"] != "") { + $aPartResults[$result["id"]]["remarks"] = array($result["projectname"]. ": " .$result["remarks"]); + } + } + } + } + + $aFinalResult = array(); + + // Iterate over all results and calculate how many parts are missing + foreach ($aPartResults as $key => $partResult) { + $missing = $partResult["quantity"] - $partResult["available"]; + + if ($missing < 0) { + $missing = 0; + } + + $partResult["missing"] = $missing; + $partResult["remarks"] = implode(", ", $partResult["remarks"]); + $partResult["projects"] = implode(", ", $partResult["projects"]); + + $aFinalResult[] = $partResult; + } + + return array("data" => $aFinalResult); + } + + public function create () {} + + public function update () {} + + public function destroy () {} +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/REST/ApplicationController.php b/src/backend/PartKeepr/REST/ApplicationController.php @@ -0,0 +1,42 @@ +<?php +namespace PartKeepr\REST; + +// Class taken over from the Sencha example +class ApplicationController { + public $request, $id, $params; + + /** + * dispatch + * Dispatch request to appropriate controller-action by convention according to the HTTP method. + */ + public function dispatch($request) { + $this->request = $request; + $this->id = $request->id; + $this->params = $request->params; + + if ($request->isRestful()) { + return $this->dispatchRestful(); + } + if ($request->action) { + return $this->{$request->action}(); + } + } + + protected function dispatchRestful() { + switch ($this->request->method) { + case 'GET': + return $this->view(); + break; + case 'POST': + return $this->create(); + break; + case 'PUT': + return $this->update(); + break; + case 'DELETE': + return $this->destroy(); + break; + } + } +} + diff --git a/src/backend/PartKeepr/REST/Model.php b/src/backend/PartKeepr/REST/Model.php @@ -0,0 +1,71 @@ +<?php +namespace PartKeepr\REST; + +// Class taken over from the Sencha example +class Model { + public $id, $attributes; + static function create($params) { + $obj = new self(get_object_vars($params)); + $obj->save(); + return $obj; + } + static function find($id) { + global $dbh; + $found = null; + foreach ($dbh->rs() as $rec) { + if ($rec['id'] == $id) { + $found = new self($rec); + break; + } + } + return $found; + } + static function update($id, $params) { + global $dbh; + $rec = self::find($id); + + if ($rec == null) { + return $rec; + } + $rs = $dbh->rs(); + + foreach ($rs as $idx => $row) { + if ($row['id'] == $id) { + $rec->attributes = array_merge($rec->attributes, get_object_vars($params)); + $dbh->update($idx, $rec->attributes); + break; + } + } + return $rec; + } + static function destroy($id) { + global $dbh; + $rec = null; + $rs = $dbh->rs(); + foreach ($rs as $idx => $row) { + if ($row['id'] == $id) { + $rec = new self($dbh->destroy($idx)); + break; + } + } + return $rec; + } + static function all() { + global $dbh; + return $dbh->rs(); + } + + public function __construct($params) { + $this->id = isset($params['id']) ? $params['id'] : null; + $this->attributes = $params; + } + public function save() { + global $dbh; + $this->attributes['id'] = $dbh->pk(); + $dbh->insert($this->attributes); + } + public function to_hash() { + return $this->attributes; + } +} + diff --git a/src/backend/PartKeepr/REST/Request.php b/src/backend/PartKeepr/REST/Request.php @@ -0,0 +1,115 @@ +<?php +namespace PartKeepr\REST; + +// Class taken over from the Sencha example +class Request { + public $restful, $method, $controller, $action, $id, $params; + + public function __construct($params) { + $this->restful = (isset($params["restful"])) ? $params["restful"] : false; + $this->method = $_SERVER["REQUEST_METHOD"]; + $this->parseRequest(); + } + public function isRestful() { + return $this->restful; + } + + public function getMethod () { + return $this->method; + } + + public function getParams () { + if ($this->params === null) { + $this->params = array(); + } + + if ($this->id !== null) { + $this->params["id"] = $this->id; + } + + $this->params = array_merge($_REQUEST, $this->params); + return $this->params; + } + + public function getService () { + if ($this->controller == "") { + $this->controller = $_REQUEST["service"]; + } + + $serviceName = $this->controller."Service"; + $namespace = 'PartKeepr\\'; + $cat = $this->controller . "\\"; + $fullName= $namespace . $cat . $serviceName; + + $class = new $fullName($this->getParams()); + + return $class; + } + + public function getAction () { + return $this->action; + } + + protected function parseRequest() { + if ($this->method == 'PUT') { // <-- Have to jump through hoops to get PUT data + $raw = ''; + $httpContent = fopen('php://input', 'r'); + while ($kb = fread($httpContent, 1024)) { + $raw .= $kb; + } + fclose($httpContent); + $params = array(); + parse_str($raw, $params); + + if (isset($params['data'])) { + $this->params = json_decode($params['data'], true); + } else { + $params = json_decode($raw, true); + $this->params = $params; + } + } else { + // grab JSON data if there... + $this->params = (isset($_REQUEST['data'])) ? json_decode($_REQUEST['data'], true) : null; + + if (isset($_REQUEST['data'])) { + $this->params = json_decode($_REQUEST['data'], true); + } else { + $raw = ''; + $httpContent = fopen('php://input', 'r'); + while ($kb = fread($httpContent, 1024)) { + $raw .= $kb; + } + $params = json_decode($raw, true); + if ($params) { + $this->params = $params; + } + } + + } + // Quickndirty PATH_INFO parser + if (isset($_SERVER["PATH_INFO"])){ + $cai = '/^\/([A-Za-z]+\w)\/([A-Za-z]+\w)\/([0-9]+)$/'; // /controller/action/id + $ca = '/^\/([A-Za-z]+\w)\/([A-Za-z]+)$/'; // /controller/action + $ci = '/^\/([A-Za-z]+\w)\/([0-9]+)$/'; // /controller/id + $c = '/^\/([A-Za-z]+\w)$/'; // /controller + $i = '/^\/([0-9]+)$/'; // /id + $matches = array(); + if (preg_match($cai, $_SERVER["PATH_INFO"], $matches)) { + $this->controller = $matches[1]; + $this->action = $matches[2]; + $this->id = $matches[3]; + } else if (preg_match($ca, $_SERVER["PATH_INFO"], $matches)) { + $this->controller = $matches[1]; + $this->action = $matches[2]; + } else if (preg_match($ci, $_SERVER["PATH_INFO"], $matches)) { + $this->controller = $matches[1]; + $this->id = $matches[2]; + } else if (preg_match($c, $_SERVER["PATH_INFO"], $matches)) { + $this->controller = $matches[1]; + } else if (preg_match($i, $_SERVER["PATH_INFO"], $matches)) { + $this->id = $matches[1]; + } + } + } +} + diff --git a/src/backend/PartKeepr/REST/Response.php b/src/backend/PartKeepr/REST/Response.php @@ -0,0 +1,21 @@ +<?php +namespace PartKeepr\REST; + +// Class taken over from the Sencha example +class Response { + public $success, $data, $message, $errors, $tid, $trace; + + public function __construct($params = array()) { + $this->success = isset($params["success"]) ? $params["success"] : false; + $this->message = isset($params["message"]) ? $params["message"] : ''; + $this->data = isset($params["data"]) ? $params["data"] : array(); + } + + public function to_json() { + return json_encode(array( + 'success' => $this->success, + 'message' => $this->message, + 'data' => $this->data + )); + } +} diff --git a/src/backend/PartKeepr/Service/AdminService.php b/src/backend/PartKeepr/Service/AdminService.php @@ -0,0 +1,14 @@ +<?php +namespace PartKeepr\Service; + +use PartKeepr\Session\SessionManager; + +class AdminService extends Service { + public function mayCall ($call) { + if (SessionManager::getCurrentSession()->getUser()->isAdmin()) { + return true; + } else { + return false; + } + } +} diff --git a/src/backend/PartKeepr/Service/AnonService.php b/src/backend/PartKeepr/Service/AnonService.php @@ -0,0 +1,6 @@ +<?php +namespace PartKeepr\Service; + +class AnonService extends Service { + +} diff --git a/src/backend/PartKeepr/Service/Exceptions/ServiceException.php b/src/backend/PartKeepr/Service/Exceptions/ServiceException.php @@ -0,0 +1,6 @@ +<?php +namespace PartKeepr\Service\Exceptions; + +use PartKeepr\Util\SerializableException; + +class ServiceException extends SerializableException {} diff --git a/src/backend/PartKeepr/Service/RestfulService.php b/src/backend/PartKeepr/Service/RestfulService.php @@ -0,0 +1,9 @@ +<?php +namespace PartKeepr\Service; + +interface RestfulService { + public function get (); + public function create (); + public function update (); + public function destroy (); +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Service/Service.php b/src/backend/PartKeepr/Service/Service.php @@ -0,0 +1,110 @@ +<?php +namespace PartKeepr\Service; + +use PartKeepr\User\User, + PartKeepr\Session\Session, + PartKeepr\Session\SessionManager, + PartKeepr\Service\Exceptions\ServiceException; + +class Service { + private $params; + + public function __construct (Array $params) { + $this->params = $params; + } + + public function mayCall ($call) { + if (SessionManager::getCurrentSession()->getUser() === null) { + return false; + } else { + /* @todo: Implement permission checking */ + return true; + } + + + } + + protected function requireParameter ($name) { + if (!$this->hasParameter($name)) { + throw new ServiceException(sprintf("Parameter %s is required.", $name)); + } + } + + public function getParameter ($name, $default = null) { + if (!$this->hasParameter($name)) { + return $default; + } else { + return $this->params[$name]; + } + } + + /** + * Returns all parameters passed to the service + * @return array An array with all parameters (key=>value format) + */ + public function getParameters () { + return $this->params; + } + + /** + * Returns the current user for this session + * + * @return User The user + */ + public function getUser () { + return SessionManager::getCurrentSession()->getUser(); + } + + /** + * Checks if the environment has an active, logged in user. + * + * @param none + * @return boolean True if a logged in user exists, false otherwise + */ + public function hasUser () { + if (!$this->hasSession()) { + return false; + } + + var_dump($this->getUser()); + if ($this->getUser() !== null) { + return true; + } else { + return false; + } + } + + /** + * Checks if there is an active session. + * + * @param none + * @return boolean true if an active session exists, false otherwise + */ + public function hasSession () { + return SessionManager::hasSession(); + } + + public function hasParameter ($name) { + if (array_key_exists($name, $this->params)) { + return true; + } else { + return false; + } + } + + public function hasHeader ($name) { + $targetName = "HTTP_".strtoupper($name); + + return array_key_exists($targetName, $_SERVER); + } + + public function getHeader ($name) { + $targetName = "HTTP_".strtoupper($name); + + if (array_key_exists($targetName, $_SERVER)) { + return $_SERVER[$targetName]; + } else { + throw new \Exception("Header ".$targetName." not found"); + } + } +} diff --git a/src/backend/PartKeepr/Service/ServiceManager.php b/src/backend/PartKeepr/Service/ServiceManager.php @@ -0,0 +1,126 @@ +<?php +namespace PartKeepr\Service; + +use PartKeepr\Session\SessionManager, + PartKeepr\Service\Exceptions\ServiceException, + PartKeepr\PartKeepr, + PartKeepr\User\User, + PartKeepr\User\UserManager, + PartKeepr\REST\Request; + +class ServiceManager { + + public static function sendHeaders () { + header("Content-Type: text/html; charset=UTF-8"); + header("Cache-Control: no-cache, must-revalidate"); + header("Access-Control-Allow-Origin: *"); + header("Access-Control-Allow-Headers: lang,call,service,X-Requested-With,X-PartKeepr-Locale,X-PartKeepr-Name,X-PartKeepr-Call"); + } + + public static function call () { + + $request = new Request(array('restful' => true)); + $service = $request->getService(); + + if ($service->hasHeader("call")) { + $call = $service->getHeader("call"); + } elseif (array_key_exists("call", $_REQUEST) && $_REQUEST["call"] != "") { + $call = $_REQUEST["call"]; + } elseif ($request->action != "") { + $call = $request->action; + } else { + switch (strtoupper($request->getMethod())) { + case "POST": + $call = "create"; + break; + case "GET": + $call = "get"; + break; + case "PUT": + $call = "update"; + break; + case "DELETE": + $call = "destroy"; + break; + default: + $call = $request->getMethod(); + break; + } + } + + $allowCall = true; + + if (!is_subclass_of($service, "PartKeepr\\Service\\AnonService")) { + $session = null; + $sessionid = false; + + $sessionid = self::getSession($service); + + + if ($sessionid === null) + { + $session = SessionManager::getInstance()->startSession(); + throw new ServiceException("You called a non-anonymous service, but did not pass the 'session' parameter."); + } else { + $session = SessionManager::getInstance()->resumeSession($sessionid); + } + + if (!$service->mayCall($call)) { + $allowCall = false; + } + } + + if (!$allowCall) { + throw new ServiceException("Permission denied"); + } + + if (!method_exists($service, $call)) { + throw new \Exception(sprintf("The service %s doesn't implement %s", get_class($service), $call)); + } + $result = $service->$call(); + + PartKeepr::getEM()->flush(); + + return $result; + + } + + private static function getSession ($service) { + if ($service->hasHeader("username") && $service->hasHeader("password") && !$service->hasHeader("session")) { + return self::authenticateByUsername($service->getHeader("username"), $service->getHeader("password")); + } + + if (array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST) && !array_key_exists("session", $_REQUEST)) { + return self::authenticateByUsername($_REQUEST["username"], $_REQUEST["password"]); + } + + if ($service->hasHeader("session")) { + return $service->getHeader("session"); + } + + if (array_key_exists("session", $_REQUEST)) { + return $_REQUEST["session"]; + } + } + + private static function authenticateByUsername ($username, $password) { + /* Build a temporary user */ + $user = new User; + $user->setRawUsername($username); + $user->setHashedPassword($password); + + $authenticatedUser = UserManager::getInstance()->authenticate($user); + + if ($authenticatedUser !== false) { + /* Start Session */ + $session = SessionManager::getInstance()->startSession($authenticatedUser); + + return $session->getSessionID(); + } else { + throw new InvalidLoginDataException(); + } + } + +} + +?>+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Session/Exceptions/SessionNotFoundException.php b/src/backend/PartKeepr/Session/Exceptions/SessionNotFoundException.php @@ -0,0 +1,10 @@ +<?php +namespace PartKeepr\Session\Exceptions; + +use PartKeepr\Util\SerializableException; + +class SessionNotFoundException extends SerializableException { + public function __construct ($id) { + parent::__construct("The session with the id $id could not be found"); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Session/Session.php b/src/backend/PartKeepr/Session/Session.php @@ -0,0 +1,59 @@ +<?php +namespace PartKeepr\Session; + +use PartKeepr\User\User, + PartKeepr\PartKeepr; + +/** @Entity */ +class Session { + + /** @Id @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + private $id; + + /** @Column(length=50) */ + private $sessionid; + + /** + * @ManyToOne(targetEntity="PartKeepr\User\User") + */ + private $user; + + public function __construct () { + + } + + public function start () { + session_start(); + session_regenerate_id(); + session_destroy(); + unset($_SESSION); + session_start(); + + $query = PartKeepr::getEM()->createQuery("DELETE FROM PartKeepr\\Session\\Session s WHERE s.sessionid = :session"); + $query->setParameter("session", session_id()); + $query->execute(); + + $this->sessionid = session_id(); + } + + public function getSessionID () { + return $this->sessionid; + } + + public function resume () { + session_id($this->sessionid); + session_start(); + } + + public function getUser () { + return $this->user; + } + + public function setUser (User $user = null) { + $this->user = $user; + } + +} +?>+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Session/SessionManager.php b/src/backend/PartKeepr/Session/SessionManager.php @@ -0,0 +1,68 @@ +<?php +namespace PartKeepr\Session; + +use PartKeepr\Util\Singleton, + PartKeepr\User\User, + PartKeepr\Session\Exceptions\SessionNotFoundException, + PartKeepr\PartKeepr; + +class SessionManager extends Singleton { + public static $currentSession = null; + + public static function getCurrentSession () { + return self::$currentSession; + } + + public static function hasSession () { + if (self::$currentSession !== null) { + return true; + } else { + return false; + } + } + + public function startSession (User $user = null) { + if (is_object($user)) { + try { + $query = PartKeepr::getEM()->createQuery("SELECT s FROM PartKeepr\\Session\\Session s WHERE s.user = :user"); + $query->setParameter("user", $user); + $query->execute(); + + $session = $query->getSingleResult(); + $session->resume(); + } catch (\Exception $e) { + $session = new Session; + $session->setUser($user); + $session->start(); + PartKeepr::getEM()->persist($session); + } + } else { + $session = new Session; + $session->setUser(null); + $session->start(); + PartKeepr::getEM()->persist($session); + } + + PartKeepr::getEM()->flush(); + + self::$currentSession = $session; + + return $session; + } + + public function resumeSession ($session) { + $query = PartKeepr::getEM()->createQuery("SELECT s FROM PartKeepr\\Session\\Session s WHERE s.sessionid = :session"); + $query->setParameter("session", $session); + $query->execute(); + try { + self::$currentSession = $query->getSingleResult(); + return self::$currentSession; + } catch (\Doctrine\ORM\NonUniqueResultException $e) { + throw new \Exception("Fatal error: Multiple sessions with id $session found."); + } catch (\Doctrine\ORM\NoResultException $e) { + throw new SessionNotFoundException($session); + } + + } +} +?>+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Setup/AbstractSetup.php b/src/backend/PartKeepr/Setup/AbstractSetup.php @@ -0,0 +1,44 @@ +<?php +namespace PartKeepr\Setup; + +use Doctrine\ORM\EntityManager; + +/** + * Represents a basic setup step + */ +abstract class AbstractSetup { + private $console; + + /** + * Represents the Doctrine Entity Manager + * @var Doctrine\ORM\EntityManager + */ + protected $entityManager; + + /** + * Represents all messages which are logged during setup + * @var array + */ + private $messages = array(); + + /** + * Constructs the setup step. + * @param EntityManager $em The entity manager + */ + public function __construct (EntityManager $em) { + $this->entityManager = $em; + } + + abstract public function run (); + + public function setConsole ($console) { + $this->console = $console; + } + + public function logMessage ($message) { + if ($this->console) { + echo "- ".$message."\n"; + } + $this->messages[] = $message; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Setup/ConfigFileSetup.php b/src/backend/PartKeepr/Setup/ConfigFileSetup.php @@ -0,0 +1,65 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\Util\Configuration, + PartKeepr\PartKeepr, + PartKeepr\Util\SerializableException; + +/** + * Creates or returns a new config file + */ +class ConfigFileSetup extends AbstractSetup { + + /** + * (non-PHPdoc) + * @see PartKeepr\Setup.AbstractSetup::run() + */ + public function run () { + switch ($_REQUEST["mode"]) { + case "save": + $this->saveConfig(); + break; + case "display": + return $this->displayConfig(); + break; + } + + return null; + } + + /** + * Returns the configuration file as string, so that it can be displayed + * during setup. + * + * @param none + * @return array An array, where the "config" key contains the configuration. + */ + private function displayConfig () { + return array("config" => Configuration::dumpConfig()); + } + + /** + * Saves the configuration file. + * + * @throws SerializableException An exception which describes what has been going wrong + */ + private function saveConfig () { + $configFile = PartKeepr::getRootDirectory()."/config.php"; + + if (file_exists($configFile)) { + if (!is_writable($configFile)) { + $message = "The config.php file could not be written, because it already exists and the webserver has "; + $message .= "no write access to it."; + + throw new SerializableException($message, 10000); + } + } else { + if (!is_writable(PartKeepr::getRootDirectory())) { + $message = "The config.php file could not be written, because the webserver has no write access to it."; + + throw new SerializableException($message, 10001); + } + } + file_put_contents($configFile, Configuration::dumpConfig()); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Setup/FootprintSetup.php b/src/backend/PartKeepr/Setup/FootprintSetup.php @@ -0,0 +1,156 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\Footprint\FootprintManager, + PartKeepr\FootprintCategory\FootprintCategoryManager, + PartKeepr\FootprintCategory\FootprintCategory, + PartKeepr\Footprint\Footprint, + PartKeepr\Footprint\FootprintImage, + PartKeepr\Footprint\FootprintAttachment, + PartKeepr\PartKeepr, + PartKeepr\Setup\Setup; + +class FootprintSetup extends AbstractSetup { + /** + * Holds the migrated footprints + * @var array + */ + private static $migratedFootprints = array(); + + const FOOTPRINT_PATH = "../setup-data/footprints/"; + const FOOTPRINT_FILE = "../setup-data/footprints/footprints.yaml"; + + /** + * Creates the root node for the footprints + */ + public function setupRootNode () { + FootprintCategoryManager::getInstance()->ensureRootExists(); + } + + public function run () { + $this->setupRootNode(); + $this->importFootprintData(); + } + + /** + * Returns a footprint by it's partdb id + * @param int $id The footprint id from the old partdb + */ + public static function getFootprintForOldId ($id) { + return FootprintSetup::$migratedFootprints[$id]; + } + + /** + * Creates a node structure for the given path + * + * @param $path array The components of the path + * @param $node Node The parent node + */ + public function addFootprintPath (Array $path, $node) { + if (count($path) == 0) { + return $node; + } + $name = array_shift($path); + + $childNode = null; + + foreach ($node->getChildren() as $child) { + if ($child->getNode()->getName() == $name) { + $childNode = $child; + } + } + + if ($childNode === null) { + $category = new FootprintCategory(); + $category->setParent($node->getNode()->getId()); + $category->setName($name); + $childNode = FootprintCategoryManager::getInstance()->addCategory($category); + } + + return $this->addFootprintPath($path, $childNode); + } + + /** + * Checks if the specified footprint exists + * @param string $name The footprint name + */ + public function footprintExists ($name) { + $dql = "SELECT COUNT(fp) FROM PartKeepr\Footprint\Footprint fp WHERE fp.name = :name"; + $query = $this->entityManager->createQuery($dql); + $query->setParameter("name", $name); + + if ($query->getSingleScalarResult() == 0) { + return false; + } else { + return true; + } + } + + /** + * Imports the footprints + * @throws \Exception + */ + public function importFootprintData () { + $count = 0; + $skipped = 0; + + /* Import pre-defined footprints */ + $data = Setup::loadYAML(self::FOOTPRINT_FILE); + + foreach ($data as $footprintName => $footprintData) { + /* Check if the footprint with the name already exists. If yes, skip the import for the single footprint */ + if ($this->footprintExists($footprintName)) { + $skipped++; + continue; + } + $footprint = new Footprint(); + $footprint->setName($footprintName); + + if (array_key_exists("description", $footprintData)) { + $footprint->setDescription($footprintData["description"]); + } + + if (array_key_exists("category", $footprintData)) { + $footprintCategory = $this->addFootprintPath(explode("/", $footprintData["category"]), FootprintCategoryManager::getInstance()->getRootNode()); + $footprint->setCategory($footprintCategory->getNode()); + } + + if (array_key_exists("image", $footprintData)) { + $footprintImage = new FootprintImage(); + $footprintImage->setFootprint($footprint); + $footprintImage->replace(self::FOOTPRINT_PATH . $footprintData["image"]); + + $footprint->setImage($footprintImage); + } + + if (array_key_exists("attachments", $footprintData) && is_array($footprintData["attachments"])) { + foreach ($footprintData["attachments"] as $attachment) { + if (!is_array($attachment)) { + throw new \Exception("Error: The property 'attachments' of $footprintName is not an array!"); + } + if (array_key_exists("url", $attachment)) { + try { + $footprintAttachment = new FootprintAttachment(); + $footprintAttachment->setFootprint($footprint); + $footprintAttachment->replaceFromURL($attachment["url"]); + if (array_key_exists("description", $attachment)) { + $footprintAttachment->setDescription($attachment["description"]); + } + + $footprint->getAttachments()->add($footprintAttachment); + } catch (\Exception $e) { + //echo "error with url ".$attachment["url"]."\n"; + } + } + + } + } + + $this->entityManager->persist($footprint); + $count++; + } + + $this->entityManager->flush(); + $this->logMessage(sprintf("Imported %d footprints, skipped %d because they already existed", $count, $skipped)); + } +} diff --git a/src/backend/PartKeepr/Setup/ManufacturerSetup.php b/src/backend/PartKeepr/Setup/ManufacturerSetup.php @@ -0,0 +1,56 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\PartKeepr, + PartKeepr\Manufacturer\Manufacturer, + PartKeepr\Manufacturer\ManufacturerManager, + PartKeepr\Manufacturer\ManufacturerICLogo, + PartKeepr\Setup\SiPrefixSetup; + +/** + * Sets up the manufacturers + */ +class ManufacturerSetup extends AbstractSetup { + + const MANUFACTURER_PATH = "../setup-data/manufacturers/"; + const MANUFACTURER_FILE = "../setup-data/manufacturers/manufacturers.yaml"; + + public function run () { + $this->setupManufacturers(); + } + + /** + * Sets up the manufacturers using the YAML file. + * @param $yaml string The path to the manufacturers YAML file + */ + public function setupManufacturers () { + $count=0; + $skipped=0; + $data = Setup::loadYAML(self::MANUFACTURER_FILE); + + foreach ($data as $mfgname => $logos) { + try { + ManufacturerManager::getInstance()->getManufacturerByName($mfgname); + $skipped++; + } catch (\Exception $e) { + $manufacturer = new Manufacturer(); + $manufacturer->setName($mfgname); + + $this->entityManager->persist($manufacturer); + + foreach ($logos as $logo) { + $mfglogo = new ManufacturerICLogo(); + $mfglogo->setManufacturer($manufacturer); + $mfglogo->replace(self::MANUFACTURER_PATH . "images/". $logo); + $mfglogo->setOriginalFilename($logo); + + $this->entityManager->persist($mfglogo); + } + $count++; + } + } + + $this->entityManager->flush(); + $this->logMessage(sprintf("Imported %d Manufacturers, skipped %d because they already exist", $count, $skipped)); + } +} diff --git a/src/backend/PartKeepr/Setup/Migration/PartDB/DistributorMigration.php b/src/backend/PartKeepr/Setup/Migration/PartDB/DistributorMigration.php @@ -0,0 +1,34 @@ +<?php +namespace PartKeepr\Setup\Migration\PartDB; + +use PartKeepr\PartKeepr, + PartKeepr\Distributor\Distributor, + PartKeepr\Distributor\DistributorManager, + PartKeepr\Setup\AbstractSetup; + +class DistributorMigration extends AbstractSetup { + /** + * Migrates the existing distributors + */ + public function run () { + $count = 0; + $skipped = 0; + $r = mysql_query("SELECT * FROM suppliers"); + while ($supplier = mysql_fetch_assoc($r)) { + $name = PartDBMigration::convertText($supplier["name"]); + try { + $distributor = DistributorManager::getInstance()->getDistributorByName($name); + $skipped++; + } catch (\Exception $e) { + $distributor = new Distributor(); + $distributor->setName($name); + + $this->entityManager->persist($distributor); + $count++; + } + } + + $this->entityManager->flush(); + $this->logMessage(sprintf("Migrated %d distributors, skipped %d because they already exist", $count, $skipped)); + } +} diff --git a/src/backend/PartKeepr/Setup/Migration/PartDB/FootprintMigration.php b/src/backend/PartKeepr/Setup/Migration/PartDB/FootprintMigration.php @@ -0,0 +1,43 @@ +<?php +namespace PartKeepr\Setup\Migration\PartDB; + +use PartKeepr\PartKeepr, + PartKeepr\Footprint\Footprint, + PartKeepr\Footprint\FootprintManager, + PartKeepr\FootprintCategory\FootprintCategoryManager, + PartKeepr\Setup\FootprintSetup; + +class FootprintMigration extends FootprintSetup { + /** + * Migrates the existing footprints + */ + public function run () { + $count = 0; + $skipped = 0; + + // Get or create node for the imported footprints + $footprintCategory = FootprintSetup::addFootprintPath(explode("/", "Imported Footprints"), FootprintCategoryManager::getInstance()->getRootNode()); + + $r = mysql_query("SELECT * FROM footprints"); + + while ($sFootprint = mysql_fetch_assoc($r)) { + $name = PartDBMigration::convertText($sFootprint["name"]); + + try { + FootprintManager::getInstance()->getFootprintByName($name); + $skipped++; + } catch (\Exception $e) { + $footprint = new Footprint(); + $footprint->setName($name); + + $footprint->setCategory($footprintCategory->getNode()); + + $this->entityManager->persist($footprint); + $count++; + } + } + + $this->entityManager->flush(); + $this->logMessage(sprintf("Migrated %d footprints, skipped %d because they already exist", $count, $skipped)); + } +} diff --git a/src/backend/PartKeepr/Setup/Migration/PartDB/PartCategoryMigration.php b/src/backend/PartKeepr/Setup/Migration/PartDB/PartCategoryMigration.php @@ -0,0 +1,50 @@ +<?php +namespace PartKeepr\Setup\Migration\PartDB; + +use PartKeepr\PartKeepr, + PartKeepr\PartCategory\PartCategory, + PartKeepr\PartCategory\PartCategoryManager, + PartKeepr\Setup\AbstractSetup; + +class PartCategoryMigration extends AbstractSetup { + private $categories = array(); + private static $migratedCategories = array(); + /** + * Migrates the old categories + */ + public function run () { + $this->addCategoryRecursive(0, array()); + + foreach ($this->categories as $oldid => $category) { + $newcategory = PartCategoryManager::getInstance()->createCategoryTreeByArray($category); + + self::$migratedCategories[$oldid] = $newcategory; + } + + } + + /** + * Creates the category tree, recursive + * @param array $aCategories the categories + * @param id $currentId The current ID to migrate + * @param Node $parent The parent node + */ + private function addCategoryRecursive ($parentId, $parents) { + $r = mysql_query("SELECT * FROM categories WHERE parentnode = ".intval($parentId)); + + while ($category = mysql_fetch_array($r)) { + $aCopy = $parents; + $aCopy[] = $category["name"]; + + $this->categories[$category["id"]] = $aCopy; + $this->addCategoryRecursive($category["id"], $aCopy); + } + } + + public static function getMigratedCategory ($id) { + if (!array_key_exists($id, self::$migratedCategories)) { + print_r(self::$migratedCategories); + } + return self::$migratedCategories[$id]; + } +} diff --git a/src/backend/PartKeepr/Setup/Migration/PartDB/PartDBMigration.php b/src/backend/PartKeepr/Setup/Migration/PartDB/PartDBMigration.php @@ -0,0 +1,71 @@ +<?php +namespace PartKeepr\Setup\Migration\PartDB; + +use PartKeepr\PartKeepr, + PartKeepr\Util\Configuration as PartKeeprConfiguration; + +class PartDBMigration { + /** + * Specifies if setup runs in console mode. + * @var boolean + */ + private $console = false; + + /** + * Runs the setup with all steps + */ + public function run () { + $this->runStep("all"); + } + + /** + * Sets console mode. + * + * In this mode, messages are directly written to the console. + */ + public function setConsole () { + $this->console = true; + } + + /** + * Runs a specific setup step, or all steps. + * @param string $step + * @throws \Exception + */ + public function runStep ($step) { + $entityManager = PartKeepr::getEM(); + + $aSteps = array( + "distributor" => new DistributorMigration($entityManager), + "footprint" => new FootprintMigration($entityManager), + "partcategory" => new PartCategoryMigration($entityManager), + "storagelocation" => new StorageLocationMigration($entityManager), + "part" => new PartMigration($entityManager) + ); + + if ($step == "all") { + foreach ($aSteps as $step) { + $step->setConsole($this->console); + $step->run(); + } + } else { + if (array_key_exists($step, $aSteps)) { + $aSteps[$step]->run(); + } else { + throw new \Exception(sprintf("Migration step %s doesn't exist", $step)); + } + } + } + + /** + * Converts strange escpaes in the database to "regular" text. + * @param string $string The string to convert + * @return string The converted string + */ + public static function convertText ($string) { + $string = stripslashes($string); + $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8'); + $string = str_replace("&#937;", "Ω", $string); + return $string; + } +} diff --git a/src/backend/PartKeepr/Setup/Migration/PartDB/PartMigration.php b/src/backend/PartKeepr/Setup/Migration/PartDB/PartMigration.php @@ -0,0 +1,131 @@ +<?php +namespace PartKeepr\Setup\Migration\PartDB; + +use PartKeepr\PartKeepr, + PartKeepr\Part\Part, + PartKeepr\Part\PartAttachment, + PartKeepr\Part\PartDistributor, + PartKeepr\Part\PartManager, + PartKeepr\Stock\StockEntry, + PartKeepr\Distributor\DistributorManager, + PartKeepr\PartCategory\PartCategoryManager, + PartKeepr\Footprint\FootprintManager, + PartKeepr\PartUnit\PartUnitManager, + PartKeepr\StorageLocation\StorageLocationManager, + PartKeepr\Setup\AbstractSetup; + +class PartMigration extends AbstractSetup { + /** + * Migrates the existing distributors + */ + public function run () { + $count = 0; + $skipped = 0; + $fc = 0; + + $r = mysql_query("SELECT * FROM parts"); + + while ($part = mysql_fetch_assoc($r)) { + $name = PartDBMigration::convertText($part["name"]); + + $oPart = new Part(); + $oPart->setName($name); + $oPart->setComment(PartDBMigration::convertText($part["comment"])); + + $oPart->setFootprint($this->getFootprintForPart($part["id"])); + $oPart->setReviewFlag(true); + $category = PartCategoryMigration::getMigratedCategory($part["id_category"]); + + if ($category === null) { + PartCategoryManager::getInstance()->getRootNode()->getNode(); + } else { + $oPart->setCategory($category); + } + + $oPart->setStorageLocation($this->getStorageLocationForPart($part["id"])); + $oPart->setMinStockLevel($part["mininstock"]); + $oPart->setPartUnit(PartUnitManager::getInstance()->getDefaultPartUnit()); + + $partDistributor = new PartDistributor(); + $partDistributor->setPart($oPart); + $partDistributor->setDistributor($this->getDistributorForPart($part["id"])); + $partDistributor->setOrderNumber($part["supplierpartnr"]); + $oPart->getDistributors()->add($partDistributor); + + + /* Add existing datasheets */ + $datasheetQuery = "SELECT datasheeturl FROM datasheets WHERE part_id = ".$part["id"]; + $r3 = mysql_query($datasheetQuery); + while ($res = mysql_fetch_assoc($r3)) { + try { + $attachment = new PartAttachment(); + $attachment->setPart($oPart); + $attachment->replaceFromURL($res["datasheeturl"]); + $attachment->setDescription(PartKeepr::i18n("Datasheet")); + $oPart->getAttachments()->add($attachment); + } catch (\Exception $e) { + Setup::progress(" - error with url ".$res["datasheeturl"].". Maybe the datasheet was not found."); + Setup::progress(" - The exception error was: ".$e->getMessage()); + } + } + + PartKeepr::getEM()->persist($oPart); + + $oStock = new StockEntry($oPart, $part["instock"]); + + $priceQuery = "SELECT AVG(preis) AS preis FROM preise WHERE part_id = ".$part["id"]; + + $r2 = mysql_query($priceQuery); + $res = mysql_fetch_assoc($r2); + + if ($res) { + if ($res["preis"] !== null) { + $oStock->setPrice(floatval($res["preis"])); + } + } + + PartKeepr::getEM()->persist($oStock); + + $fc++; + + // Flush every STEP_SIZE parts + if ($fc>PartMigration::STEP_SIZE) { + PartKeepr::getEM()->flush(); + $fc=0; + } + } + + $this->entityManager->flush(); + $this->logMessage(sprintf("Migrated %d parts, skipped %d because they already exist", $count, $skipped)); + } + + private function getFootprintForPart ($oldid) { + $r = mysql_query("SELECT footprints.name FROM footprints, parts WHERE footprints.id = parts.id_footprint AND parts.id = ".intval($oldid)); + + $data = mysql_fetch_assoc($r); + + return FootprintManager::getInstance()->getFootprintByName(PartDBMigration::convertText($data["name"])); + } + + private function getStorageLocationForPart ($oldid) { + $r = mysql_query("SELECT storeloc.name FROM storeloc, parts WHERE storeloc.id = parts.id_storeloc AND parts.id = ".intval($oldid)); + + $data = mysql_fetch_assoc($r); + + return StorageLocationManager::getInstance()->getStorageLocationByName(PartDBMigration::convertText($data["name"])); + } + + private function getDistributorForPart ($oldid) { + $r = mysql_query("SELECT suppliers.name FROM suppliers, parts WHERE suppliers.id = parts.id_supplier AND parts.id = ".intval($oldid)); + + $data = mysql_fetch_assoc($r); + + return DistributorManager::getInstance()->getDistributorByName(PartDBMigration::convertText($data["name"])); + } + + /** + * Defines the size of the records which are held in memory unless we flush to the DB. + * @var int + */ + const STEP_SIZE = 100; +} diff --git a/src/backend/PartKeepr/Setup/Migration/PartDB/StorageLocationMigration.php b/src/backend/PartKeepr/Setup/Migration/PartDB/StorageLocationMigration.php @@ -0,0 +1,51 @@ +<?php +namespace PartKeepr\Setup\Migration\PartDB; + +use PartKeepr\PartKeepr, + PartKeepr\StorageLocation\StorageLocation, + PartKeepr\StorageLocation\StorageLocationManager, + PartKeepr\Setup\AbstractSetup; + +class StorageLocationMigration extends AbstractSetup { + /** + * Holds the migrated storage locations + * @var array + */ + private static $migratedStorageLocations = array(); + + /** + * Migrates the storage locations + */ + public function run () { + $count = 0; + $skipped = 0; + + $r = mysql_query("SELECT * FROM storeloc"); + + while ($store = mysql_fetch_assoc($r)) { + $name = PartDBMigration::convertText($store["name"]); + try { + $storageLocation = StorageLocationManager::getInstance()->getStorageLocationByName($name); + $skipped++; + } catch (\Exception $e) { + $oStorageLocation = new StorageLocation(); + $oStorageLocation->setName($name); + + $this->entityManager->persist($oStorageLocation); + $count++; + } + } + + $this->entityManager->flush(); + $this->logMessage(sprintf("Migrated %d storage locations, skipped %d because they already exist", $count, $skipped)); + + } + + /** + * Returns the storage location by id + * @param int $id + */ + public static function getMigratedStorageLocation ($id) { + return StorageLocationSetup::$migratedStorageLocations[$id]; + } +} diff --git a/src/backend/PartKeepr/Setup/MiscSettingsSetup.php b/src/backend/PartKeepr/Setup/MiscSettingsSetup.php @@ -0,0 +1,69 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\CronLogger\CronLoggerManager, + PartKeepr\PartKeepr; + +/** + * Sets up misc stuff, which doesn't fit into other steps + */ +class MiscSettingsSetup extends AbstractSetup { + public function run () { + $this->markCronjobsAsRun(); + $this->clearAPCCache(); + $this->regenerateProxies(); + } + + /** + * Marks the cronjobs as run, so that the user doesn't get an error during the first day. + * + * This is necessary because if the user sets up the system and enters the cronjobs, there is a chance + * that no cronjob has been ran when he logs in the first time and thus gets confused. + */ + public function markCronjobsAsRun () { + foreach (PartKeepr::getRequiredCronjobs() as $cronjob) { + CronLoggerManager::getInstance()->markCronRun($cronjob); + } + } + + /** + * Clears the APC cache to push out old entries + */ + public function clearAPCCache () { + if (function_exists("apc_clear_cache")) { + apc_clear_cache(); + apc_clear_cache("user"); + } + } + + /** + * Re-generates all proxies. This is analog to doctrine orm:generate-proxies + * + * @throws \InvalidArgumentException + */ + public function regenerateProxies () { + $em = $this->entityManager; + + $metadatas = $em->getMetadataFactory()->getAllMetadata(); + $destPath = $em->getConfiguration()->getProxyDir(); + + if ( ! is_dir($destPath)) { + mkdir($destPath, 0777, true); + } + + $destPath = realpath($destPath); + + if ( ! file_exists($destPath)) { + throw new \InvalidArgumentException( + sprintf("Proxies destination directory '<info>%s</info>' does not exist.", $em->getConfiguration()->getProxyDir()) + ); + } else if ( ! is_writable($destPath)) { + throw new \InvalidArgumentException( + sprintf("Proxies destination directory '<info>%s</info>' does not have write permissions.", $destPath) + ); + } + + $em->getProxyFactory()->generateProxyClasses($metadatas, $destPath); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Setup/PartCategorySetup.php b/src/backend/PartKeepr/Setup/PartCategorySetup.php @@ -0,0 +1,28 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\PartKeepr, + PartKeepr\PartCategory\PartCategoryManager, + PartKeepr\PartCategory\PartCategory; + +class PartCategorySetup extends AbstractSetup { + /** + * Sets up the root category node + */ + public function setupRootCategory () { + PartCategoryManager::getInstance()->ensureRootExists(); + } + + public function updateCategoryPathCache () { + PartCategoryManager::getInstance()->updateCategoryPaths( + PartCategoryManager::getInstance()->getRootNode() + ); + + PartKeepr::getEM()->flush(); + } + + public function run () { + $this->setupRootCategory(); + $this->updateCategoryPathCache(); + } +} diff --git a/src/backend/PartKeepr/Setup/PartUnitSetup.php b/src/backend/PartKeepr/Setup/PartUnitSetup.php @@ -0,0 +1,36 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\PartKeepr, + PartKeepr\Part\PartUnit; + +class PartUnitSetup extends AbstractSetup { + /** + * Holds the default unit + * @var object + */ + private static $defaultUnit; + + /** + * Sets up the default part unit if none exists + */ + public function run () { + $dql = "SELECT COUNT(p) FROM PartKeepr\Part\PartUnit p WHERE p.is_default = :default"; + $query = $this->entityManager->createQuery($dql); + $query->setParameter("default", true); + + if ($query->getSingleScalarResult() == 0) { + $partUnit = new PartUnit(); + $partUnit->setName(PartKeepr::i18n("Pieces")); + $partUnit->setShortName(PartKeepr::i18n("pcs")); + $partUnit->setDefault(true); + + $this->entityManager->persist($partUnit); + $this->entityManager->flush(); + + $this->logMessage("Added default part unit"); + } else { + $this->logMessage("Skipped adding default part unit, because a default part unit already exists"); + } + } +} diff --git a/src/backend/PartKeepr/Setup/SchemaSetup.php b/src/backend/PartKeepr/Setup/SchemaSetup.php @@ -0,0 +1,34 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\PartKeepr; +/** + * Updates (or creates) the database schema + */ +class SchemaSetup extends AbstractSetup { + public function run () { + $tool = new \Doctrine\ORM\Tools\SchemaTool($this->entityManager); + $classes = PartKeepr::getClassMetaData(); + $tool->updateSchema($classes, true); + $this->logMessage("Database Schema created/updated"); + } + + /** + * Checks if the specified database has UTF-8 encoding + * @param $connection The DBAL connection + * @param string $dbname + */ + public static function mysqlHasUTF8Encoding ($connection, $dbname) { + $statement = $connection->prepare("SELECT default_character_set_name FROM information_schema.SCHEMATA S WHERE schema_name = :schema"); + $statement->bindValue("schema", $dbname); + $statement->execute(); + + $encoding = $statement->fetchColumn(0); + + if ($encoding != "utf8") { + return false; + } else { + return true; + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Setup/Setup.php b/src/backend/PartKeepr/Setup/Setup.php @@ -0,0 +1,176 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\PartKeepr, + PartKeepr\Util\Configuration as PartKeeprConfiguration; + +class Setup { + /** + * Specifies if setup runs in console mode. + * @var boolean + */ + private $console = false; + + /** + * Defines if the setup runs in verbose mode. + * @var boolean + */ + private static $verbose = false; + + /** + * Runs the migration with all steps + */ + public function run () { + $this->runStep("all"); + } + + /** + * Sets console mode. + * + * In this mode, messages are directly written to the console. + */ + public function setConsole () { + $this->console = true; + } + + /** + * Runs a specific setup step, or all steps. + * + * @param string $step The step to execute + * @throws \Exception + */ + public function runStep ($step) { + $entityManager = PartKeepr::getEM(); + + $aSteps = array( + "schema" => new SchemaSetup($entityManager), + "adminuser" => new UserSetup($entityManager), + "partunit" => new PartUnitSetup($entityManager), + "footprint" => new FootprintSetup($entityManager), + "partcategory" => new PartCategorySetup($entityManager), + "siprefix" => new SiPrefixSetup($entityManager), + "unit" => new UnitSetup($entityManager), + "manufacturer" => new ManufacturerSetup($entityManager), + "miscsettings" => new MiscSettingsSetup($entityManager) + ); + + $aActions = array( + "configfile" => new ConfigFileSetup($entityManager) + ); + if ($step == "all") { + foreach ($aSteps as $step) { + $step->setConsole($this->console); + $step->run(); + } + } else { + if (array_key_exists($step, $aSteps)) { + return $aSteps[$step]->run(); + } + + if (array_key_exists($step, $aActions)) { + return $aActions[$step]->run(); + } + + throw new \Exception(sprintf("Setup step %s doesn't exist", $step)); + } + } + + /** + * Tests for APC. Throws an exception if APC is missing or not active. + * @throws \Exception + */ + public function testAPC () { + if (!extension_loaded("apc")) { + throw new \Exception(PartKeepr::i18n("The extension 'apc' is not loaded. Make sure that it is installed (see http://php.net/manual/en/apc.installation.php) and that it is enabled (set apc.enabled=1 in your php.ini).")); + } + } + + /** + * Tests for suitable memory_limit settings + * @todo stub + */ + public function testMemoryLimit () { + //echo ini_get("memory_limit"); + } + + /** + * Sets the verbose flag + * @param boolean $verbose True if verbose output is wanted, false otherwise + */ + public static function setVerbose ($verbose) { + Setup::$verbose = $verbose; + } + + /** + * Outputs a progress message. + * + * @param string $string The string to output + * @param boolean $verbose True if the string should only be printed if verbosity is turned on + */ + public static function progress ($string, $verbose = false) { + if (!$verbose || ($verbose && Setup::$verbose)) { + echo $string."\n"; + } + + } + + /** + * Loads the given YAML file. Due to an API brach between Doctrine 2.0.5 and Doctrine 2.0.6, + * we need to work it around. + * @param string $file The path of the file to load + * @return array The parsed YAML file + */ + public static function loadYAML ($file) { + return \Symfony\Component\Yaml\Yaml::parse($file); + } + + /** + * Sets the database configuration array from $_REQUEST + */ + public static function setDatabaseConfigurationFromRequest () { + if (isset($_REQUEST["dbname"])) { + PartKeeprConfiguration::setOption("partkeepr.database.dbname", $_REQUEST["dbname"]); + } + + if (isset($_REQUEST["user"])) { + PartKeeprConfiguration::setOption("partkeepr.database.username", $_REQUEST["user"]); + } + if (isset($_REQUEST["password"])) { + PartKeeprConfiguration::setOption("partkeepr.database.password", $_REQUEST["password"]); + } + if (isset($_REQUEST["host"])) { + PartKeeprConfiguration::setOption("partkeepr.database.host", $_REQUEST["host"]); + } + + if (isset($_REQUEST['port'])) { + PartKeeprConfiguration::setOption("partkeepr.database.port", $_REQUEST["port"]); + } + + switch ($_REQUEST["driver"]) { + case "mysql": + PartKeeprConfiguration::setOption("partkeepr.database.driver","pdo_mysql"); + break; + case "pgsql": + PartKeeprConfiguration::setOption("partkeepr.database.driver","pdo_pgsql"); + break; + default: + throw new \Exception(sprintf("Invalid driver %s specified.", $_REQUEST["driver"])); + break; + } + } + + /** + * Runs some checks for the CLI setup + */ + public function runCLIChecks () { + + if (PartKeeprConfiguration::getOption("partkeepr.database.driver") == "pdo_mysql") { + $dbname = PartKeeprConfiguration::getOption("partkeepr.database.dbname"); + if (!SchemaSetup::mysqlHasUTF8Encoding(PartKeepr::getEM()->getConnection(), $dbname )) { + echo "Error: The database $dbname hasn't got the UTF-8 encoding. You need to set the database encoding to UTF-8. Aborting.\n"; + die; + } + } + + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Setup/SiPrefixSetup.php b/src/backend/PartKeepr/Setup/SiPrefixSetup.php @@ -0,0 +1,47 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\PartKeepr, + PartKeepr\SiPrefix\SiPrefix, + PartKeepr\SiPrefix\SiPrefixManager; + +class SiPrefixSetup extends AbstractSetup { + + const SIPREFIX_DATA_FILE = "../setup-data/siprefixes.yaml"; + + /** + * Stores the migrated si prefixes + * @var array + */ + private static $siPrefixes = array(); + + public function run () { + $this->setupSiPrefixes(); + } + + /** + * Sets up the SI prefixes + */ + public function setupSiPrefixes () { + $count = 0; + $skipped = 0; + + $data = Setup::loadYAML(self::SIPREFIX_DATA_FILE); + + foreach ($data as $prefixName => $prefixData) { + if (!SiPrefixManager::getInstance()->siPrefixExists($prefixName)) { + $prefix = new SiPrefix(); + $prefix->setPrefix($prefixName); + $prefix->setPower($prefixData["power"]); + $prefix->setSymbol($prefixData["symbol"]); + $this->entityManager->persist($prefix); + $count++; + } else { + $skipped++; + } + } + + $this->entityManager->flush(); + $this->logMessage(sprintf("Imported %d Si Prefixes, skipped %d", $count, $skipped)); + } +} diff --git a/src/backend/PartKeepr/Setup/UnitSetup.php b/src/backend/PartKeepr/Setup/UnitSetup.php @@ -0,0 +1,62 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\PartKeepr, + PartKeepr\Unit\Unit, + PartKeepr\SiPrefix\SiPrefixManager, + PartKeepr\Unit\UnitManager, + PartKeepr\Setup\SiPrefixSetup; + +class UnitSetup extends AbstractSetup { + + const UNIT_DATA_FILE = "../setup-data/units.yaml"; + + + public function run () { + $this->setupUnits(); + } + /** + * Sets up the default units + * @throws \Exception + */ + public function setupUnits () { + $count = 0; + $skipped = 0; + $data = Setup::loadYAML(self::UNIT_DATA_FILE); + + $aUnits = array(); + + foreach ($data as $unitName => $unitData) { + if (UnitManager::getInstance()->unitExists($unitName)) { + $skipped++; + continue; + } + $unit = new Unit(); + $unit->setName($unitName); + $unit->setSymbol($unitData["symbol"]); + + if (array_key_exists("prefixes", $unitData)) { + if (!is_array($unitData["prefixes"])) { + throw new \Exception($unitName." doesn't contain a prefix list, or the prefix list is not an array."); + } + + foreach ($unitData["prefixes"] as $prefix) { + + $siPrefix = SiPrefixManager::getInstance()->getSiPrefixBySymbol($prefix); + if ($siPrefix === false) { + throw new \Exception("Unable to find prefix ".$prefix); + } + $unit->getPrefixes()->add($siPrefix); + } + } + + PartKeepr::getEM()->persist($unit); + $count++; + } + + $this->entityManager->flush(); + $this->logMessage(sprintf("Imported %d Units, skipped %d because they already exist", $count, $skipped)); + } + + +} diff --git a/src/backend/PartKeepr/Setup/UserSetup.php b/src/backend/PartKeepr/Setup/UserSetup.php @@ -0,0 +1,31 @@ +<?php +namespace PartKeepr\Setup; + +use PartKeepr\User\User; + +/** + * Creates a new admin user, but only if no admin user exists. + */ +class UserSetup extends AbstractSetup { + public function run () { + $dql = "SELECT COUNT(u) FROM PartKeepr\User\User u WHERE u.username = :username OR u.admin = :admin"; + $query = $this->entityManager->createQuery($dql); + $query->setParameter("username", "admin"); + $query->setParameter("admin", true); + + if ($query->getSingleScalarResult() == 0) { + $user = new User(); + $user->setUsername("admin"); + $user->setPassword("admin"); + $user->setAdmin(true); + + $this->entityManager->persist($user); + $this->entityManager->flush(); + + $this->logMessage("Admin User created"); + } else { + $this->logMessage( "Skipped admin user creation, because an user named 'admin'". + "or another user with an admin flag already exists"); + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/SiPrefix/SiPrefix.php b/src/backend/PartKeepr/SiPrefix/SiPrefix.php @@ -0,0 +1,91 @@ +<?php +namespace PartKeepr\SiPrefix; + +use PartKeepr\Util\BaseEntity, + PartKeepr\PartKeepr, + PartKeepr\Util\Exceptions\OutOfRangeException; + + +/** @Entity **/ +class SiPrefix extends BaseEntity { + /** + * The prefix of the Si-Prefix (e.g. yotta, deca, deci, centi) + * @Column(type="string") + * @var string + */ + private $prefix; + + /** + * The symbol of the Si-Prefix (e.g. m, M, G) + * @Column(type="string",length=2) + * @var string + */ + private $symbol; + + /** + * The power of the Si-Prefix (e.g. milli = 10^-3) + * @Column(type="integer") + * @var int + */ + private $power; + + /** + * Sets the prefix name. + * @param string $prefix + */ + public function setPrefix ($prefix) { + $this->prefix = $prefix; + } + + /** + * Returns the prefix name + * @return string The prefix name + */ + public function getPrefix () { + return $this->prefix; + } + + /** + * Sets the symbol for the prefix + * @param string $symbol The symbol + */ + public function setSymbol ($symbol) { + $this->symbol = $symbol; + } + + /** + * Returns the symbol for the prefix + * @return string The symbol + */ + public function getSymbol () { + return $this->symbol; + } + + /** + * Sets the power in a 10^n power (n=power) + * @param int $power The 10^power + */ + public function setPower ($power) { + $this->power = $power; + } + + /** + * Returns the power (10^n) + * @return int The power + */ + public function getPower () { + return $this->power; + } + + /** + * Serializes the object into an array format. + * @return array the object in serialized format. + */ + public function serialize () { + return array( + "id" => $this->getId(), + "symbol" => $this->getSymbol(), + "prefix" => $this->getPrefix(), + "power" => $this->getPower()); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/SiPrefix/SiPrefixManager.php b/src/backend/PartKeepr/SiPrefix/SiPrefixManager.php @@ -0,0 +1,42 @@ +<?php +namespace PartKeepr\SiPrefix; + +use PartKeepr\Util\Singleton, + PartKeepr\PartKeepr; + +class SiPrefixManager extends Singleton { + public $siPrefixSymbolCache = array(); + + public function getSiPrefixBySymbol ($symbol) { + if (!is_array($this->siPrefixSymbolCache) || count($this->siPrefixSymbolCache) == 0) { + $this->createSiPrefixSymbolCache(); + } + + foreach ($this->siPrefixSymbolCache as $entry) { + if ($entry->getSymbol() == $symbol) { + return $entry; + } + } + + throw new \Exception(sprintf("Symbol '%s' not found", $symbol)); + } + + private function createSiPrefixSymbolCache () { + $dql = "SELECT sip FROM PartKeepr\SiPrefix\SiPrefix sip"; + $query = PartKeepr::getEM()->createQuery($dql); + + $this->siPrefixSymbolCache = $query->getResult(); + } + + public function siPrefixExists ($prefix) { + $dql = "SELECT COUNT(sip) FROM PartKeepr\SiPrefix\SiPrefix sip WHERE sip.prefix = :prefix"; + $query = PartKeepr::getEM()->createQuery($dql); + $query->setParameter("prefix", $prefix); + + if ($query->getSingleScalarResult() == 0) { + return false; + } else { + return true; + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/SiPrefix/SiPrefixService.php b/src/backend/PartKeepr/SiPrefix/SiPrefixService.php @@ -0,0 +1,27 @@ +<?php +namespace PartKeepr\SiPrefix; + +use PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\PartKeepr, + PartKeepr\Session\SessionManager; + +class SiPrefixService extends Service implements RestfulService { + public function get () { + $query = PartKeepr::getEM()->createQuery("SELECT si.id, si.prefix, si.symbol, si.power FROM PartKeepr\SiPrefix\SiPrefix si"); + + return array("data" => $query->getArrayResult()); + } + + public function create () { + throw new \Exception("Not yet implemented"); + } + + public function update () { + throw new \Exception("Not yet implemented"); + } + + public function destroy () { + throw new \Exception("Not yet implemented"); + } +} diff --git a/src/backend/PartKeepr/Statistic/StatisticService.php b/src/backend/PartKeepr/Statistic/StatisticService.php @@ -0,0 +1,148 @@ +<?php +namespace PartKeepr\Statistic; + +use PartKeepr\Part\PartUnit, + PartKeepr\Service\Service, + PartKeepr\PartKeepr, + PartKeepr\Part\PartManager, + PartKeepr\PartCategory\PartCategoryManager, + PartKeepr\PartUnit\PartUnitManager; + +class StatisticService extends Service { + public function getCurrentStats () { + + $aData = array(); + $aData["partCount"] = PartManager::getInstance()->getPartCount(); + $aData["categoryCount"] = PartCategoryManager::getInstance()->getCategoryCount(); + $aData["totalPrice"] = PartManager::getInstance()->getTotalPrice(); + $aData["averagePrice"] = PartManager::getInstance()->getAveragePrice(); + $aData["partsWithPrice"] = PartManager::getInstance()->getPartCount(true); + $aData["partsWithoutPrice"] = $aData["partCount"] - $aData["partsWithPrice"]; + + $result = PartUnitManager::getInstance()->getUnitCounts(); + + $aUnits = array(); + + foreach ($result as $row) { + $aUnits[] = array( + "name" => PartUnit::loadById($row["puid"])->getName(), + "stockLevel" => $row["stockLevel"]); + } + + $aData["units"] = $aUnits; + + return $aData; + } + + /** + * Returns the range of all recorded statistic snapshots. + */ + public function getStatisticRange () { + $dql = "SELECT MIN(sts.dateTime), MAX(sts.dateTime) FROM PartKeepr\Statistic\StatisticSnapshot sts"; + $query = PartKeepr::getEM()->createQuery($dql); + + $data = $query->getArrayResult(); + + return array("data" => array("start" => $data[0][1], "end" => $data[0][2])); + + } + /** + * Returns sampled statistics from the database. + * + * This call takes a start and an end time, and calculates a set of statistics + * for each interval. + * + * The sampleSize, which has a default of 50, specifies how many single statistic + * points in the given date interval will be returned. + * + * This function interpolates the statistics if there are not enough statistic samples available. + */ + public function getSampledStatistics () { + $fooStart = microtime(true); + + $this->requireParameter("startDateTime"); + $this->requireParameter("endDateTime"); + + + $start = \DateTime::createFromFormat("Y-m-d H:i:s", $this->getParameter("startDateTime")); + $end = \DateTime::createFromFormat("Y-m-d H:i:s", $this->getParameter("endDateTime")); + + if ($start->getTimestamp() > $end->getTimestamp()) { + // Swap both times + list($start, $end) = array($end, $start); + } + + if ($this->hasParameter("sampleSize")) { + $sampleSize = $this->getParameter("sampleSize"); + } else { + $sampleSize = 25; + } + + $intervalSize = intval(($end->getTimestamp() - $start->getTimestamp()) / $sampleSize); + + $queryStartTime = clone $start; + $queryEndTime = clone $start; + $queryEndTime->add(new \DateInterval("PT".$intervalSize."S")); + + $partUnitQuery = "SELECT pu FROM PartKeepr\Part\PartUnit pu"; + $query = PartKeepr::getEM()->createQuery($partUnitQuery); + + $aPartUnits = $query->getResult(); + + $aRecords = array(); + + $dql = "SELECT AVG(sts.parts) AS parts, AVG(sts.categories) AS categories FROM PartKeepr\Statistic\StatisticSnapshot sts WHERE sts.dateTime >= :start AND sts.dateTime <= :end"; + $mainQuery = PartKeepr::getEM()->createQuery($dql); + + $dql = "SELECT AVG(stsu.stockLevel) AS stockLevel FROM PartKeepr\Statistic\StatisticSnapshotUnit stsu JOIN stsu.statisticSnapshot sts WHERE sts.dateTime >= :start AND sts.dateTime <= :end AND stsu.partUnit = :partUnit"; + $subQuery = PartKeepr::getEM()->createQuery($dql); + + for ($i=0;$i<$sampleSize;$i++) { + + + $mainQuery->setParameter("start", $queryStartTime); + $mainQuery->setParameter("end", $queryEndTime); + + $result = $mainQuery->getResult(); + + $record = $result[0]; + + if ($record["parts"] !== null) { + $record["parts"] = floatval($record["parts"]); + } + + if ($record["categories"] !== null) { + $record["categories"] = floatval($record["categories"]); + } + + foreach ($aPartUnits as $partUnit) { + $subQuery->setParameter("start", $queryStartTime); + $subQuery->setParameter("end", $queryEndTime); + $subQuery->setParameter("partUnit", $partUnit); + + $aResult = $subQuery->getResult(); + + if ($aResult[0]["stockLevel"] !== null) { + $record["units"][$partUnit->getName()] = floatval($aResult[0]["stockLevel"]); + } else { + $record["units"][$partUnit->getName()] = null; + } + + } + + $record["start"] = $queryStartTime->format("Y-m-d H:i:s"); + + if ($record["parts"] !== null) { + $aRecords[] = $record; + } + + + $queryStartTime->add(new \DateInterval("PT".$intervalSize."S")); + $queryEndTime->add(new \DateInterval("PT".$intervalSize."S")); + } + + + return array("status" => "ok", "data" => $aRecords); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Statistic/StatisticSnapshot.php b/src/backend/PartKeepr/Statistic/StatisticSnapshot.php @@ -0,0 +1,109 @@ +<?php +namespace PartKeepr\Statistic; + +use PartKeepr\PartKeepr; + +/** @Entity **/ +class StatisticSnapshot { + /** + * @Id @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + * @var integer + */ + private $id; + + /** + * Defines the date when this snapshot has been taken + * @Column(type="datetime") + * @var DateTime + */ + private $dateTime; + + /** + * Defines the amount of different parts in the database + * @Column(type="integer") + * @var int + */ + private $parts; + + /** + * Defines the amount of categories + * @Column(type="integer") + * @var int + */ + private $categories; + + /** + * Holds all defined units in the database + * @OneToMany(targetEntity="PartKeepr\Statistic\StatisticSnapshotUnit",mappedBy="statisticSnapshot",cascade={"persist", "remove"}) + */ + private $units; + + /** + * Creates a new statistic snapshot + */ + public function __construct () { + $this->units = new \Doctrine\Common\Collections\ArrayCollection(); + $this->setDateTime(new \DateTime()); + } + + /** + * Sets the date+time for the snapshot + * @param \DateTime $dateTime The date+time for the snapshot + */ + public function setDateTime (\DateTime $dateTime) { + $this->dateTime = $dateTime; + } + + /** + * Returns the date+time for the snapshot + * @return DateTime The date+time for the snapshot + */ + public function getDateTime () { + return $this->dateTime; + } + + /** + * Sets the amount of overall parts for the snapshot + * @param int $parts The amount of parts + */ + public function setParts ($parts) { + $this->parts = $parts; + } + + /** + * Returns the amount of overall parts for the snapshot + * @return int The amount of parts + */ + public function getParts () { + return $this->parts; + } + + /** + * Sets the amount of categories for the snapshot + * @param int $categories The amount of categories + */ + public function setCategories ($categories) { + $this->categories = $categories; + } + + /** + * Returns the amount of categories + * @return int The amount of categories + */ + public function getCategories () { + return $this->categories; + } + + /** + * Returns the ID of this snapshot + * @return int The ID of this snapshot + */ + public function getId () { + return $this->id; + } + + public function getUnits () { + return $this->units; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Statistic/StatisticSnapshotManager.php b/src/backend/PartKeepr/Statistic/StatisticSnapshotManager.php @@ -0,0 +1,38 @@ +<?php +namespace PartKeepr\Statistic; + +use PartKeepr\Util\Singleton, + PartKeepr\Part\PartUnit, + PartKeepr\Part\PartManager, + PartKeepr\PartUnit\PartUnitManager, + PartKeepr\PartKeepr, + PartKeepr\PartCategory\PartCategoryManager, + PartKeepr\PartUnit\Exceptions\PartUnitNotFoundException; + +class StatisticSnapshotManager extends Singleton { + public function createSnapshot () { + + $snapshot = new StatisticSnapshot(); + $snapshot->setParts(PartManager::getInstance()->getPartCount()); + $snapshot->setCategories(PartCategoryManager::getInstance()->getCategoryCount()); + + $result = PartUnitManager::getInstance()->getUnitCounts(); + + foreach ($result as $row) { + $snapshotUnit = new StatisticSnapshotUnit(); + $snapshotUnit->setPartUnit(PartUnit::loadById($row["puid"])); + $snapshotUnit->setStatisticSnapshot($snapshot); + + if ($row["stockLevel"] !== null) { + $snapshotUnit->setStockLevel($row["stockLevel"]); + } else { + $snapshotUnit->setStockLevel(0); + } + + $snapshot->getUnits()->add($snapshotUnit); + } + + PartKeepr::getEM()->persist($snapshot); + PartKeepr::getEM()->flush(); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Statistic/StatisticSnapshotUnit.php b/src/backend/PartKeepr/Statistic/StatisticSnapshotUnit.php @@ -0,0 +1,95 @@ +<?php +namespace PartKeepr\Statistic; + +use PartKeepr\Statistic\StatisticSnapshot, + PartKeepr\Part\PartUnit, + PartKeepr\PartKeepr; + + +/** @Entity **/ +class StatisticSnapshotUnit { + /** + * @Id @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + * @var integer + */ + private $id; + + /** + * @ManyToOne(targetEntity="PartKeepr\Statistic\StatisticSnapshot") + * The statistic snapshot this entity belongs to + * @var StatisticSnapshot + */ + private $statisticSnapshot; + + /** + * @ManyToOne(targetEntity="PartKeepr\Part\PartUnit") + * The statistic snapshot this entity belongs to + * @var StatisticSnapshot + */ + private $partUnit; + + /** + * The stockLevel for the unit + * @Column(type="integer") + * @var int + */ + private $stockLevel; + + /** + * Sets the statistic snapshot this entity belongs to + * @param StatisticSnapshot $snapshot The snapshot + */ + public function setStatisticSnapshot (StatisticSnapshot $snapshot) { + $this->statisticSnapshot = $snapshot; + } + + /** + * Returns the snapshot this entity belongs to + * @return StatisticSnapshot The snapshot + */ + public function getStatisticSnapshot () { + return $this->statisticSnapshot; + } + + /** + * + * Sets the part unit for this entity + * @param PartUnit $unit The part unit + */ + public function setPartUnit (PartUnit $unit) { + $this->partUnit = $unit; + } + + /** + * Returns the part unit for this entity + * @return PartUnit The part unit + */ + public function getPartUnit () { + return $this->partUnit; + } + + /** + * Returns the ID of this statistic snapshot unit + * @return int The ID + */ + public function getId () { + return $this->id; + } + + /** + * Sets the stock level for this unit snapshot + * @param int $stockLevel + */ + public function setStockLevel ($stockLevel) { + $this->stockLevel = $stockLevel; + } + + /** + * Returns the stock level for this unit snapshot + * @return int The stock level + */ + public function getStockLevel () { + return $this->stockLevel; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Stock/StockEntry.php b/src/backend/PartKeepr/Stock/StockEntry.php @@ -0,0 +1,239 @@ +<?php +namespace PartKeepr\Stock; + +use PartKeepr\Part\Part, + PartKeepr\User\User, + PartKeepr\PartKeepr, + PartKeepr\Util\BaseEntity, + PartKeepr\Util\Serializable; + +/** @Entity @HasLifecycleCallbacks **/ +class StockEntry extends BaseEntity implements Serializable { + /** + * @Column(type="integer") + */ + private $stockLevel; + + /** + * @ManyToOne(targetEntity="PartKeepr\Part\Part") + */ + private $part; + + /** + * @ManyToOne(targetEntity="PartKeepr\User\User") + */ + private $user; + + /** + * @Column(type="decimal",precision=13,scale=4,nullable=true) + * @var float + */ + private $price; + + /** + * @Column(type="datetime") + * @var DateTime + */ + private $dateTime; + + /** + * Indicates if the stock level is a correction entry. + * + * @Column(type="boolean") + * @var boolean + */ + private $correction; + + /** + * @Column(type="string",nullable=true) + * @var string + */ + private $comment; + + + /** + * Creates a new stock entry. A stock entry tracks how many parts + * were the stockLevel is the amount of items added/removed, + * by which user and how much the user paid for it (for adding parts only!) + * + * @param Part $part The part which was added/removed + * @param int $stockLevel The stock level. Positive value means added parts, negative values means removed parts. + * @param User $user The user who removed/added parts + */ + public function __construct (Part $part, $stockLevel, User $user = null) { + $this->setPart($part); + $this->setStockLevel($stockLevel); + $this->setUser($user); + $this->setDateTime(new \DateTime()); + $this->setCorrection(false); + } + + + /** + * Sets the date+time + * @param \DateTime $dateTime The date+time + */ + private function setDateTime (\DateTime $dateTime) { + $this->dateTime = $dateTime; + } + + /** + * Returns the date+time when the record was created. + * @return \DateTime The date+time when the record was created + */ + public function getDateTime () { + return $this->dateTime; + } + + /** + * Sets if the stock entry is a correction record. + * @param $bCorrection boolean True if the record is a correction record, false otherwise + */ + public function setCorrection ($bCorrection) { + $this->correction = $bCorrection; + } + + /** + * Returns if the entry is a correction entry. + * @return boolean True if the entry is a correction entry, false otherwise + */ + public function getCorrection () { + return $this->correction; + } + + /** + * Sets the price for the item stored. + * + * Please note that the price is for a single item only, and can be null. + * @param float $price The price to set + */ + public function setPrice ($price) { + $this->price = $price; + } + + /** + * Returns the price for this entry. The price is for a single item only. + * + * @return float The price for this entry. + */ + public function getPrice () { + return $this->price; + } + + /** + * Sets the stock level for this entry. + * + * Negative values means part removal, positive values means part adding. + * @param int $stockLevel The stock level + */ + public function setStockLevel($stockLevel) { + $this->stockLevel = $stockLevel; + } + + /** + * Returns the stock level for this entry. + * @return int The stock level + */ + public function getStockLevel () { + return $this->stockLevel; + } + + /** + * Sets the part assigned to this entry. + * @param Part $part The part to set + */ + public function setPart (Part $part) { + $this->part = $part; + } + + /** + * Returns the part assigned to this entry. + * @return Part $part The part + */ + public function getPart () { + return $this->part; + } + + /** + * Sets the user assigned to this entry. + * @param User $user The user The user to set + */ + public function setUser (User $user = null) { + $this->user = $user; + } + + /** + * Returns the user for this entry + * @return User the user + */ + public function getUser () { + return $this->user; + } + + /** + * If the stock level is negative, we can't have a price. + * @PrePersist + */ + public function checkPrice () { + if ($this->getStockLevel() < 0 && $this->getPrice() !== null) { + $this->setPrice(null); + } + } + + /** + * Updates the stock leve for a part + * @PostPersist + */ + public function postPersist () { + $this->part->updateStockLevel(); + $this->part->updatePrice(); + } + + /** + * Returns if the current stock entry is a removal. + * @return boolean True if the entry is a removal, false otherwise + */ + public function isRemoval () { + if ($this->getStockLevel() < 0) { + return true; + } else { + return false; + } + } + + /** + * Sets a comment + * @param string $comment + */ + public function setComment ($comment) { + $this->comment = $comment; + } + + /** + * Returns the comment + * @return string The comment + */ + public function getComment () { + return $this->comment; + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Util.Serializable::serialize() + */ + public function serialize () { + return array( + "id" => $this->getId(), + "part_name" => $this->getPart()->getName(), + "part_id" => $this->getPart()->getId(), + "storageLocation_name" => $this->getPart()->getStorageLocation()->getName(), + "username" => is_object($this->getUser()) ? $this->getUser()->getUsername() : PartKeepr::i18n("Unknown User"), + "user_id" => is_object($this->getUser()) ? $this->getUser()->getId() : null, + "stockLevel" => abs($this->getStockLevel()), + "comment" => $this->getComment(), + "dateTime" => $this->getDateTime()->format("Y-m-d H:i:s"), + "direction" => ($this->getStockLevel() < 0) ? "out" : "in", + "price" => $this->getPrice() + ); + } +} diff --git a/src/backend/PartKeepr/Stock/StockManager.php b/src/backend/PartKeepr/Stock/StockManager.php @@ -0,0 +1,66 @@ +<?php +namespace PartKeepr\Stock; + +use PartKeepr\Manager\AbstractManager, + Doctrine\ORM\QueryBuilder, + PartKeepr\Manager\ManagerFilter, + PartKeepr\PartKeepr; + +class StockManager extends AbstractManager { + /** + * Returns the FQCN for the target entity to operate on. + * @return string The FQCN, e.g. PartKeepr\Part + */ + public function getEntityName () { + return 'PartKeepr\Stock\StockEntry'; + } + + /** + * Returns all fields which need to appear in the getList ResultSet. + * @return array An array of all fields which should be returned + */ + public function getQueryFields () { + return array(); + } + + /** + * Returns the default sort field + * + * @return string The default sort field + */ + public function getDefaultSortField () { + return "dateTime"; + } + + /** + * Applies a custom query to the QueryBuilder + * + * @param QueryBuilder $qb The query builder + * @param ManagerFilter $filter The query filter + */ + protected function applyCustomQuery (QueryBuilder $qb, ManagerFilter $filter) { + + // Apply special handling for non-direct fields in relations, where the frontend has no idea about. + foreach ($filter->getSorters() as $sorter) { + switch ($sorter->getSortField()) { + case "q.part_name": + $qb->join("q.part", "p"); + $sorter->setSortField("p.name"); + break; + case "q.user_id": + $qb->leftJoin("q.user", "u"); + $sorter->setSortField("u.username"); + break; + case "q.direction": + $sorter->setSortField("q.dateTime"); + break; + case "q.storageLocation_name": + $qb->join("q.part", "p")->join("p.storageLocation", "st"); + $sorter->setSortField("st.name"); + break; + default: + break; + } + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Stock/StockService.php b/src/backend/PartKeepr/Stock/StockService.php @@ -0,0 +1,93 @@ +<?php +namespace PartKeepr\Stock; + +use PartKeepr\Stock\StockEntry, + PartKeepr\PartKeepr, + PartKeepr\User\User, + PartKeepr\Manager\ManagerFilter, + PartKeepr\Session\SessionManager, + PartKeepr\Service\RestfulService, + PartKeepr\Service\Service; + +class StockService extends Service implements RestfulService { + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::get() + */ + public function get () { + if ($this->hasParameter("id")) { + return array("data" => StockManager::getInstance()->getEntity($this->getParameter("id"))->serialize()); + } else { + $parameters = new ManagerFilter($this); + $parameters->setFilterField("name"); + + if ($this->hasParameter("part")) { + $parameters->setFilterCallback(array($this, "filterCallback")); + } + return StockManager::getInstance()->getList($parameters); + } + } + + /** + * If the "part" parameter is set, join the part into the result and filter on that + * @param QueryBuilder The $queryBuilder + */ + public function filterCallback ($queryBuilder) { + $queryBuilder->andWhere("q.part = :part"); + $queryBuilder->setParameter("part", $this->getParameter("part")); + } + + public function create () { + throw new \Exception("Not yet implemented"); + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::update() + */ + public function update () { + $this->requireParameter("id"); + + $stockEntry = StockEntry::loadById($this->getParameter("id")); + + if (!SessionManager::getCurrentSession()->getUser()->isAdmin() && + !(SessionManager::getCurrentSession()->getUser() && $stockEntry->getUser() && SessionManager::getCurrentSession()->getUser()->getId() == $stockEntry->getUser()->getId() )) { + throw new \Exception("Permission denied"); + } + + /* It's not allowed to edit a price for a removal */ + if (!$stockEntry->isRemoval()) { + $stockEntry->setPrice(abs($this->getParameter("price"))); + } + + /** + * Only an admin user may correct the in&out stock levels + */ + if (SessionManager::getCurrentSession()->getUser()->isAdmin()) { + if ($this->getParameter("direction") == "out") { + $stockEntry->setStockLevel(-(abs($this->getParameter("stockLevel")))); + } else { + $stockEntry->setStockLevel($this->getParameter("stockLevel")); + } + + } + + if (SessionManager::getCurrentSession()->getUser()->isAdmin()) { + try { + $stockEntry->setUser(User::loadById($this->getParameter("user_id"))); + } catch (\Exception $e) { + $stockEntry->setUser(null); + } + + } + + $stockEntry->setComment($this->getParameter("comment")); + PartKeepr::getEM()->flush(); + + return array("data" => $stockEntry->serialize()); + } + + public function destroy () { + throw new \Exception("Not yet implemented"); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/StorageLocation/Exceptions/StorageLocationNotFoundException.php b/src/backend/PartKeepr/StorageLocation/Exceptions/StorageLocationNotFoundException.php @@ -0,0 +1,12 @@ +<?php +namespace PartKeepr\StorageLocation\Exceptions; + +use PartKeepr\Util\SerializableException, + PartKeepr\PartKeepr; + +class StorageLocationNotFoundException extends SerializableException { + public function __construct () { + parent::__construct(PartKeepr::i18n("Storage Location not found.")); + } +} +?>+ \ No newline at end of file diff --git a/src/backend/PartKeepr/StorageLocation/StorageLocation.php b/src/backend/PartKeepr/StorageLocation/StorageLocation.php @@ -0,0 +1,105 @@ +<?php +namespace PartKeepr\StorageLocation; + +use PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable, + PartKeepr\Util\BaseEntity; + +/** @Entity **/ +class StorageLocation extends BaseEntity implements Serializable, Deserializable { + /** + * Holds the name for our storage location + * @Column(type="string",unique=true) + * @var string + */ + private $name; + + /** + * Holds the storage location image + * @OneToOne(targetEntity="PartKeepr\StorageLocation\StorageLocationImage",mappedBy="storageLocation",cascade={"persist", "remove"}) + * @var StorageLocationImage + */ + private $image; + + /** + * Sets the name for the storage location + * @param string $name the name to set + */ + public function setName ($name) { + $this->name = $name; + } + + /** + * Returns the name of the storage location + * @return string The name + */ + public function getName () { + return $this->name; + } + + /** + * Sets the storage location image + * @param StorageLocationImage $image The storage location image + */ + public function setImage (StorageLocationImage $image) { + $this->image = $image; + $image->setStorageLocation($this); + } + + /** + * Returns the storage location image + * @return StorageLocationImage The storage location image + */ + public function getImage () { + return $this->image; + } + + /** + * Returns this storage location in serialized form + * @return array The serialized storage location + */ + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "image_id" => is_object($this->getImage()) ? $this->getImage()->getId() : null); + } + + /** + * Deserializes the storage location + * @param array $parameters The array with the parameters to set + */ + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "name": + $this->setName($value); + break; + case "image_id": + if ($value == "") { + echo "/** Breaking because of empty value */"; + break; + } + + try { + $image = StorageLocationImage::loadById($value); + $this->setImage($image); + } catch (\Exception $e) { + if ($this->getImage()) { + // Image was not found, maybe a temporary image? + $this->getImage()->replaceFromTemporaryFile($value); + } else { + $image = StorageLocationImage::createFromTemporaryFile($value); + $this->setImage($image); + echo "/**"; + echo $image->getId(); + echo "*/"; + echo "/** FOO */"; + } + } + + break; + } + } + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/StorageLocation/StorageLocationImage.php b/src/backend/PartKeepr/StorageLocation/StorageLocationImage.php @@ -0,0 +1,50 @@ +<?php +namespace PartKeepr\StorageLocation; + +use PartKeepr\Util\Serializable, + PartKeepr\Image\Image; + +/** + * Holds a storage location image + * @Entity + **/ +class StorageLocationImage extends Image implements Serializable { + /** + * The storage location object + * @OneToOne(targetEntity="PartKeepr\StorageLocation\StorageLocation",inversedBy="image") + * @var StorageLocation + */ + private $storageLocation = null; + + /** + * Creates a new storage location image instance + */ + public function __construct () { + parent::__construct(Image::IMAGE_STORAGELOCATION); + } + + /** + * Sets the storage location + * @param StorageLocation $storageLocation The storage location to set + */ + public function setStorageLocation (StorageLocation $storageLocation) { + $this->storageLocation = $storageLocation; + } + + /** + * Returns the storage location + * @return StorageLocation the storage location + */ + public function getStorageLocation () { + return $this->storageLocation; + } + + /** + * + * Serializes this storage location image + * @return array The serialized storage location image + */ + public function serialize () { + return array("id" => $this->getId(), "storageLocation_id" => $this->getStorageLocation()->getId()); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/StorageLocation/StorageLocationManager.php b/src/backend/PartKeepr/StorageLocation/StorageLocationManager.php @@ -0,0 +1,94 @@ +<?php +namespace PartKeepr\StorageLocation; + +use PartKeepr\Util\Singleton, + PartKeepr\StorageLocation\StorageLocation, + PartKeepr\PartKeepr, + PartKeepr\Category\CategoryManager, + PartKeepr\StorageLocation\Exceptions\StorageLocationNotFoundException; + +class StorageLocationManager extends Singleton { + public function getStorageLocations ($start = 0, $limit = 10, $sort = "footprint", $dir = "asc", $filter = "") { + + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select("st.id, st.name")->from("PartKeepr\StorageLocation\StorageLocation","st"); + + if ($filter != "") { + $qb = $qb->where("LOWER(st.name) LIKE :filter"); + $qb->setParameter("filter", "%".strtolower($filter)."%"); + } + + if ($limit > -1) { + $qb->setMaxResults($limit); + $qb->setFirstResult($start); + } + + $qb->orderBy("st.".$sort, $dir); + + $query = $qb->getQuery(); + + $result = $query->getResult(); + + $totalQueryBuilder = PartKeepr::getEM()->createQueryBuilder(); + $totalQueryBuilder->select("COUNT(st.id)")->from("PartKeepr\StorageLocation\StorageLocation","st"); + + + + if ($filter != "") { + $totalQueryBuilder = $totalQueryBuilder->where("LOWER(st.name) LIKE :filter"); + $totalQueryBuilder->setParameter("filter", "%".strtolower($filter)."%"); + } + + $totalQuery = $totalQueryBuilder->getQuery(); + + return array("data" => $result, "start" => $start, "totalCount" => $totalQuery->getSingleScalarResult()); + } + + public function getStorageLocation ($id) { + $storageLocation = PartKeepr::getEM()->find("PartKeepr\StorageLocation\StorageLocation", $id); + + if ($storageLocation) { + return $storageLocation; + } else { + throw new StorageLocationNotFoundException(); + } + } + + public function getStorageLocationByName ($name) { + $query = PartKeepr::getEM()->createQuery("SELECT s FROM PartKeepr\StorageLocation\StorageLocation s WHERE s.name = :name"); + $query->setParameter("name", $name); + + return $query->getSingleResult(); + } + + public function addStorageLocation ($name) { + $storageLocation = new StorageLocation(); + $storageLocation->setName($name); + + PartKeepr::getEM()->persist($storageLocation); + PartKeepr::getEM()->flush(); + + return $storageLocation; + } + public function deleteStorageLocation ($id) { + $storageLocation = $this->getStorageLocation($id); + + PartKeepr::getEM()->remove($storageLocation); + PartKeepr::getEM()->flush(); + } + + public function getOrCreateStorageLocation ($storageLocation) { + if (is_int($storageLocation)) { + try { + return $this->getStorageLocation($storageLocation); + } catch (StorageLocationNotFoundException $e) {} + } + + $sl = new StorageLocation(); + $sl->setName($storageLocation); + + PartKeepr::getEM()->persist($sl); + + return $sl; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/StorageLocation/StorageLocationService.php b/src/backend/PartKeepr/StorageLocation/StorageLocationService.php @@ -0,0 +1,108 @@ +<?php +namespace PartKeepr\StorageLocation; + +use PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\Part\PartManager, + PartKeepr\Util\SerializableException, + PartKeepr\Stock\StockEntry, + PartKeepr\PartKeepr, + PartKeepr\Session\SessionManager; + +class StorageLocationService extends Service implements RestfulService { + + public function get () { + if ($this->hasParameter("id")) { + return array("data" => StorageLocationManager::getInstance()->getStorageLocation($this->getParameter("id"))->serialize()); + } else { + if ($this->hasParameter("sort")) { + $tmp = json_decode($this->getParameter("sort"), true); + + $aSortParams = $tmp[0]; + } else { + $aSortParams = array( + "property" => "name", + "direction" => "ASC"); + } + return StorageLocationManager::getInstance()->getStorageLocations( + $this->getParameter("start", $this->getParameter("start", 0)), + $this->getParameter("limit", $this->getParameter("limit", 25)), + $this->getParameter("sortby", $aSortParams["property"]), + $this->getParameter("dir", $aSortParams["direction"]), + $this->getParameter("query", "")); + } + } + + public function create () { + $this->requireParameter("name"); + + $storageLocation = new StorageLocation(); + $storageLocation->deserialize($this->getParameters()); + + PartKeepr::getEM()->persist($storageLocation); + + try { + PartKeepr::getEM()->flush(); + } catch (\PDOException $e) { + if ($e->getCode() == "23505") { + $exception = new SerializableException(sprintf(PartKeepr::i18n("Storage Location %s already exists!"), $storageLocation->getName())); + $exception->setDetail(sprintf(PartKeepr::i18n("You tried to add the storage location %s, but a storage location with the same name already exists."), $storageLocation->getName())); + + throw $exception; + } else { + throw $e; + } + } + + + return array("data" => $storageLocation->serialize()); + } + + public function update () { + $this->requireParameter("id"); + $this->requireParameter("name"); + $storageLocation = StorageLocationManager::getInstance()->getStorageLocation($this->getParameter("id")); + $storageLocation->deserialize($this->getParameters()); + + PartKeepr::getEM()->flush(); + + return array("data" => $storageLocation->serialize()); + + } + + public function destroy () { + $this->requireParameter("id"); + + StorageLocationManager::getInstance()->deleteStorageLocation($this->getParameter("id")); + + return array("data" => null); + } + + /** + * Creates multiple storage locations at once. + * + * Requires that the parameter "storageLocations" is set to an array with the names of the storage locations. + * Returns all error messages as "data" index in the result array. + */ + public function massCreate () { + $this->requireParameter("storageLocations"); + + $aMessages = array(); + + foreach ($this->getParameter("storageLocations") as $storageLocation) { + try { + $obj = StorageLocationManager::getInstance()->getStorageLocationByName($storageLocation); + $aMessages[] = sprintf(PartKeepr::i18n("Storage Location %s already exists"), $storageLocation); + } catch (\Exception $e) { + $obj = new StorageLocation(); + $obj->setName($storageLocation); + PartKeepr::getEM()->persist($obj); + } + + } + + PartKeepr::getEM()->flush(); + + return array("data" => $aMessages); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/System/SystemInformationRecord.php b/src/backend/PartKeepr/System/SystemInformationRecord.php @@ -0,0 +1,55 @@ +<?php +namespace PartKeepr\System; + +/** + * This class represents a system information record. + * + * This is basically a category, a name and a value. No logic included within + * the class. + * + * For example, records could hold: + * + * Name Value Category + * ===================================================================================== + * Doctrine ORM 2.1.0 Libraries + * Doctrine DBAL 2.1.0 Libraries + * Doctrine Migrations git-f87afe9223dbfecaaddb Libraries + * + * PHP Version 5.3.2 Server Software + * Operating System Linux (Funtoo Linux - baselayout 2.1.8) Server Software + + * @author felicitus + * + */ +class SystemInformationRecord { + /** + * Holds the category name + * @var string + */ + public $category; + + /** + * Holds the name + * @var string + */ + public $name; + + /** + * Holds the value + * @var mixed + */ + public $value; + + /** + * Creates a new system information record. + * + * @param string $name + * @param mixed $value + * @param string $category + */ + public function __construct ($name, $value, $category) { + $this->name = $name; + $this->value = $value; + $this->category = $category; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/System/SystemService.php b/src/backend/PartKeepr/System/SystemService.php @@ -0,0 +1,99 @@ +<?php +namespace PartKeepr\System; + +use PartKeepr\Util\Configuration, + PartKeepr\Service\Service, + PartKeepr\PartKeepr, + PartKeepr\CronLogger\CronLoggerManager, + PartKeepr\Util\OS\OperatingSystem; + +class SystemService extends Service { + /** + * Returns a list of system information records. + * + * Please note that it is not defined which information is returned; the result + * should be seen as "informational" to the system operator, not for automated purposes. + */ + public function getSystemInformation () { + $aData = array(); + + $aData[] = new SystemInformationRecord("Doctrine ORM", \Doctrine\ORM\Version::VERSION, "Libraries"); + $aData[] = new SystemInformationRecord("Doctrine DBAL", \Doctrine\DBAL\Version::VERSION, "Libraries"); + + $aData[] = new SystemInformationRecord("PHP Version", phpversion(), "System"); + + $os = new OperatingSystem(); + + $aData[] = new SystemInformationRecord("Operating System Type", $os->getPlatform(), "System"); + $aData[] = new SystemInformationRecord("Operating System Release", $os->getRelease(), "System"); + + $aData[] = new SystemInformationRecord("memory_limit", ini_get("memory_limit"), "PHP"); + $aData[] = new SystemInformationRecord("post_max_size", ini_get("post_max_size"), "PHP"); + $aData[] = new SystemInformationRecord("upload_max_filesize", ini_get("upload_max_filesize"), "PHP"); + $aData[] = new SystemInformationRecord("post_max_size", ini_get("post_max_size"), "PHP"); + $aData[] = new SystemInformationRecord("allow_url_fopen", ini_get("allow_url_fopen"), "PHP"); + $aData[] = new SystemInformationRecord("max_execution_time", ini_get("max_execution_time"), "PHP"); + $aData[] = new SystemInformationRecord("APC enabled", (extension_loaded("apc") ? PartKeepr::i18n("Yes") : PartKeepr::i18n("No")), "PHP"); + + $aData[] = new SystemInformationRecord("PartKeepr Version", PartKeepr::getVersion(), "PartKeepr"); + + + foreach (Configuration::getOptions() as $key => $value) { + + // Hide passwords + if ($key == "partkeepr.database.password" || $key == "partkeepr.migration.partdb.password") { + $value = "<hidden>"; + } + + $aData[] = new SystemInformationRecord($key, $value, "PartKeepr Configuration Information"); + } + + return array("data" => $aData); + } + + /** + * Returns the database schema status. + * + * This method is usuall called once the user logs in, and alerts him if the schema is not up-to-date. + * + * Returns either status incomplete if the schema is not up-to-date, or complete if everything is OK. + */ + public function getSystemStatus () { + + if (Configuration::getOption("partkeepr.cronjobs.disablecheck", false) === true) { + // Skip cronjob tests + $inactiveCronjobs = array(); + } else { + $inactiveCronjobs = CronLoggerManager::getInstance()->getInactiveCronjobs(); + } + + + return array("data" => + array( + "inactiveCronjobCount" => count($inactiveCronjobs), + "inactiveCronjobs" => $inactiveCronjobs, + "schemaStatus" => $this->getSchemaStatus())); + } + + /** + * Checks if the schema is up-to-date. If yes, it returns "complete", if not, it returns "incomplete". + * + * @param none + * @return string Either "complete" or "incomplete" + */ + protected function getSchemaStatus () { + $metadatas = PartKeepr::getEM()->getMetadataFactory()->getAllMetadata(); + + $schemaTool = new \Doctrine\ORM\Tools\SchemaTool(PartKeepr::getEM()); + + $queries = $schemaTool->getUpdateSchemaSql($metadatas, true); + + if (count($queries) > 0) { + return "incomplete"; + } else { + return "complete"; + } + } + + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/SystemNotice/SystemNotice.php b/src/backend/PartKeepr/SystemNotice/SystemNotice.php @@ -0,0 +1,141 @@ +<?php +namespace PartKeepr\SystemNotice; + +use PartKeepr\UploadedFile\UploadedFile, + PartKeepr\Util\BaseEntity, + PartKeepr\Util\Serializable, + PartKeepr\Util\Deserializable; + +/** + * Holds a system notice + * @Entity + **/ +class SystemNotice extends BaseEntity implements Serializable { + /** + * @Column(type="datetime") + * @var \DateTime + */ + private $date; + + /** + * @Column(type="string") + * @var string + */ + private $title; + + /** + * The description of this attachment + * @Column(type="text") + * @var string + */ + private $description; + + /** + * Defines if the system notice has been acknowledged + * @Column(type="boolean") + * @var boolean + */ + private $acknowledged = false; + + /** + * Specifies the type. This is required for unique notices which shouldn't pop up every time we create them. + * @Column(type="string") + * @var string + */ + private $type; + + /** + * Sets the date and time for this entry + * @param \DateTime $date The date and time + */ + public function setDate (\DateTime $date) { + $this->date = $date; + } + + /** + * Returns the date and time for this entry + * + * @return \DateTime the date and time for this entry + */ + public function getDate () { + return $this->date; + } + + /** + * Sets the title for this entry + * @param string $title the title for this entry + */ + public function setTitle ($title) { + $this->title = $title; + } + + /** + * Returns the title for this entry + * @return string the title + */ + public function getTitle () { + return $this->title; + } + + /** + * Sets the description + * @param string $description + */ + public function setDescription ($description) { + $this->description = $description; + } + + /** + * Returns the description + * @return string The description + */ + public function getDescription () { + return $this->description; + } + + /** + * Sets the value of the acknowledged flag + * @param boolean $bAck True if the notice should be acknowledged (default), false otherwise + */ + public function setAcknowledgedFlag ($bAck = true) { + $this->acknowledged = $bAck; + } + + /** + * Returns the value of the acknowledged flag + * + * @return boolean true if this notice has been acknowledged, false otherwise + */ + public function getAcknowledgedFlag () { + return $this->acknowledged; + } + + /** + * Sets the type of this entry + * @param string $type + */ + public function setType ($type) { + $this->type = $type; + } + + /** + * Returns the type of this entry + * @return string The type + */ + public function getType () { + return $this->type; + } + + /** + * Serializes this system notice attachment + * @return array The serialized system notice + */ + public function serialize () { + return array( + "id" => $this->getId(), + "date" => $this->getDate()->format("Y-m-d H:i:s"), + "title" => $this->getTitle(), + "description" => $this->getDescription(), + "acknowledged" => $this->getAcknowledgedFlag()); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/SystemNotice/SystemNoticeManager.php b/src/backend/PartKeepr/SystemNotice/SystemNoticeManager.php @@ -0,0 +1,55 @@ +<?php +namespace PartKeepr\SystemNotice; + +use PartKeepr\Manager\AbstractManager, + PartKeepr\Project\Project, + PartKeepr\PartKeepr; + +class SystemNoticeManager extends AbstractManager { + /** + * Returns the FQCN for the target entity to operate on. + * @return string The FQCN, e.g. PartKeepr\Part + */ + public function getEntityName () { + return 'PartKeepr\SystemNotice\SystemNotice'; + } + + /** + * Returns all fields which need to appear in the getList ResultSet. + * @return array An array of all fields which should be returned + */ + public function getQueryFields () { + return array("id", "title", "date"); + } + + /** + * Returns the default sort field + * + * @return string The default sort field + */ + public function getDefaultSortField () { + return "date"; + } + + public function createUniqueSystemNotice ($type, $title, $description) { + $dql = "SELECT sn FROM PartKeepr\SystemNotice\SystemNotice sn WHERE sn.type = :type"; + $query = PartKeepr::getEM()->createQuery($dql); + + $query->setParameter("type", $type, \PDO::PARAM_BOOL); + + try { + $notice = $query->getSingleResult(); + } catch (\Exception $e) { + $notice = new SystemNotice(); + PartKeepr::getEM()->persist($notice); + } + + $notice->setDate(new \DateTime()); + $notice->setTitle($title); + $notice->setDescription($description); + $notice->setType($type); + + PartKeepr::getEM()->flush(); + + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/SystemNotice/SystemNoticeService.php b/src/backend/PartKeepr/SystemNotice/SystemNoticeService.php @@ -0,0 +1,83 @@ +<?php +namespace PartKeepr\SystemNotice; + +use PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\PartKeepr, + PartKeepr\Manager\ManagerFilter; + +class SystemNoticeService extends Service implements RestfulService { + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::get() + */ + public function get () { + if ($this->hasParameter("id")) { + return array("data" => SystemNoticeManager::getInstance()->getEntity($this->getParameter("id"))->serialize()); + } else { + $parameters = new ManagerFilter($this); + $parameters->setFilterCallback(array($this, "filterCallback")); + + return SystemNoticeManager::getInstance()->getList($parameters); + } + } + + public function filterCallback ($queryBuilder) { + $queryBuilder->andWhere("q.acknowledged = :acknowledged"); + $queryBuilder->setParameter("acknowledged", false, \PDO::PARAM_BOOL); + } + /** + * Stub method to fulfill the RestfulService. We don't want to have SystemNotices created by the user, so + * we bail out with an exception. + * + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::create() + */ + public function create () { + throw new \Exception("Not implemented"); + } + + /** + * Stub method to fulfill the RestfulService. We don't want to have SystemNotices updated by the user, so + * we bail out with an exception. + * + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::update() + */ + public function update () { + throw new \Exception("Not implemented"); + + } + + /** + * (non-PHPdoc) + * @see PartKeepr\Service.RestfulService::destroy() + */ + public function destroy () { +