partkeepr

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

commit e36f073deb581c23a9699ccdabe9d4d86b8dab04
parent ae1bcfc9c787a3be47de5e0c583f53bb812c87da
Author: Timo A. Hummel <felicitus@felicitus.org>
Date:   Sun, 27 Jan 2013 07:10:51 -0800

Merge pull request #275 from Boldie/master

Implementing a way to print labels as PDF
Diffstat:
Mdocumentation/developer/SUBMITTING-CODE | 4++--
Msrc/backend/PartKeepr/Auth/AuthService.php | 6++++--
Asrc/backend/PartKeepr/EventNotification/Event.php | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/EventNotification/EventManager.php | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/EventNotification/EventNotificationService.php | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/EventNotification/LastNotification.php | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/EventNotification/LastNotificationManager.php | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backend/PartKeepr/Part/Part.php | 7+++++++
Asrc/backend/PartKeepr/Printing/Exceptions/InvalidArgumentException.php | 12++++++++++++
Asrc/backend/PartKeepr/Printing/Exceptions/RendererNotFoundException.php | 13+++++++++++++
Asrc/backend/PartKeepr/Printing/PageBasicLayout/PageBasicLayout.php | 224+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/PageBasicLayout/PageBasicLayoutManager.php | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/PageBasicLayout/PageBasicLayoutService.php | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/PrintingJob/PrintingJob.php | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/PrintingJob/PrintingJobManager.php | 35+++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/PrintingJob/PrintingJobService.php | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/PrintingJobConfiguration/PrintingJobConfiguration.php | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/PrintingJobConfiguration/PrintingJobConfigurationManager.php | 36++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/PrintingJobConfiguration/PrintingJobConfigurationService.php | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/PrintingService.php | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/Renderer/PDFDefaultRenderer.php | 279+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/Renderer/TCPDFAbstractRenderer.php | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/Renderer/ZebraLabelWriterRenderer.php | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/RendererFactoryIfc.php | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/RendererFactoryRegistry.php | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/RendererIfc.php | 31+++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/SimpleRendererFactory.php | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/Utils/DecodeConfiguration.php | 32++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/Utils/PercentOrNumericHelper.php | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Printing/Utils/Placeholder.php | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backend/PartKeepr/REST/Request.php | 23+++++++++++++++--------
Asrc/backend/PartKeepr/Service/FilterExtractor.php | 38++++++++++++++++++++++++++++++++++++++
Msrc/backend/PartKeepr/Service/ServiceManager.php | 8++++++++
Msrc/backend/PartKeepr/Session/Session.php | 4++++
Msrc/backend/PartKeepr/User/User.php | 18++++++++++++++++++
Msrc/backend/PartKeepr/User/UserManager.php | 64++++++++++++++++------------------------------------------------
Msrc/backend/PartKeepr/User/UserService.php | 53++++++++++++++++++++++++++++++++++-------------------
Asrc/backend/PartKeepr/Util/Exceptions/ObjectNotFoundException.php | 17+++++++++++++++++
Asrc/backend/PartKeepr/Util/SimpleObjectManager.php | 40++++++++++++++++++++++++++++++++++++++++
Msrc/frontend/file.php | 4++++
Msrc/frontend/js/Components/Editor/EditorComponent.js | 2+-
Msrc/frontend/js/Components/Grid/GridMenuPlugin.js | 20++++++++++++++++++++
Msrc/frontend/js/Components/MenuBar.js | 48+++++++++++++++++++++++++++++++++++++++++++++---
Asrc/frontend/js/Components/Printing/PageBasicLayoutEditor.js | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/frontend/js/Components/Printing/PageBasicLayoutEditorComponent.js | 20++++++++++++++++++++
Asrc/frontend/js/Components/Printing/PageBasicLayoutGrid.js | 15+++++++++++++++
Asrc/frontend/js/Components/Printing/PrintStorageLocations.js | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/frontend/js/Components/Printing/PrintingExecutor.js | 38++++++++++++++++++++++++++++++++++++++
Asrc/frontend/js/Components/Printing/PrintingJobConfigurationEditor.js | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/frontend/js/Components/Printing/PrintingJobConfigurationEditorComponent.js | 20++++++++++++++++++++
Asrc/frontend/js/Components/Printing/PrintingJobConfigurationGrid.js | 15+++++++++++++++
Asrc/frontend/js/Components/Printing/PrintingWindow.js | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/frontend/js/Components/Project/ProjectEditor.js | 2+-
Asrc/frontend/js/Models/PrintingPageBasicLayout.js | 21+++++++++++++++++++++
Asrc/frontend/js/Models/PrintingPrintingJobConfiguration.js | 20++++++++++++++++++++
Asrc/frontend/js/Models/PrintingRenderer.js | 11+++++++++++
Asrc/frontend/js/Models/PrintingResponse.js | 7+++++++
Asrc/frontend/js/Models/PrintingType.js | 11+++++++++++
58 files changed, 3336 insertions(+), 84 deletions(-)

diff --git a/documentation/developer/SUBMITTING-CODE b/documentation/developer/SUBMITTING-CODE @@ -52,7 +52,7 @@ JavaScript code should be checked with jslint prior to committing in git. Get and install 'JavaScript Lint' (jsl) - Linux - Download jsl source tarball - http://http://www.javascriptlint.com/download.htm + http://www.javascriptlint.com/download.htm - Extract the tarball tar -xzf jsl-0.3.0-src.tar.gz - Switch to the src directory @@ -67,7 +67,7 @@ Get and install 'JavaScript Lint' (jsl) - As per Linux - Windows - Download jsl - http://http://www.javascriptlint.com/download.htm + http://www.javascriptlint.com/download.htm - Extract 'jsl.exe' from the zip file - Move 'jsl.exe' to a location on the system path diff --git a/src/backend/PartKeepr/Auth/AuthService.php b/src/backend/PartKeepr/Auth/AuthService.php @@ -27,12 +27,12 @@ class AuthService extends AnonService { * documentation="Authenticates a user and starts a new session upon success.", * returnValues={ * @ServiceReturnValue( - * name="sessionid", + * name="username", * type="string:50", * description="The logged in username" * ), * @ServiceReturnValue( - * name="username", + * name="sessionid", * type="string:50", * description="The session ID" * ), @@ -76,6 +76,8 @@ class AuthService extends AnonService { /* Start Session */ $session = SessionManager::getInstance()->startSession($authenticatedUser); + $session->getUser()->updateSeen(); + $aPreferences = array(); foreach ($session->getUser()->getPreferences() as $result) { diff --git a/src/backend/PartKeepr/EventNotification/Event.php b/src/backend/PartKeepr/EventNotification/Event.php @@ -0,0 +1,60 @@ +<?php +namespace PartKeepr\EventNotification; + +use PartKeepr\Util\BaseEntity; + +/** + * The event notification conecpt implements the main concept of notifying a + * client on a special event he has registered for. + * The EventNotification is used to carry events from one request to a + * process which is polling for this event using a long poll or a complete + * different kind of service (like a Websocket if possible). + * + * The Event class itself represents a type of event we + * can signal. + * + * @Entity + */ +class Event extends BaseEntity{ + /** + * Name of the event + * @Column(length=64,unique=true) + * @var string + */ + private $name; + + /** + * This is a counter which counts up, everytime the event is issued. + * @Column(type="datetime") + * @var datetime + */ + private $lastOccured; + + public function __construct( $name ){ + $this->name = $name; + $this->lastOccured = new \DateTime("now"); + } + + public function setName( $name ){ + $this->name = $name; + } + + public function getName(){ + return $this->name; + } + + /** + * Sets the event to be emitted now. + */ + public function emit(){ + $this->lastOccured = new \DateTime("now"); + } + + /** + * Retrieve the timestamp this event has occured. + */ + public function getLastOccured(){ + return $this->lastOccured; + } + +} + \ No newline at end of file diff --git a/src/backend/PartKeepr/EventNotification/EventManager.php b/src/backend/PartKeepr/EventNotification/EventManager.php @@ -0,0 +1,43 @@ +<?php +namespace PartKeepr\EventNotification; + +use PartKeepr\Util\Exceptions\ObjectNotFoundException; + +use PartKeepr\EventNotification\Event; +use PartKeepr\PartKeepr; +use PartKeepr\Util\Singleton; + +/** + * This is a manager class for working with the event system. This manager + * is responsible for the Event. + * + */ +class EventManager extends Singleton{ + /** + * Retrieve an event by name. If the entity does not exists, one will + * be created. + * @param string $name Name of the event + */ + public function getOrCreateByName( $name ){ + $obj = PartKeepr::getEM()->getRepository('PartKeepr\EventNotification\Event')->findOneByName($name); + if (!$obj){ + $obj = new Event( $name ); + PartKeepr::getEM()->persist($obj); + PartKeepr::getEM()->flush(); + } + + return $obj; + } + + /** + * Gets an existing event by its name. + * @param unknown $name + */ + public function getByName( $name ){ + $obj = PartKeepr::getEM()->getRepository('PartKeepr\EventNotification\Event')->findOneByName($name); + if (!$obj){ + throw new ObjectNotFoundException('PartKeepr\EventNotification\Event', "name=$name"); + } + return $obj; + } +} diff --git a/src/backend/PartKeepr/EventNotification/EventNotificationService.php b/src/backend/PartKeepr/EventNotification/EventNotificationService.php @@ -0,0 +1,74 @@ +<?php +namespace PartKeepr\EventNotification; + +use PartKeepr\Service\Service; +use PartKeepr\EventNotification\LastNotificationManager; + +/** + * This service can be used to register to events. This registration is + * done on aper session base and if the session is recreated all registrations + * will go away. + * Once the session has registered a listener to an event, he will be able to + * poll for occurance of this event. If this happens the user of this service + * can react on it. + */ +class EventNotificationService extends Service{ + /** + * Registers a listener to an event. + */ + public function registerListener(){ + $this->requireParameter("event"); + $eventName = $this->getParameter("event"); + + LastNotificationManager::getInstance()->startListeningToEvent( $eventName ); + return array('data'=>''); + } + + /** + * Deregisters listening to event. + */ + public function deregisterListener(){ + $this->requireParameter("event"); + $eventName = $this->getParameter("event"); + + LastNotificationManager::getInstance()->stopListeningToEvent( $eventName ); + return array('data'=>''); + } + + /** + * Check if we were notified. This method sets all events to confirmed after they + * have been sent to the user. + * + * This servcice call additional supports a long polling mechanism with the parameters: + * long: Set this to 1 to activate the long polling mode + * timeout: Optional, use this to define a user defined timeout if a shortre timeout + * than the default timeout is needed. + */ + public function isNotified(){ + $longPollingMode = $this->getParameter("long",false); + + // The maximum time we will try to fetch a notify until we will return. + // For correct usage, also have a look at the maximum execution time. + $maxTimeInSeconds = max( $this->getParameter("timeout",15), 15 ); + + $listeners = array(); + + $time_start = microtime(true); + do + { + $listeners = LastNotificationManager::getInstance()->getNotifiedListeners(); + usleep(250000); + }while( $longPollingMode !== false + && count( $listeners ) == 0 + && (microtime(true) - $time_start) < $maxTimeInSeconds ); + + $data = array(); + + foreach( $listeners as $listener ){ + $data[] = $listener->getEvent()->getName(); + $listener->confirm(); + } + + return array('data'=> $data); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/EventNotification/LastNotification.php b/src/backend/PartKeepr/EventNotification/LastNotification.php @@ -0,0 +1,65 @@ +<?php +namespace PartKeepr\EventNotification; + +use PartKeepr\EventNotification\Event; +use PartKeepr\Session\Session; +use PartKeepr\Util\BaseEntity; + +/** + * This entity is used to store the last notification of + * an event. + * + * @Entity + */ +class LastNotification extends BaseEntity{ + /** + * This is the associated event. + * @ManyToOne(targetEntity="PartKeepr\EventNotification\Event") + */ + private $event; + + /** + * @ManyToOne(targetEntity="PartKeepr\Session\Session") + * @JoinColumn(onDelete="CASCADE") + */ + private $session; + + /** + * This is the last timestamp, the user was notified about + * an occured event. + * + * @Column(type="datetime") + * @var datetime + */ + private $lastNotify; + + + public function __construct ( Event $event, Session $session ) { + $this->event = $event; + $this->session = $session; + $this->lastNotify = new \DateTime("now"); + } + + /** + * Checks if this class is notified by its event. + */ + public function isNotified(){ + return $lastNotify < $event->getLastOccured(); + } + + /** + * Confirms the notify and resets the notified state to false. + */ + public function confirm(){ + $this->lastNotify = new \DateTime("now"); + } + + /** + * Retrieves the event. + * @return Event + */ + public function getEvent(){ + return $this->event; + } + +} diff --git a/src/backend/PartKeepr/EventNotification/LastNotificationManager.php b/src/backend/PartKeepr/EventNotification/LastNotificationManager.php @@ -0,0 +1,64 @@ +<?php +namespace PartKeepr\EventNotification; + +use PartKeepr\EventNotification\LastNotification; +use PartKeepr\PartKeepr; +use PartKeepr\Session\SessionManager; +use PartKeepr\Util\Singleton; + +/** + * This is a manager class for working with the event system. This manager + * is responsible for the Event. + * + */ +class LastNotificationManager extends Singleton{ + /** + * Creates or retrieves a LastNotification entity by its event name. + * + * @param string $name Name of the event to retrive the record. + */ + private function getOrCreateByEventName( $name ){ + $event = EventManager::getInstance()->getByName($name); + $session = SessionManager::getCurrentSession(); + + $obj = PartKeepr::getEM()->getRepository('PartKeepr\EventNotification\LastNotification') + ->findOneBy(array('event' => $event->getId(), 'session' => $session->getId() )); + + if (!$obj){ + $obj = new LastNotification($event, $session); + PartKeepr::getEM()->persist($obj); + PartKeepr::getEM()->flush(); + } + + return $obj; + } + + /** + * Starts listeneing to the event with the given name. + */ + public function startListeningToEvent( $name ){ + $notify = $this->getOrCreateByEventName( $name ); + } + + /** + * Ends listening to the event and destroy the record of the listener. + */ + public function stopListeningToEvent( $name ){ + $notify = $this->getOrCreateByEventName( $name ); + PartKeepr::getEM()->remove($notify); + PartKeepr::getEM()->flush(); + } + + /** + * Get all entries which are notified by the event. + */ + public function getNotifiedListeners(){ + $session = SessionManager::getCurrentSession(); + $query = PartKeepr::getEM()->createQuery("SELECT l FROM PartKeepr\EventNotification\LastNotification l JOIN l.session s JOIN l.event e WHERE s.id = ?1 AND e.lastOccured > l.lastNotify"); + $query->setParameter(1,$session->getId()); + + return $query->getResult(); + } +} + + diff --git a/src/backend/PartKeepr/Part/Part.php b/src/backend/PartKeepr/Part/Part.php @@ -372,6 +372,13 @@ class Part extends BaseEntity implements Serializable, Deserializable { } /** + * Retrieves the footrpint + */ + public function getFootprint () { + return $this->footprint; + } + + /** * Sets the comment for this part * @param string $comment The comment for this part */ diff --git a/src/backend/PartKeepr/Printing/Exceptions/InvalidArgumentException.php b/src/backend/PartKeepr/Printing/Exceptions/InvalidArgumentException.php @@ -0,0 +1,11 @@ +<?php +namespace PartKeepr\Printing\Exceptions; + +use PartKeepr\Util\SerializableException; + +class InvalidArgumentException extends SerializableException { + public function __construct ($detailedReason) { + parent::__construct( $detailedReason ); + } +} +?>+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/Exceptions/RendererNotFoundException.php b/src/backend/PartKeepr/Printing/Exceptions/RendererNotFoundException.php @@ -0,0 +1,12 @@ +<?php +namespace PartKeepr\Printing\Exceptions; + +use PartKeepr\Util\SerializableException, + PartKeepr\PartKeepr; + +class RendererNotFoundException extends SerializableException { + public function __construct ($detailedReason, $requestedClass, $availableClasses) { + parent::__construct(PartKeepr::i18n("No adequate renderer found: $detailedReason \nRequested: $requestedClass \nAvailable: ".implode(", ",$availableClasses))); + } +} +?>+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/PageBasicLayout/PageBasicLayout.php b/src/backend/PartKeepr/Printing/PageBasicLayout/PageBasicLayout.php @@ -0,0 +1,224 @@ +<?php +namespace PartKeepr\Printing\PageBasicLayout; + +use PartKeepr\Util\BaseEntity, + PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable; + +/** + * This class is a layout which can be used to describe a typical page + * which has a n*m cell table on it. Such a table is interesting for e.g. if + * one will print labels. + * + * The layout of such a page needs: + * - Paper size + * - TopLeft position on page + * - Width and height of the cells (every cell has the same size) + * - Number of cells in columns an rows. + * + * @Entity + */ +class PageBasicLayout extends BaseEntity implements Serializable, Deserializable { + /** + * The name of this layout. + * @Column(type="text") + */ + private $name; + + /** + * The comment for this layout. + * @Column(type="text") + */ + private $comment; + + /** + * The numbers of columns on the page + * @Column(type="integer") + * @var integer + */ + private $columnCount = 1; + + /** + * The numbers of rows on the page + * @Column(type="integer") + * @var integer + */ + private $rowCount = 1; + + /** + * The size of the entire paper + * + * @Column(type="text") + */ + private $paperSize = "A4"; + + /** + * Indicates the orientation of the paper or better, the orientation + * of the final text. + * @Column(type="boolean") + */ + private $paperPortrait = true; + + /** + * This is the cell width in mm. + * @Column(type="float") + * @var float + */ + private $cellWidthInMM = 100; + + /** + * This is the cell height in mm. + * @Column(type="float") + * @var float + */ + private $cellHeightInMM = 100; + + /** + * This is the top left X position of the first cell on the + * sheet. + * @Column(type="float") + * @var float + */ + private $topLeftXInMM = 0; + + /** + * This is the top left Y position of the first cell on the + * sheet. + * @Column(type="float") + * @var float + */ + private $topLeftYInMM = 0; + + public function setName( $name ){ + $this->name = $name; + } + + public function getName(){ + return $this->name; + } + + public function setComment( $comment ){ + $this->comment = $comment; + } + + public function getComment(){ + return $this->comment; + } + + public function setColumnCount( $count ){ + $this->columnCount = $count; + } + + public function getColumnCount(){ + return $this->columnCount; + } + + public function setRowCount( $count ){ + $this->rowCount = $count; + } + + public function getRowCount(){ + return $this->rowCount; + } + + public function setPaperSize( $size ){ + $this->paperSize = $size; + } + + public function getPaperSize(){ + return $this->paperSize; + } + + public function setPaperPortrait( $portrait ){ + $this->paperPortrait = $portrait; + } + + public function getPaperPortrait(){ + return $this->paperPortrait; + } + + public function setCellWidthInMM( $size ){ + $this->cellWidthInMM = $size; + } + + public function getCellWidthInMM( ){ + return $this->cellWidthInMM; + } + + public function setCellHeightInMM( $size ){ + $this->cellHeightInMM = $size; + } + + public function getCellHeightInMM( ){ + return $this->cellHeightInMM; + } + + public function setTopLeftXInMM( $pos ){ + $this->topLeftXInMM = $pos; + } + + public function getTopLeftXInMM(){ + return $this->topLeftXInMM; + } + + public function setTopLeftYInMM( $pos ){ + $this->topLeftYInMM = $pos; + } + + public function getTopLeftYInMM(){ + return $this->topLeftYInMM; + } + + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "comment" => $this->getComment(), + "columnCount" => $this->getColumnCount(), + "rowCount" => $this->getRowCount(), + "paperSize" => $this->getPaperSize(), + "paperPortrait" => $this->getPaperPortrait(), + "cellWidthInMM" => $this->getCellWidthInMM(), + "cellHeightInMM" => $this->getCellHeightInMM(), + "topLeftXInMM" => $this->getTopLeftXInMM(), + "topLeftYInMM" => $this->getTopLeftYInMM() + ); + } + + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "name": + $this->setName( $value); + break; + case "comment": + $this->setComment($value); + break; + case "columnCount": + $this->setColumnCount($value); + break; + case "rowCount": + $this->setRowCount($value); + break; + case "paperSize": + $this->setPaperSize($value); + break; + case "paperPortrait": + $this->setPaperPortrait($value); + break; + case "cellWidthInMM": + $this->setCellWidthInMM($value); + break; + case "cellHeightInMM": + $this->setCellHeightInMM($value); + break; + case "topLeftXInMM": + $this->setTopLeftXInMM($value); + break; + case "topLeftYInMM": + $this->setTopLeftYInMM($value); + break; + } + } + } +} diff --git a/src/backend/PartKeepr/Printing/PageBasicLayout/PageBasicLayoutManager.php b/src/backend/PartKeepr/Printing/PageBasicLayout/PageBasicLayoutManager.php @@ -0,0 +1,57 @@ +<?php +namespace PartKeepr\Printing\PageBasicLayout; + +use PartKeepr\PartKeepr, + PartKeepr\Printing\PageBasicLayout\PrintingPageBasicLayout, + PartKeepr\Util\Singleton, + PartKeepr\Util\Exceptions\ObjectNotFoundException; + +class PageBasicLayoutManager extends Singleton { + public function getMultipleObjects ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { + $qb = PartKeepr::getEM()->createQueryBuilder(); + $qb->select("st.id, st.name")->from("PartKeepr\Printing\PageBasicLayout\PageBasicLayout","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\Printing\PageBasicLayout\PageBasicLayout","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 getObjectById ($id) { + $obj = PartKeepr::getEM()->find("PartKeepr\Printing\PageBasicLayout\PageBasicLayout", $id); + if ($obj) { + return $obj; + } else { + throw new ObjectNotFoundException("PartKeepr\Printing\PageBasicLayout\PageBasicLayout", $id); + } + } + public function deleteObjectById ($id) { + $obj = $this->getObjectById($id); + + PartKeepr::getEM()->remove($obj); + PartKeepr::getEM()->flush(); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/PageBasicLayout/PageBasicLayoutService.php b/src/backend/PartKeepr/Printing/PageBasicLayout/PageBasicLayoutService.php @@ -0,0 +1,65 @@ +<?php +namespace PartKeepr\Printing\PageBasicLayout; + +use PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\Printing\PageBasicLayout\PageBasicLayout, + PartKeepr\Printing\PageBasicLayout\PageBasicLayoutManager, + PartKeepr\PartKeepr; + +class PageBasicLayoutService extends Service implements RestfulService { + + public function get () { + if ($this->hasParameter("id")) { + return array("data" => PageBasicLayoutManager::getInstance()->getObjectById($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 PageBasicLayoutManager::getInstance()->getMultipleObjects( + $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"); + + $obj = new PageBasicLayout(); + $obj->deserialize($this->getParameters()); + + PartKeepr::getEM()->persist($obj); + PartKeepr::getEM()->flush(); + + return array("data" => $obj->serialize()); + } + + public function update () { + $this->requireParameter("id"); + $this->requireParameter("name"); + $obj = PageBasicLayoutManager::getInstance()->getObjectById($this->getParameter("id")); + $obj->deserialize($this->getParameters()); + + PartKeepr::getEM()->flush(); + + return array("data" => $obj->serialize()); + + } + + public function destroy () { + $this->requireParameter("id"); + + PageBasicLayoutManager::getInstance()->deleteObjectById($this->getParameter("id")); + + return array("data" => null); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/PrintingJob/PrintingJob.php b/src/backend/PartKeepr/Printing/PrintingJob/PrintingJob.php @@ -0,0 +1,113 @@ +<?php +namespace PartKeepr\Printing\PrintingJob; + +use PartKeepr\EventNotification\EventManager; +use PartKeepr\Session\SessionManager, + PartKeepr\UploadedFile\TempUploadedFile, + PartKeepr\User\User, + PartKeepr\Util\BaseEntity, + PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable; +/** + * This is a single job waiting for beeing processed. + * + * @Entity @HasLifecycleCallbacks + */ +class PrintingJob extends BaseEntity implements Serializable { + /** + * The timestamp when the job was created. + * @Column(type="datetime") + */ + private $created; + + /** + * Will be set if the job was processed successfully by somebody and is marked + * as done. + * @Column(type="boolean") + */ + private $done; + + /** + * This is the user which has created this printing job. + * @ManyToOne(targetEntity="PartKeepr\User\User") + */ + private $owner; + + /** + * Target user the printing job is for. This is mostly the printer or output queue + * which should be used to process this job. + * @ManyToOne(targetEntity="PartKeepr\User\User") + */ + private $target; + + /** + * Holds the data which was rendered for printing. + * @OneToOne(targetEntity="PartKeepr\UploadedFile\TempUploadedFile") + */ + private $data; + + public function __construct () { + $this->created = new \DateTime(); + $this->done = false; + $this->owner = SessionManager::getInstance()->getCurrentSession()->getUser(); + } + + /** + * This method is a callback for the PostPersist event. We will add it to our database + * dependent eventNotification. + * + * @PostPersist @PostUpdate + */ + public function onPostPersist(){ + if (!$this->done) { + EventManager::getInstance()->getOrCreateByName("Printing.pendingJob")->emit(); + } + } + + public function getCreated(){ + return $this->created; + } + + public function setDone( $done ){ + $this->done = $done; + } + + public function getDone(){ + return $this->done; + } + + public function setData( TempUploadedFile $data ){ + $this->data = $data; + } + + public function getData( ){ + return $this->data; + } + + public function setOwner( User $user ) { + $this->owner = $user; + } + + public function getOwner() { + return $this->owner; + } + + public function setTarget( User $user ){ + $this->target = $user; + } + + public function getTarget() { + return $this->target; + } + + public function serialize () { + return array( + "id" => $this->getId(), + "created" => $this->getCreated(), + "done" => $this->getDone(), + "owner" => $this->getOwner()->getId(), + "target" => $this->getTarget()->getId(), + "data" => $this->getData()->getId() + ); + } +} diff --git a/src/backend/PartKeepr/Printing/PrintingJob/PrintingJobManager.php b/src/backend/PartKeepr/Printing/PrintingJob/PrintingJobManager.php @@ -0,0 +1,34 @@ +<?php +namespace PartKeepr\Printing\PrintingJob; + +use Doctrine\ORM\Query, + Doctrine\ORM\QueryBuilder, + PartKeepr\Manager\AbstractManager, + PartKeepr\Manager\ManagerFilter, + PartKeepr\PartKeepr, + PartKeepr\Printing\PrintingJob\PrintingJob, + PartKeepr\Util\Singleton, + PartKeepr\Util\Exceptions\ObjectNotFoundException; + +class PrintingJobManager extends AbstractManager { + public function getEntityName () { + return 'PartKeepr\Printing\PrintingJob\PrintingJob'; + } + + public function getQueryFields () { + return array("id","created","done","ow.id AS owner","ta.id AS target","da.id AS data"); + } + + public function getDefaultSortField () { + return "created"; + } + + protected function applyCustomQuery (QueryBuilder $qb, ManagerFilter $filter) { + /** + * Pull in additional tables + */ + $qb ->leftJoin("q.target", "ta") + ->leftJoin("q.owner", "ow") + ->leftJoin("q.data", "da"); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/PrintingJob/PrintingJobService.php b/src/backend/PartKeepr/Printing/PrintingJob/PrintingJobService.php @@ -0,0 +1,85 @@ +<?php +namespace PartKeepr\Printing\PrintingJob; + +use PartKeepr\PartKeepr, + PartKeepr\Manager\ManagerFilter, + PartKeepr\Printing\PrintingJob\PrintingJob, + PartKeepr\Printing\PrintingJob\PrintingJobManager, + PartKeepr\Service\RestfulService, + PartKeepr\Service\FilterExtractor, + PartKeepr\Service\Service, + PartKeepr\Session\SessionManager; + +class PrintingJobService extends Service implements RestfulService { + /** + * Checks the permission and throws an exception if the access is denied. + * @throws \Exception + */ + private function checkPermission( $job ){ + $user = SessionManager::getInstance()->getCurrentSession()->getUser(); + if( !$user->isAdmin() && $user === $job->getTarget() && $user === $job->getOwner() ) + throw new \Exception("Permission denied!"); + } + + public function get () { + if ($this->hasParameter("id")) { + $job = PrintingJobManager::getInstance()->getEntity($this->getParameter("id")); + $this->checkPermission($job); + + return array("data" => $job->serialize()); + } else { + $filter = new ManagerFilter($this); + $filter->setFilterCallback(array($this, "filterCallback")); + return PrintingJobManager::getInstance()->getList($filter); + } + } + + public function filterCallback ($queryBuilder) { + $filter = new FilterExtractor($this); + + // Apply access restriction filters here + $user = SessionManager::getInstance()->getCurrentSession()->getUser(); + + $queryBuilder->andWhere("(q.target = :sessionuser OR q.owner = :sessionuser)"); + $queryBuilder->setParameter("sessionuser", $user->getId() ); + + // Apply User filters here + if ($filter->has("done") && $filter->get("done") != "") { + $queryBuilder->andWhere("q.done = :done"); + $queryBuilder->setParameter("done", $filter->get("done") ); + } + } + + public function create () { + throw new \Exception("Creation of printing jobs cannot be done by this service!"); + } + + /** + * This update method only supports updating the done flag! + * @see \PartKeepr\Service\RestfulService::update() + */ + public function update () { + $this->requireParameter("id"); + $this->requireParameter("done"); + + $obj = PrintingJobManager::getInstance()->getEntity($this->getParameter("id")); + $this->checkPermission($obj); + + $obj->setDone( $this->getParameter("done")=='true'); + PartKeepr::getEM()->flush(); + + return array("data" => $obj->serialize()); + } + + public function destroy () { + $this->requireParameter("id"); + $id = $this->getParameter("id"); + + $job = PrintingJobManager::getInstance()->getEntity($id); + $this->checkPermission($job); + + PrintingJobManager::getInstance()->deleteEntity($id); + + return array("data" => null); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/PrintingJobConfiguration/PrintingJobConfiguration.php b/src/backend/PartKeepr/Printing/PrintingJobConfiguration/PrintingJobConfiguration.php @@ -0,0 +1,149 @@ +<?php +namespace PartKeepr\Printing\PrintingJobConfiguration; + +use PartKeepr\Printing\PageBasicLayout\PageBasicLayout; + +use PartKeepr\Util\BaseEntity, + PartKeepr\Util\Deserializable, + PartKeepr\Util\Serializable; + +/** + * This is a configuration for a specific job to export. + * Such a job is bound to a specific type to export and + * holds all additional configuration options to execute + * the job correctly. + * + * @Entity + */ +class PrintingJobConfiguration extends BaseEntity implements Serializable, Deserializable { + /** + * The name of this layout. + * @Column(type="text") + */ + private $name; + + /** + * The comment for this layout. + * @Column(type="text") + */ + private $comment; + + /** + * This holds the object type this configuration is used for. + * @Column(type="text") + */ + private $objectType; + + /** + * The classname of the export renderer. + * @Column(type="text") + */ + private $exportRenderer; + + /** + * This is the layout to use if we are in printing mode. + * @ManyToOne(targetEntity="PartKeepr\Printing\PageBasicLayout\PageBasicLayout") + */ + private $pageLayout; + + /** + * This field holds additional renderer configuration data. This configuration + * is very renderer specific and can be used to parametrize the output more. + * + * @Column(type="text") + */ + private $rendererConfiguration; + + public function setName( $name ){ + $this->name = $name; + } + + public function getName(){ + return $this->name; + } + + public function setComment( $comment ){ + $this->comment = $comment; + } + + public function getComment(){ + return $this->comment; + } + + public function setObjectType( $type ){ + $this->objectType = $type; + } + + public function getObjectType( ){ + return $this->objectType; + } + + public function setExportRenderer( $rendererName ){ + $this->exportRenderer = $rendererName; + } + + public function getExportRenderer(){ + return $this->exportRenderer; + } + + public function setPageLayout( $layout ){ + $this->pageLayout = $layout; + } + + public function getPageLayout(){ + return $this->pageLayout; + } + + public function setRendererConfiguration( $cfg ){ + $this->rendererConfiguration = $cfg; + } + + public function getRendererConfiguration(){ + return $this->rendererConfiguration; + } + + public function serialize () { + return array( + "id" => $this->getId(), + "name" => $this->getName(), + "comment" => $this->getComment(), + "objectType" => $this->getObjectType(), + "exportRenderer" => $this->getExportRenderer(), + "pageLayout" => is_object( $this->getPageLayout() ) ? $this->getPageLayout()->getId(): null, + "rendererConfiguration" => $this->getRendererConfiguration() + ); + } + + public function deserialize (array $parameters) { + foreach ($parameters as $key => $value) { + switch ($key) { + case "name": + $this->setName( $value); + break; + case "comment": + $this->setComment($value); + break; + case "objectType": + $this->setObjectType($value); + break; + case "exportRenderer": + $this->setExportRenderer($value); + break; + case "pageLayout": + if ($value === 0) { + $this->setPageLayout(null); + } else { + try { + $this->setPageLayout( PageBasicLayout::loadById($value) ); + } catch (\Exception $e) { + $this->setPageLayout(null); + } + } + break; + case "rendererConfiguration": + $this->setRendererConfiguration($value); + break; + } + } + } +} diff --git a/src/backend/PartKeepr/Printing/PrintingJobConfiguration/PrintingJobConfigurationManager.php b/src/backend/PartKeepr/Printing/PrintingJobConfiguration/PrintingJobConfigurationManager.php @@ -0,0 +1,35 @@ +<?php +namespace PartKeepr\Printing\PrintingJobConfiguration; + +use PartKeepr\Manager\AbstractManager, + PartKeepr\PartKeepr, + PartKeepr\Printing\PrintingJobConfiguration\PrintingPrintingJobConfiguration, + PartKeepr\Util\Singleton, + PartKeepr\Util\Exceptions\ObjectNotFoundException; + +class PrintingJobConfigurationManager extends AbstractManager { + public function getEntityName () { + return 'PartKeepr\Printing\PrintingJobConfiguration\PrintingJobConfiguration'; + } + + public function getQueryFields () { + return array("id", "name", "comment", "objectType", "exportRenderer", "rendererConfiguration"); + } + + public function getDefaultSortField () { + return "name"; + } + + public function deleteConfiguration ($id) { + $part = PartManager::getInstance()->getConfiguration($id); + + PartKeepr::getEM()->remove($part); + PartKeepr::getEM()->flush(); + } + + public function getConfiguration ($id) { + $part = PartKeepr::getEM()->find(getEntityName(), $id); + + return $part; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/PrintingJobConfiguration/PrintingJobConfigurationService.php b/src/backend/PartKeepr/Printing/PrintingJobConfiguration/PrintingJobConfigurationService.php @@ -0,0 +1,68 @@ +<?php +namespace PartKeepr\Printing\PrintingJobConfiguration; + +use PartKeepr\PartKeepr, + PartKeepr\Manager\ManagerFilter, + PartKeepr\Printing\PrintingJobConfiguration\PrintingJobConfiguration, + PartKeepr\Printing\PrintingJobConfiguration\PrintingJobConfigurationManager, + PartKeepr\Service\RestfulService, + PartKeepr\Service\FilterExtractor, + PartKeepr\Service\Service; + +class PrintingJobConfigurationService extends Service implements RestfulService { + public function get () { + if ($this->hasParameter("id")) { + return array("data" => PrintingJobConfigurationManager::getInstance()->getEntity($this->getParameter("id"))->serialize()); + } else { + $filter = new ManagerFilter($this); + $filter->setFilterCallback(array($this, "filterCallback")); + + return PrintingJobConfigurationManager::getInstance()->getList($filter); + } + } + + /** + * Advanced filtering for the list + * @param QueryBuilder The $queryBuilder + */ + public function filterCallback ($queryBuilder) { + $filter = new FilterExtractor($this); + + if ($filter->has("objectType") && $filter->get("objectType") != "") { + $queryBuilder->andWhere("q.objectType = :objtype"); + $queryBuilder->setParameter("objtype", $filter->get("objectType") ); + } + } + + public function create () { + $this->requireParameter("name"); + + $obj = new PrintingJobConfiguration(); + $obj->deserialize($this->getParameters()); + + PartKeepr::getEM()->persist($obj); + PartKeepr::getEM()->flush(); + + return array("data" => $obj->serialize()); + } + + public function update () { + $this->requireParameter("id"); + $this->requireParameter("name"); + $obj = PrintingJobConfigurationManager::getInstance()->getEntity($this->getParameter("id")); + $obj->deserialize($this->getParameters()); + + PartKeepr::getEM()->flush(); + + return array("data" => $obj->serialize()); + + } + + public function destroy () { + $this->requireParameter("id"); + + PrintingJobConfigurationManager::getInstance()->deleteEntity($this->getParameter("id")); + + return array("data" => null); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/PrintingService.php b/src/backend/PartKeepr/Printing/PrintingService.php @@ -0,0 +1,165 @@ +<?php +namespace PartKeepr\Printing; + +use PartKeepr\Printing\PrintingJob\PrintingJob; + +use PartKeepr\PartKeepr, + PartKeepr\Printing\Exceptions\InvalidArgumentException, + PartKeepr\Printing\Exceptions\RendererNotFoundException, + PartKeepr\Printing\PageBasicLayout\PageBasicLayoutManager, + PartKeepr\Printing\PDFLabelRenderer, + PartKeepr\Printing\PrintingJobConfiguration\PrintingJobConfigurationManager, + PartKeepr\Printing\Utils\DecodeConfiguration, + PartKeepr\Service\Service, + PartKeepr\StorageLocation\StorageLocation, + PartKeepr\UploadedFile\TempUploadedFile, + PartKeepr\User\UserManager, + PartKeepr\Util\Configuration as PartKeeprConfiguration; + +/** + * This service is the entry point for our printing/exporting + * service. + */ +class PrintingService extends Service { + /** + * This array contains all object types which can be used to + * for printing. Only these object types will be accepted by the + * generatePrintout method to be able to restrict access to the + * database. + * The value is used to display the selection to the user. + */ + private $availableObjectTypes = array( + 'PartKeepr\StorageLocation\StorageLocation' => 'StorageLocation', + 'PartKeepr\Part\Part' => 'Part' + ); + + /** + * Prints the selected storage locations to a dedicated file + * and returns the url to this file. + */ + public function startExport () { + $this->requireParameter("ids"); + $this->requireParameter("configuration"); + $this->requireParameter("objectType"); + + $ids = explode(',',$this->getParameter("ids")); + $configurationId = $this->getParameter("configuration"); + $objectType = $this->getParameter("objectType"); + + $printerUser = null; + if ($this->hasParameter("target") && $this->getParameter("target") != "") { + $printerUser = UserManager::getInstance()->getUser($this->getParameter("target")); + } + + // check object type for valid object types for security reasons. + // See Select query below and be aware of SQL injection! + if ( !array_key_exists($objectType, $this->availableObjectTypes) ){ + throw new RendererNotFoundException("Object type is forbidden!", $objectType, array_keys($this->availableObjectTypes)); + } + + $configuration = PrintingJobConfigurationManager::getInstance()->getEntity( $configurationId ); + + $query = PartKeepr::getEM()->createQuery("SELECT s FROM $objectType s WHERE s.id IN (?1)"); + $query->setParameter(1,$ids); + $dataToRender = $query->getResult(); + + $renderingObjects = array(); + if ($configuration->getPageLayout() !== null ){ + $renderingObjects[] = $configuration->getPageLayout(); + } + + $renderer = RendererFactoryRegistry::getInstance()->getRendererFactory( $configuration->getExportRenderer()) + ->createInstance( $renderingObjects, $configuration->getRendererConfiguration() ); + + $renderer->passRenderingData($dataToRender); + + $tempFile = tempnam("/tmp", "PWC"); + $renderer->storeResult( $tempFile ); + + $tmpFile = new TempUploadedFile(); + $tmpFile->replace($tempFile); + $tmpFile->setOriginalFilename("generatedFile.".$renderer->getSuggestedExtension()); + PartKeepr::getEM()->persist($tmpFile); + PartKeepr::getEM()->flush(); + + //Create a job if we have a valid printer target + if ($printerUser !== null){ + $job = new PrintingJob(); + $job->setData($tmpFile); + $job->setTarget($printerUser); + PartKeepr::getEM()->persist($job); + PartKeepr::getEM()->flush(); + } + + return array("fileid" => $tmpFile->getId() ); + } + + /** + * This service method will return all available renderers for + * the given data objecttype to render. + */ + public function getAvailableRenderer() { + $objectType = $this->getParameter("objectType", null); + + // Fail early for this type of request! + if ( $objectType!==null && !array_key_exists($objectType, $this->availableObjectTypes) ){ + throw new RendererNotFoundException("Object type is forbidden!", $objectType, array_keys($this->availableObjectTypes) ); + } + + $data = array(); + $renderers = $objectType === null + ? RendererFactoryRegistry::getInstance()->getRendererFactory( null ) + : RendererFactoryRegistry::getInstance()->getRendererFactoryForRenderData( $objectType ); + + foreach ( $renderers as $renderer ){ + $data[] = array("id" => $renderer->getCreatedClassname(), + "name" => $renderer->getName() ); + } + + return array("data" => $data ); + } + + /** + * Retrieve the available types which can be used with the given renderer. + * If no renderer parameter is passed, we will return all types which are supported in + * general. + */ + public function getAvailableTypes() { + $rendererName = $this->getParameter("renderer", ""); + $data = array(); + + if ($rendererName==""){ + foreach ($this->availableObjectTypes as $type => $userType){ + $data[] = array("id" => $type + ,"name" => $userType ); + } + }else{ + $factory = RendererFactoryRegistry::getInstance()->getRendererFactory($rendererName); + $supportedTypes = $factory->getSupportedClassesForRendering(); + + //var_dump($supportedTypes); + + foreach ($supportedTypes as $type ){ + if ( array_key_exists( $type, $this->availableObjectTypes ) ){ + $data[] = array("id" => $type + ,"name" => $this->availableObjectTypes[$type] ); + } + } + } + + return array("data" => $data ); + } + + /** + * Retrieve the needed parameters for the renderer class. + */ + public function getNeededParameters(){ + $this->requireParameter("renderer"); + $rendererName = $this->getParameter("renderer", ""); + $data = array(); + + $factory = RendererFactoryRegistry::getInstance()->getRendererFactory($rendererName); + return array("data" => $factory->getParameterObjectTypes() ); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/Renderer/PDFDefaultRenderer.php b/src/backend/PartKeepr/Printing/Renderer/PDFDefaultRenderer.php @@ -0,0 +1,279 @@ +<?php +namespace PartKeepr\Printing\Renderer; + +use PartKeepr\Part\Part, + PartKeepr\Printing\Exceptions\RendererNotFoundException, + PartKeepr\Printing\PageBasicLayout\PageBasicLayout, + PartKeepr\Printing\RendererFactoryRegistry, + PartKeepr\Printing\Renderer\TCPDFAbstractRenderer, + PartKeepr\Printing\SimpleRendererFactory, + PartKeepr\Printing\Utils\PercentOrNumericHelper, + PartKeepr\Printing\Utils\Placeholder, + PartKeepr\Printing\Utils\DecodeConfiguration, + PartKeepr\StorageLocation\StorageLocation + ; + +/** + * This class implements a way to render different datasets to + * a labeling layout. + */ +class PDFDefaultRenderer extends TCPDFAbstractRenderer{ + /** + * Our default configuration for this label renderer. + * @var array + */ + private $defaultConfiguration = array( + 'barcodeEnable' => false, + 'barcodeType' => 'QRCODE,L', + 'textBarcode' => "PA/!!id!!", + 'barcodeWidth' => "12", + 'barcodeHeight' => "12", + 'barcodeXPos' => "-7", + 'barcodeYPos' => "-7", + 'barcodeWithText' => false, + 'barcode2D' => true, + 'fontFamily' => 'times', + 'fontStyle' => '', + 'fontSize' => 12, + 'text' => '<span style="font-size: 11pt;"><b>!!name!!</b></span><br><span style="font-size: 8pt;">!!description!!</span>' + ); + + public function __construct (array $objects, $cfgString ) { + $configuration = DecodeConfiguration::decode($cfgString); + + // Apply the personal default configuration here. + // The base will apply the base default parameters for you. + $configuration = array_merge( $this->defaultConfiguration, $configuration ); + parent::__construct( $objects, $configuration); + } + + public function passRenderingData( $data ){ + // Here we got our data passed. We have to decide how we want + // to render the data, so we dispatch it to our internal rendering + // methods to make the code more clear. + if ( is_array( $data ) ){ + if (count($data)>0){ + $elem = reset($data); + if ($elem instanceof StorageLocation){ + $this->renderStorageLocations($data); + } elseif ($elem instanceof Part){ + $this->renderParts($data); + } + else{ + throw new RendererNotFoundException("Unable to handle object type with renderer PDFLabelRenderer.", + get_class($elem),array("StorageLocation")); + } + } + } + else{ + // If the selected object is not an array, we make an array first + // to have only one case to handle. + passRenderingData( array( $ata ) ); + } + } + + /** + * This method renders an array of StorageLocations to our sheet. + * @param array $locations + */ + private function renderStorageLocations( array $locations ){ + foreach ($locations as $location){ + $this->renderCellFromLocation( $location ); + } + } + + /** + * This method just renders the next single cell with the given location + * as content. + */ + private function renderCellFromLocation( StorageLocation $location ){ + // Move the pointer to the next cell and initialize PDF class + // correctly. Also draws the grid if necessary. + $this->initNextCell(); + + $padding = 3; + + $name = $location->getName(); + + $this->pdf->SetCellPadding($padding); + $this->pdf->SetFont( $this->configuration['fontFamily'], + $this->configuration['fontStyle'], + $this->configuration['fontSize']); + $this->pdf->SetXY( $this->xCellPos,$this->yCellPos ); + + // Start clipping. Every thing with StartTransform and StopTransform + // will be clipped to the Rect. This is a cool feature if one field is + // too long, it will not destroy the rest of your page and it is partial + // usable. + $this->pdf->StartTransform(); + // Draw clipping rectangle to match html cell. + $this->pdf->Rect($this->xCellPos, $this->yCellPos, $this->layout->getCellWidthInMM(), + $this->layout->getCellHeightInMM(), 'CNZ'); + + $this->pdf->MultiCell( $this->layout->getCellWidthInMM(), + $this->layout->getCellHeightInMM(), $name, '0'); + + $this->pdf->StopTransform(); + + // Place a Barcode at the end + if ($this->configuration['barcodeEnable']){ + $barcodeWidth = $this->configuration['barcodeWidth']; + $barcodeHeight = $this->layout->getCellHeightInMM() - 5; + + if ($this->configuration['barcode2D']){ + $this->pdf->write2DBarcode($name, $this->configuration['barcodeType'], + $this->xCellPos + $this->layout->getCellWidthInMM() - $barcodeWidth - 5, + $this->yCellPos + $this->layout->getCellHeightInMM() / 2 - $barcodeHeight / 2, + $barcodeWidth, + $barcodeHeight + ); + } + else { + $style = array('text'=> $this->configuration['barcodeWithText'] ); + $this->pdf->write1DBarcode($name, $this->configuration['barcodeType'], + $this->xCellPos + $this->layout->getCellWidthInMM() - $barcodeWidth - 5, + $this->yCellPos + $this->layout->getCellHeightInMM() / 2 - $barcodeHeight / 2, + $barcodeWidth, + $barcodeHeight, + "", + $style + ); + } + } + } + + /** + * This method renders an array of Parts to our sheet. + * @param array $parts + */ + private function renderParts( array $parts ){ + foreach ($parts as $part){ + $this->renderCellFromPart( $part ); + } + } + + /** + * This method just renders the next single cell with a part + * as content. + */ + private function renderCellFromPart( Part $part ){ + // Move the pointer to the next cell and initialize PDF class + // correctly. Also draws the grid if necessary. + $this->initNextCell(); + + $padding = 3; + + $dataReplacement = new Placeholder( $part, "!!", "!!" ); + + $text = $dataReplacement->apply($this->configuration['text']); + $barcodeId = $dataReplacement->apply($this->configuration['textBarcode'] ); + + $this->pdf->SetCellPadding($padding); + $this->pdf->SetFont( $this->configuration['fontFamily'], + $this->configuration['fontStyle'], + $this->configuration['fontSize']); + $this->pdf->SetXY( $this->xCellPos,$this->yCellPos ); + + if ($this->configuration['barcodeEnable']){ + $widthParameter = new PercentOrNumericHelper($this->configuration['barcodeWidth']); + $heightParameter = new PercentOrNumericHelper($this->configuration['barcodeHeight']); + $xPosParameter = new PercentOrNumericHelper($this->configuration['barcodeXPos']); + $yPosParameter = new PercentOrNumericHelper($this->configuration['barcodeYPos']); + + $xPos = $xPosParameter->getValueWrap(0, $this->layout->getCellWidthInMM() ); + $yPos = $yPosParameter->getValueWrap(0, $this->layout->getCellHeightInMM() ); + $width = $widthParameter->getValue(0,$this->layout->getCellWidthInMM()); + $height = $heightParameter->getValue(0,$this->layout->getCellHeightInMM()); + + $regions = array(); + + $this->pdf->resetInternalMargins(); + + if ($this->configuration['barcode2D']){ + $this->pdf->write2DBarcode($barcodeId, $this->configuration['barcodeType'], + $this->xCellPos + $xPos - $width / 2 , + $this->yCellPos + $yPos - $height / 2, + $width, + $height + ); + } + + // This region will add a "do not write text here" over our barcode This is a kind + // of hack to make text fluently moving around :) + $textMarginX = 7; + $textMarginY = 7; + + $left = $xPos < $this->layout->getCellWidthInMM() / 2; + $top = $yPos < $this->layout->getCellHeightInMM() / 2; + + // $this->pdf->writeHTML("LEFT: $left TOP: $top XPOS: $xPos YPOS: $yPos WIDTH: ".$this->layout->getCellWidthInMM()." HEIGHT: ".$this->layout->getCellHeightInMM(), true, false, true, false, ''); + if( $left ) + { + if( $top ){ + $regions[] = array('page' => '' + , 'xt' => $xPos + $this->xCellPos + $textMarginX + , 'yt' => 0 + , 'xb' => $xPos + $this->xCellPos + $textMarginX + , 'yb' => $yPos + $this->yCellPos + $textMarginY, 'side' => 'L'); + }else + { + $regions[] = array('page' => '' + , 'xt' => $xPos + $this->xCellPos + $textMarginX + , 'yt' => $yPos + $this->yCellPos - $textMarginY + , 'xb' => $xPos + $this->xCellPos + $textMarginX + , 'yb' => $this->yCellPos + $this->layout->getCellHeightInMM() + , 'side' => 'L'); + } + }else + { + if( $top ){ + $regions[] = array('page' => '' + , 'xt' => $xPos + $this->xCellPos - $textMarginX + , 'yt' => 0 + , 'xb' => $xPos + $this->xCellPos - $textMarginX + , 'yb' => $yPos + $this->yCellPos + $textMarginY, 'side' => 'R'); + }else + { + $regions[] = array('page' => '' + , 'xt' => $xPos + $this->xCellPos - $width / 2 - $textMarginX + , 'yt' => $yPos + $this->yCellPos - $height/ 2 - $textMarginY + , 'xb' => $xPos + $this->xCellPos - $width / 2 - $textMarginX + , 'yb' => $this->yCellPos + $this->layout->getCellHeightInMM() + , 'side' => 'R'); + } + } + $this->pdf->setPageRegions($regions); + } + + // Start clipping. Every thing with StartTransform and StopTransform + // will be clipped to the Rect. This is a cool feature if one field is + // too long, it will not destroy the rest of your page and it is partial + // usable. + $this->pdf->StartTransform(); + // Draw clipping rectangle to match html cell. + $this->pdf->Rect($this->xCellPos, $this->yCellPos, $this->layout->getCellWidthInMM(), + $this->layout->getCellHeightInMM(), 'CNZ'); + + $this->pdf->WriteHTMLCell( $this->layout->getCellWidthInMM(), + $this->layout->getCellHeightInMM(), + $this->xCellPos, + $this->yCellPos, + $text); + + $this->pdf->StopTransform(); + + $this->pdf->setPageRegions(); + } +} + +// We have to register this class to the registry. +// Only if the class is registered, it can be found by the +// registry and you will see it in the application. +RendererFactoryRegistry::getInstance()->registerFactory( + new SimpleRendererFactory("Default PDF renderer", + "PartKeepr\Printing\Renderer\PDFDefaultRenderer", + array("PartKeepr\StorageLocation\StorageLocation", + "PartKeepr\Part\Part"), + array("PartKeepr\Printing\PageBasicLayout\PageBasicLayout") + ) + ); diff --git a/src/backend/PartKeepr/Printing/Renderer/TCPDFAbstractRenderer.php b/src/backend/PartKeepr/Printing/Renderer/TCPDFAbstractRenderer.php @@ -0,0 +1,190 @@ +<?php +namespace PartKeepr\Printing\Renderer; + +use PartKeepr\Printing\RendererIfc, + PartKeepr\Printing\RendererFactoryRegistry, + PartKeepr\Printing\RendererNotFoundException, + PartKeepr\Printing\SimpleRendererFactory, + PartKeepr\Printing\PageBasicLayout\PageBasicLayout, + PartKeepr\StorageLocation\StorageLocation + ; + +require_once('tcpdf/tcpdf.php'); + +/** + * This wrapper class can be used to remove some bugs from the + * TCPDF. https://sourceforge.net/p/tcpdf/bugs/773/ + * @author sven + * + */ +class TCPDFWrapper extends \TCPDF{ + public function __construct($orientation, $unit, $format){ + parent::__construct($orientation, $unit, $format); + } + + public function resetInternalMargins(){ + $this->crMargin = $this->rMargin = $this->original_rMargin; + $this->clMargin = $this->lMargin = $this->original_lMargin; + } +} + +/** + * This class is a abstract renderer to help somebody by creating its + * own plugins. Use the PDFDefaultRenderer as an example. + */ +abstract class TCPDFAbstractRenderer implements RendererIfc{ + /** + * This contains our layout for the page to be rendered. + */ + protected $layout = null; + + /** + * Our internal used pdf generator. + */ + protected $pdf; + + /** + * This is the count of the cells we have actually rendered. You can name + * it a pointer to the next cell, which should be rendered. We render all + * columns first and then increment the row. + */ + protected $cellsRendered; + + /** + * This is the position of the actual cell we want to process. + * Top left point. + */ + protected $xCellPos = 0; + + /** + * This is the position of the actual cell we want to process. + * Top left point. + */ + protected $yCellPos = 0; + + /** + * This is an array with configuration things. + * @var array + */ + protected $configuration; + + /** + * This boolean can be set to true to not trigger an error on startup. + * We implement it this way, to ensure the implementer of the plugin has + * thought of this potential security risk. + * + * @var unknown + */ + protected $doNotErrorIfTCPDFSetsCalls = false; + + /** + * Our default configuration for this label renderer. + * @var array + */ + protected $basedefaultConfiguration = array( + 'borderGrid' => true, + 'startingCell' => 0 + ); + + public function __construct ( array $objects, array $configuration ) { + foreach( $objects as $obj ){ + if ($obj instanceof \PartKeepr\Printing\PageBasicLayout\PageBasicLayout){ + $this->layout = $obj; + } + } + + if ($this->layout === null){ + throw new \PartKeepr\Printing\Exceptions\InvalidArgumentException("Required object not passed!"); + } + + $this->configuration = array_merge( $this->basedefaultConfiguration, $configuration ); + + $this->cellsRendered = $this->configuration['startingCell']; + + if (K_TCPDF_CALLS_IN_HTML && !$doNotErrorIfTCPDFSetsCalls){ + trigger_error("TCPDF has set K_TCPDF_CALLS_IN_HTML to true, which is a security issue with this class since the user can modify the html text!",E_USER_ERROR); + } + + $this->pdf = new TCPDFWrapper( $this->layout->getPaperPortrait() ? 'P': 'L' + , 'mm', $this->layout->getPaperSize() ); + + // set document information + $this->pdf->SetCreator(PDF_CREATOR); + $this->pdf->SetAuthor('PartDB'); + $this->pdf->SetTitle('PartDB Labeling Document'); + $this->pdf->SetSubject('This is a priontout of for labeling or indexing'); + $this->pdf->SetKeywords('PartDB'); + + // remove default header/footer + $this->pdf->setPrintHeader(false); + $this->pdf->setPrintFooter(false); + + // set default monospaced font + $this->pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED); + + //set margins + $this->pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT); + + //set auto page breaks + $this->pdf->SetAutoPageBreak(FALSE, PDF_MARGIN_BOTTOM); + + //set image scale factor + $this->pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); + + //set some language-dependent strings + //$this->pdf->setLanguageArray($l); + + // --------------------------------------------------------- + } + + public function getSuggestedExtension(){ + return "pdf"; + } + + public function storeResult( $outFile ){ + $this->pdf->Output($outFile, 'F'); + } + + public function outputResult($filename){ + $this->pdf->Output($filename, 'I'); + } + + protected function renderBorderGrid(){ + $colYStart = $this->layout->getTopLeftYInMM(); + $colYEnd = $this->layout->getRowCount() * $this->layout->getCellHeightInMM() + $this->layout->getTopLeftYInMM(); + + $rowXStart = $this->layout->getTopLeftXInMM(); + $rowXEnd = $this->layout->getColumnCount() * $this->layout->getCellWidthInMM() + $this->layout->getTopLeftXInMM(); + + for ($r=0; $r <= $this->layout->getRowCount(); $r++){ + $rowYPos = $r * $this->layout->getCellHeightInMM() + $this->layout->getTopLeftYInMM(); + $this->pdf->Line( $rowXStart, $rowYPos, $rowXEnd, $rowYPos ); + } + + for ($c=0; $c <= $this->layout->getColumnCount(); $c++){ + $colXPos = $c * $this->layout->getCellWidthInMM() + $this->layout->getTopLeftXInMM(); + $this->pdf->Line( $colXPos, $colYStart, $colXPos, $colYEnd ); + } + } + + /** + * Opens the cell and sets the class to the correct coordinates. + */ + protected function initNextCell(){ + $page = floor( $this->cellsRendered / ($this->layout->getColumnCount() * $this->layout->getRowCount())); + $cellXCoordinate = $this->cellsRendered % $this->layout->getColumnCount(); + $cellYCoordinate = ( floor($this->cellsRendered / $this->layout->getColumnCount()) ) % $this->layout->getRowCount() ; + + $this->xCellPos = $cellXCoordinate * $this->layout->getCellWidthInMM() + $this->layout->getTopLeftXInMM(); + $this->yCellPos = $cellYCoordinate * $this->layout->getCellHeightInMM() + $this->layout->getTopLeftYInMM(); + + if ($cellXCoordinate == 0 && $cellYCoordinate == 0){ + $this->pdf->AddPage(); + if ($this->configuration['borderGrid']){ + $this->renderBorderGrid(); + } + } + + $this->cellsRendered++; + } +} diff --git a/src/backend/PartKeepr/Printing/Renderer/ZebraLabelWriterRenderer.php b/src/backend/PartKeepr/Printing/Renderer/ZebraLabelWriterRenderer.php @@ -0,0 +1,134 @@ +<?php +namespace PartKeepr\Printing\Renderer; + +use PartKeepr\Part\Part, + PartKeepr\Printing\PageBasicLayout\PageBasicLayout, + PartKeepr\Printing\RendererFactoryRegistry, + PartKeepr\Printing\RendererIfc, + PartKeepr\Printing\SimpleRendererFactory, + PartKeepr\Printing\Exceptions\RendererNotFoundException, + PartKeepr\Printing\Utils\DecodeConfiguration, + PartKeepr\Printing\Utils\Placeholder, + PartKeepr\StorageLocation\StorageLocation + ; + +/** + * This class implements a printing renderer which renders the data + * to the ZPL printing language. The idea behind is to create a template using + * the Designer software provided by Zebra, print it to a file and then use this + * file as a template here. + */ +class ZebraLabelWriterRenderer implements RendererIfc{ + /** + * Our default configuration for this label renderer. + * @var array + */ + private $defaultConfiguration = array( + 'part' => array( 'template' => +<<<'EOD' +CT~~CD,~CC^~CT~ +^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR3,3~SD8^JUS^LRN^CI0^XZ +^XA +^MMT +^PW320 +^LL0176 +^LS0 +^FT16,44^A0N,34,33^FH\^FD<<name>>^FS +^FT16,76^A0N,23,24^FH\^FD<<description>>^FS +^BY3,3,33^FT49,146^BCN,,Y,N +^FD>;<<id>>^FS +^PQ1,0,1,Y^XZ +EOD + ) + ); + + /** + * The rendered data up to now. + */ + protected $out = ""; + + /** + * This is the current active configuration. + * @var unknown + */ + protected $configuration; + + /** + * This is the configuration passed to this class. + * @var unknown + */ + protected $configurationIn; + + public function __construct (array $obj, $cfgString ) { + $configuration = DecodeConfiguration::decode($cfgString); + $this->configurationIn = $configuration; + } + + public function passRenderingData( $data ){ + // Here we got our data passed. We have to decide how we want + // to render the data, so we dispatch it to our internal rendering + // methods to make the code more clear. + if ( is_array( $data ) ){ + if (count($data)>0){ + $elem = reset($data); + if ($elem instanceof StorageLocation){ + $this->renderStorageLocations($data); + } elseif ($elem instanceof Part){ + $this->renderParts($data); + } + else{ + throw new RendererNotFoundException("Unable to handle object type with this renderer.", + get_class($elem),array("StorageLocation")); + } + } + } + else{ + // If the selected object is not an array, we make an array first + // to have only one case to handle. + passRenderingData( array( $ata ) ); + } + } + + /** + * This method renders an array of Parts to our sheet. + * @param array $parts + */ + private function renderParts( array $parts ){ + $this->configuration = array_merge( $this->defaultConfiguration['part'], $this->configurationIn ); + + foreach ($parts as $part){ + $this->renderSinglePart( $part ); + } + } + + + private function renderSinglePart( Part $part ){ + $dataReplacement = new Placeholder( $part, "<<", ">>"); + $this->out .= $dataReplacement->apply($this->configuration['template']) . "\n"; + } + + public function getSuggestedExtension(){ + return "zpl"; + } + + public function storeResult( $outFile ){ + $file = fopen($outFile,'w'); + fwrite( $file, $this->out ); + fclose( $file ); + } + + public function outputResult($filename){ + return $this->out; + } +} + +// We have to register this class to the registry. +// Only if the class is registered, it can be found by the +// registry and you will see it in the application. +RendererFactoryRegistry::getInstance()->registerFactory( + new SimpleRendererFactory("Zebra Label Renderer", + "PartKeepr\Printing\Renderer\ZebraLabelWriterRenderer", + array("PartKeepr\Part\Part"), + array() + ) + ); diff --git a/src/backend/PartKeepr/Printing/RendererFactoryIfc.php b/src/backend/PartKeepr/Printing/RendererFactoryIfc.php @@ -0,0 +1,45 @@ +<?php + +namespace PartKeepr\Printing; + +/** + * This interface can be used to support different + * sets of renderer. One set of renderer is often used + * to identify a render method like te output format or a + * specific layout of the label. + */ +interface RendererFactoryIfc{ + /** + * This method will create a new concrete instance of the renderer ready to + * use. The returned renderer will always support the RenderingIfc which + * can be used to pass the data to the renderer. + * + * @param array $objects An Array of object instances. Must contain at least all + * objects which are requested by getParameterObjectTypes() + * @param string $configuration + * @return RenderingIfc New renderer to use for rendering. + */ + public function createInstance(array $objects, $configuration); + + /** + * Returns a name for the renderer which is readable to humans. + */ + public function getName(); + + /** + * Returns the class names of the created instances. + */ + public function getCreatedClassname(); + + /** + * Retruns the supported classes which can be passed to the renderer + * via passRenderingdata($data). + */ + public function getSupportedClassesForRendering(); + + /** + * Returns the object types, which are required for operation. + * @return An array holding the names of the type instances needed. + */ + public function getParameterObjectTypes(); +} diff --git a/src/backend/PartKeepr/Printing/RendererFactoryRegistry.php b/src/backend/PartKeepr/Printing/RendererFactoryRegistry.php @@ -0,0 +1,78 @@ +<?php + +namespace PartKeepr\Printing; + +use PartKeepr\Util\Singleton, + PartKeepr\Printing\RendererFactoryIfc, + PartKeepr\Printing\Exceptions\RendererNotFoundException; + +/** + * This registry is used to collect all possible RendererFactories + * and provide an interface to fetch them as needed. + */ +class RendererFactoryRegistry extends Singleton { + var $factories = array(); + + var $findRendererAlreadyRun = false; + + /** + * Returns a renderer factory by its class name. + * @param string className The name of the class to fetch the factory for. + */ + public function getRendererFactory( $className ){ + $this->findRenderer(); + + if ($className===null){ + return $this->factories; + } + + if (!array_key_exists($className, $this->factories) ){ + throw new RendererNotFoundException("No renderer found in registry.",$className, array_keys($this->factories)); + } + + return $this->factories[ $className ]; + } + + /** + * Retruns all renderer which are suitable for rendering the fiven data. + * + * @param string $renderDataClassName + */ + public function getRendererFactoryForRenderData( $renderDataClassName ){ + $this->findRenderer(); + + $rval = array(); + foreach ($this->factories as $factory) { + $supported = $factory->getSupportedClassesForRendering(); + if ( array_search( $renderDataClassName, $supported ) !== false ){ + array_push($rval, $factory); + } + } + return $rval; + } + + /** + * Registers a new factory to the tregistry. + * + * @param RendererFactoryIfc $factory The factory to register. + */ + public function registerFactory( RendererFactoryIfc $factory ){ + $this->factories[ $factory->getCreatedClassname() ] = $factory; + } + + /** + * This method searches for the renderer in den Renderer directory and + * loads them. + */ + public function findRenderer(){ + if (!$this->findRendererAlreadyRun){ + foreach (glob(dirname(__FILE__).DIRECTORY_SEPARATOR."Renderer".DIRECTORY_SEPARATOR."*.php") as $filename) { + if (is_file($filename)) { + require_once($filename); + } + } + + $this->findRendererAlreadyRun = true; + } + } +} diff --git a/src/backend/PartKeepr/Printing/RendererIfc.php b/src/backend/PartKeepr/Printing/RendererIfc.php @@ -0,0 +1,30 @@ +<?php +namespace PartKeepr\Printing; + +/** + * The rendering interface is used to pass data to a renderer and + * retrieve the rendered results afterwards. How the data is rendered + * and which data can be processed is implementation specific. + */ +interface RendererIfc{ + /** + * Passes the data which should be rendered. The supported + * data differs from renderer to renderer and is documented + * in the concrete implementations. + * + * @param unknown $data Data to pass to the renderer. + */ + public function passRenderingData( $data ); + + /** + * Returns the suggestion of the file extension to use. + */ + public function getSuggestedExtension(); + + /** + * Store the created result to the given filename. Filename + * can also be treated as a directory if needed. + * @param filename Filename to store the final file. + */ + public function storeResult( $filename ); +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/SimpleRendererFactory.php b/src/backend/PartKeepr/Printing/SimpleRendererFactory.php @@ -0,0 +1,61 @@ +<?php + +namespace PartKeepr\Printing; + +use PartKeepr\Printing\PageBasicLayout\PageBasicLayout; + +/** + * This factory is used to select a concrete renderer for + * rendering the content later. + * + * One renderer can support a specific layout of the later + * printed labels or the sheet. The factory itself supports + * extending it by adding a plugable extension interface. + * + * The constructor of the given object must match the following signature: + * __construct( PrintingPageBasicLayout $layout, array $configuration ) + */ +class SimpleRendererFactory implements RendererFactoryIfc{ + private $name; + + private $className; + + private $supportedClasses; + + private $neededParameterObjectTypes; + + /** + * Constructas a new simple factory for the given class. + * + * @param string $name + * @param string $className + * @param array $supportedClasses + */ + public function __construct( $name, $className, array $supportedClasses, array $neededParameterObjectTypes){ + $this->name = $name; + $this->className = $className; + $this->supportedClasses = $supportedClasses; + $this->neededParameterObjectTypes = $neededParameterObjectTypes; + } + + public function createInstance(array $objects, $configuration){ + return new $this->className($objects, $configuration); + } + + public function getName(){ + return $this->name; + } + + public function getCreatedClassname(){ + return $this->className; + } + + public function getSupportedClassesForRendering(){ + return $this->supportedClasses; + } + + public function getParameterObjectTypes(){ + return $this->neededParameterObjectTypes; + } +} + diff --git a/src/backend/PartKeepr/Printing/Utils/DecodeConfiguration.php b/src/backend/PartKeepr/Printing/Utils/DecodeConfiguration.php @@ -0,0 +1,31 @@ +<?php +namespace PartKeepr\Printing\Utils; + +use PartKeepr\Printing\Exceptions\InvalidArgumentException; + +/** + * Helper class which enables us to decode a configuration which was passed as JSON + * string. The class will also do error handling of data inside the JSON. + */ +class DecodeConfiguration{ + /** + * Method decodes the incoming string and returns the JSON decoded array if + * everything was fine. If a syntax error was detected, it throws an exception. + * + * @param string $string + * @throws InvalidArgumentException + */ + public static function decode( $string ){ + $jsonDecoded = json_decode(trim($string), true); + if ($jsonDecoded===null){ + if (strlen($string) == 0 ){ + $jsonDecoded = array(); + } + else{ + throw new InvalidArgumentException( PartKeepr::i18n('Extended rendering configuration contains an error!')); + } + } + + return $jsonDecoded; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Printing/Utils/PercentOrNumericHelper.php b/src/backend/PartKeepr/Printing/Utils/PercentOrNumericHelper.php @@ -0,0 +1,101 @@ +<?php +namespace PartKeepr\Printing\Utils; + +/** + * This is a helper class to extract a number or a percentage + * from an argument. + * The following string represents a percentage: 100% + * And just the raw number will be dealt as absolute number: 100 + * + * Additionally in percentage mode an additional absolute offset can + * be given: 50% - 4 => + */ +class PercentOrNumericHelper{ + private $percentageValue; + + private $absoluteValue; + + /** + * Constructs new class und use the parameter as argument. + * @param $toAnalyze This is our absolute numberic or percentage value + */ + public function __construct($toAnalyze){ + if (is_numeric($toAnalyze)){ + $this->percentageValue = 0; + $this->absoluteValue = $toAnalyze; + } + else{ + $this->isAbsolute = false; + if (preg_match('/^\s*([+-]*[0-9]+)\s*%\s*(([+-])\s*([0-9]+))*\s*$/', $toAnalyze, $matches ) === 1 ){ + $this->percentageValue = $matches[1] / 100; + if (count($matches)>2){ + $this->absoluteValue = $matches[2]; + }else{ + $this->absoluteValue = 0; + } + } + else { + // Error in string, throw user error??? + $this->percentageValue = 0; + $this->absoluteValue = 0; + } + } + } + + /** + * Converts this value to be between minValue and maxValue. + * If the analyzed value is: + * - percentage: The percentage represents the position between min and max + * - absolute: The absolute value will be used as is if inside bounds and will + * be min if smaller or max if greater + */ + public function getValue( $minValue, $maxValue ){ + if ($minValue>$maxValue ){ + list($minValue,$maxValue) = array($maxValue,$minValue); + } + + $value = $this->percentageValue * ($maxValue - $minValue) + $minValue + $this->absoluteValue; + + $value = min( $maxValue, $value); + return max( $minValue, $value); + } + + /** + * This method works like getValue, but will wrap around if the value is out of bound. + */ + public function getValueWrap( $minValue, $maxValue ){ + if ($minValue>$maxValue ){ + list($minValue,$maxValue) = array($maxValue,$minValue); + } + $distance = ($maxValue - $minValue); + $calculatedDistance = $this->percentageValue * $distance + $this->absoluteValue; + + $distanceTimes = floor( $calculatedDistance / $distance ); + $calculatedDistance -= $distanceTimes * $distance; + + return $minValue + $calculatedDistance; + } + + /** + * Returns the value without any bounding. Returns either the absolute value + * or $range * $percentage + */ + public function getValueUnbound( $range ){ + return $range * $this->percentageValue + $this->absoluteValue; + } + + /** + * Returns the percentage part of the value. + */ + public function getPercentageValue(){ + return $this->percentageValue; + } + + /** + * Returns the absolute part of the value. + */ + public function getAbsoluteValue(){ + return $this->absoluteValue; + } + +} diff --git a/src/backend/PartKeepr/Printing/Utils/Placeholder.php b/src/backend/PartKeepr/Printing/Utils/Placeholder.php @@ -0,0 +1,55 @@ +<?php +namespace PartKeepr\Printing\Utils; + +use + PartKeepr\Part\Part, + PartKeepr\StorageLocation\StorageLocation; + +/** + * This class is used to provide the ability to replace text inside a string. + * These texts can have various sources. Previous to the use the placeholder class + * needs to be initialized with the set to use. + */ +class Placeholder{ + /** + * This is our array with the replacements for search and replace. + */ + private $replacements = array(); + + /** + * Constructs a new placeholder class and use the object to fill + * the placeholder with. + * + * @param unknown $object + */ + public function __construct ( $object, $begin = "<<", $end = ">>" ) { + $replace = array(); + if ( $object instanceof Part ){ + $replace = array( + 'id' => $object->getId(), + 'name' => $object->getName(), + 'internalNumber' => $object->getInternalPartNumber(), + 'description' => $object->getDescription(), + 'categoryFull' => $object->getCategory()->getCategoryPath(), + 'categoryLast' => $object->getCategory()->getName(), + 'footprintName' => $object->getFootprint() === null ? '': $object->getFootprint()->getName(), + 'storageLocationName' => $object->getStorageLocation() === null ? '': $object->getStorageLocation()->getName() + ); + } + + // Finally apply begin and ends. + foreach( $replace as $key => $value ){ + $this->replacements[$begin.$key.$end] = $value; + } + } + + /** + * Applies the placeholders and retruns the replaced string. + * + * @param string $text Text to replace the placeholders in. + */ + public function apply( $text ){ + return strtr($text, $this->replacements); + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/REST/Request.php b/src/backend/PartKeepr/REST/Request.php @@ -36,10 +36,17 @@ class Request { $this->controller = $_REQUEST["service"]; } - $serviceName = $this->controller."Service"; - $namespace = 'PartKeepr\\'; - $cat = $this->controller . "\\"; - $fullName= $namespace . $cat . $serviceName; + // Here we split our controller path. We got then elements: + // - The element is used for the class name too. + // - All elements will be used to build the path to the class + // Foo.Bar => PartKeepr\\Foo\\Bar\\BarService + $serviceClassPath = explode( '.', $this->controller ); + array_unshift($serviceClassPath,'PartKeepr' ); + $serviceName = end($serviceClassPath)."Service"; + reset($serviceClassPath); + array_push($serviceClassPath, $serviceName); + + $fullName = implode("\\",$serviceClassPath); $class = new $fullName($this->getParams()); @@ -88,10 +95,10 @@ class Request { } // 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 + $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)) { diff --git a/src/backend/PartKeepr/Service/FilterExtractor.php b/src/backend/PartKeepr/Service/FilterExtractor.php @@ -0,0 +1,38 @@ +<?php + +namespace PartKeepr\Service; + +use PartKeepr\Service\Service; + +/** + * This class can be used to extract the filter parameters from requests. + */ +class FilterExtractor{ + private $filters; + + public function __construct( Service $service ){ + $this->filters = array(); + + if ($service->hasParameter("filter")) { + $tmp = json_decode($service->getParameter("filter"), true); + + foreach ($tmp as $item) { + if ( array_key_exists("property", $item) + && array_key_exists("value", $item)) { + $this->filters[ $item['property'] ] = $item["value"]; + } + } + } + } + + public function get( $property ){ + return $this->filters[$property]; + } + + public function has( $property ){ + return array_key_exists( $property, $this->filters ); + } +} + +$filter = ""; + diff --git a/src/backend/PartKeepr/Service/ServiceManager.php b/src/backend/PartKeepr/Service/ServiceManager.php @@ -111,6 +111,14 @@ class ServiceManager extends Singleton { if (!$this->service->mayCall($call)) { $allowCall = false; } + + // Update Seen flag of the current user. + if ($session !== null) { + $user = $session->getUser(); + if ($user !== null) { + $user->updateSeen(); + } + } } if (!$allowCall) { diff --git a/src/backend/PartKeepr/Session/Session.php b/src/backend/PartKeepr/Session/Session.php @@ -38,6 +38,10 @@ class Session { $this->sessionid = session_id(); } + public function getId(){ + return $this->id; + } + public function getSessionID () { return $this->sessionid; } diff --git a/src/backend/PartKeepr/User/User.php b/src/backend/PartKeepr/User/User.php @@ -19,6 +19,9 @@ class User extends BaseEntity implements Serializable, Deserializable { /** @Column(type="boolean") */ private $admin; + /** @Column(type="datetime",nullable=true) */ + private $lastSeen; + /** * Creates a new user object. * @@ -142,6 +145,21 @@ class User extends BaseEntity implements Serializable, Deserializable { } /** + * Updates the last seen field to the current time. + */ + public function updateSeen() { + $this->lastSeen = new \DateTime("now"); + } + + /** + * Retrieve the last seen flag for a user. + * @return \DateTime + */ + public function getLastSeen() { + return $this->lastSeen; + } + + /** * Serializes the user object and returns it as array, suitable * to process via json_encode. * @param none diff --git a/src/backend/PartKeepr/User/UserManager.php b/src/backend/PartKeepr/User/UserManager.php @@ -1,58 +1,26 @@ <?php namespace PartKeepr\User; -use PartKeepr\Util\Singleton, - PartKeepr\User\User, +use + PartKeepr\Category\CategoryManager, + PartKeepr\Manager\AbstractManager, PartKeepr\PartKeepr, PartKeepr\User\Exceptions\InvalidLoginDataException, - PartKeepr\Category\CategoryManager, PartKeepr\User\Exceptions\UserAlreadyExistsException, - PartKeepr\User\Exceptions\UserNotFoundException; - -class UserManager extends Singleton { - /** - * Returns a list of users. - * - * @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 user's name by, default empty - */ - public function getUsers ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { - - $qb = PartKeepr::getEM()->createQueryBuilder(); - $qb->select("st.id, st.username")->from("PartKeepr\User\User","st"); + PartKeepr\User\Exceptions\UserNotFoundException, + PartKeepr\User\User; - if ($filter != "") { - $qb = $qb->where("LOWER(st.username) 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\User\User","st"); - - - - if ($filter != "") { - $totalQueryBuilder = $totalQueryBuilder->where("LOWER(st.username) LIKE :filter"); - $totalQueryBuilder->setParameter("filter", "%".strtolower($filter)."%"); - } - - $totalQuery = $totalQueryBuilder->getQuery(); - - return array("data" => $result, "totalCount" => $totalQuery->getSingleScalarResult()); +class UserManager extends AbstractManager { + public function getEntityName () { + return 'PartKeepr\User\User'; + } + + public function getQueryFields () { + return array("id","username"); + } + + public function getDefaultSortField () { + return "username"; } /** diff --git a/src/backend/PartKeepr/User/UserService.php b/src/backend/PartKeepr/User/UserService.php @@ -1,11 +1,14 @@ <?php namespace PartKeepr\User; -use PartKeepr\Service\RestfulService, - PartKeepr\Service\Service, +use PartKeepr\PartKeepr, - PartKeepr\User\User, - PartKeepr\Session\SessionManager; + PartKeepr\Service\FilterExtractor, + PartKeepr\Manager\ManagerFilter, + PartKeepr\Service\RestfulService, + PartKeepr\Service\Service, + PartKeepr\Session\SessionManager, + PartKeepr\User\User; class UserService extends Service implements RestfulService { @@ -25,21 +28,33 @@ class UserService extends Service implements RestfulService { return array("data" => UserManager::getInstance()->getUser($this->getParameter("id"))->serialize()); } else { - if ($this->hasParameter("sort")) { - $tmp = json_decode($this->getParameter("sort"), true); - - $aSortParams = $tmp[0]; - } else { - $aSortParams = array( - "property" => "username", - "direction" => "ASC"); - } - return UserManager::getInstance()->getUsers( - $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", "")); + $filter = new ManagerFilter($this); + $filter->setFilterCallback(array($this, "filterCallback")); + return UserManager::getInstance()->getList($filter); + } + } + + + public function filterCallback ($queryBuilder) { + $filter = new FilterExtractor($this); + + if ( $this->hasParameter("query") + && $query = $this->getParameter("query") ){ + + $queryBuilder->andWhere("LOWER(q.username) LIKE :query"); + $queryBuilder->setParameter("query", $query ); + } + + if ( $filter->has("lastSeenMax") + && $lastSeenMax = $filter->get('lastSeenMax') ){ + $queryBuilder->andWhere("q.lastSeen > :lastSeenMax"); + $queryBuilder->setParameter("lastSeenMax", new \DateTime('- '.$lastSeenMax.' seconds')); + } + + if( $filter->has('hideCurrentUser') + && $filter->get('hideCurrentUser') == '1'){ + $queryBuilder->andWhere("q.id != :currentUserId"); + $queryBuilder->setParameter("currentUserId", SessionManager::getCurrentSession()->getUser()->getId() ); } } diff --git a/src/backend/PartKeepr/Util/Exceptions/ObjectNotFoundException.php b/src/backend/PartKeepr/Util/Exceptions/ObjectNotFoundException.php @@ -0,0 +1,17 @@ +<?php +namespace PartKeepr\Util\Exceptions; + +use PartKeepr\Util\SerializableException, + PartKeepr\PartKeepr; + +/** + * Thrown when an object was no found. + */ +class ObjectNotFoundException extends SerializableException { + public function __construct ($class, $id) { + parent::__construct( + sprintf( + PartKeepr::i18n("The object of type %s with the id %s could not be found in the database."), + $class, $id)); + } +} diff --git a/src/backend/PartKeepr/Util/SimpleObjectManager.php b/src/backend/PartKeepr/Util/SimpleObjectManager.php @@ -0,0 +1,39 @@ +<?php +namespace PartKeepr\Util; + +use PartKeepr\PartKeepr, + PartKeepr\Util\Exceptions\ObjectNotFoundException, + PartKeepr\Util\Singleton; + +class SimpleObjectManager extends Singleton{ + /** + * Contains the object we are responsible for. + * @var string + */ + private $classType; + + protected function __construct ($classType) { + $this->classType = $classType; + } + + public function getObjectById ($id) { + $obj = PartKeepr::getEM()->find($classType, $id); + if ($obj) { + return $obj; + } else { + throw new ObjectNotFoundException($classType, $id); + } + } + + public function addObject ($obj) { + PartKeepr::getEM()->persist($obj); + PartKeepr::getEM()->flush(); + return $obj; + } + public function deleteObjectById ($id) { + $obj = $this->getPageBasicLayout($id); + + PartKeepr::getEM()->remove($obj); + PartKeepr::getEM()->flush(); + } +}+ \ No newline at end of file diff --git a/src/frontend/file.php b/src/frontend/file.php @@ -4,6 +4,7 @@ use PartKeepr\Footprint\FootprintAttachment, PartKeepr\Project\ProjectAttachment, PartKeepr\Part\PartAttachment, PartKeepr\UploadedFile\TempUploadedFile, + PartKeepr\UploadedFile\UploadedFile, PartKeepr\PartKeepr, PartKeepr\Image\Image, PartKeepr\Manufacturer\ManufacturerICLogo; @@ -33,6 +34,9 @@ if (substr($id, 0, 4) === "TMP:") { case "PartKeepr.ProjectAttachment": $file = ProjectAttachment::loadById($id); break; + case "Print": + $file = TempUploadedFile::loadById($id); + break; default: $file = null; // Add default image? diff --git a/src/frontend/js/Components/Editor/EditorComponent.js b/src/frontend/js/Components/Editor/EditorComponent.js @@ -173,7 +173,7 @@ Ext.define('PartKeepr.EditorComponent', { Ext.Object.merge(config, { autoLoad: true, model: this.model, - autoSync: false, // Do not change. If true, new (empty) records would be immediately commited to the database. + autoSync: false, // Do not change. If true, new (empty) records would be immediately committed to the database. remoteFilter: true, remoteSort: true, pageSize: 15}); diff --git a/src/frontend/js/Components/Grid/GridMenuPlugin.js b/src/frontend/js/Components/Grid/GridMenuPlugin.js @@ -38,6 +38,11 @@ Ext.define("PartKeepr.GridMenuPlugin", { handler: this.exportWiki, scope: this }] + },{ + icon: 'resources/fugue-icons/icons/printer.png', + text: i18n('Print ...'), + handler: this.exportPrint, + scope: this }] }); @@ -70,6 +75,21 @@ Ext.define("PartKeepr.GridMenuPlugin", { this.doExport(Ext.ux.exporter.Exporter.exportAny(this.grid, "excel", {}), this.getExportFilename() + ".xlsx"); }, /** + * Exports selection to print + */ + exportPrint: function () { + selection = this.grid.getSelectionModel().getSelection(); + var ids = new Array(); + for (var i=0;i<selection.length;i++) { + ids.push(selection[i].get("id")); + } + + var val = Ext.create("PartKeepr.PrintingWindow"); + val.setObjectType('PartKeepr\\Part\\Part'); + val.setObjectIds(ids); + val.show(); + }, + /** * Returns the filename without extension for the grid. Defaults to the grid's title * @returns {String} the filename */ diff --git a/src/frontend/js/Components/MenuBar.js b/src/frontend/js/Components/MenuBar.js @@ -74,9 +74,21 @@ Ext.define('PartKeepr.MenuBar', { text: i18n("Stock History"), handler: this.showStockHistory, icon: 'resources/fugue-icons/icons/notebook.png' - } - - ] + },{ + text: i18n("Print and Labeling"), + icon: 'resources/fugue-icons/icons/printer.png', + menu: [ + { + text: i18n("Storage Locations"), + handler: this.showPrintStorageLocations + },{ + text : i18n("Edit Label Layout"), + handler : this.editPrintingPageBasicLayout + },{ + text : i18n("Edit Configuration"), + handler : this.editPrintingJobConfiguration + }] + }] }); this.systemMenu = Ext.create('Ext.menu.Menu', { @@ -284,6 +296,36 @@ Ext.define('PartKeepr.MenuBar', { PartKeepr.getApplication().addItem(j); j.show(); + }, + showPrintStorageLocations: function () { + var j = Ext.create("PartKeepr.PrintStorageLocations", { + title: i18n("Printing and Labeling: Storage Locations"), + iconCls: 'icon-drill', + closable: true + }); + + PartKeepr.getApplication().addItem(j); + j.show(); + }, + editPrintingPageBasicLayout: function () { + var j = Ext.create("PartKeepr.Printing.PageBasicLayoutEditorComponent", { + title: i18n("Labeling Layout"), + iconCls: 'icon-drill', + closable: true + }); + + PartKeepr.getApplication().addItem(j); + j.show(); + }, + editPrintingJobConfiguration: function () { + var j = Ext.create("PartKeepr.Printing.PrintingJobConfigurationEditorComponent", { + title: i18n("Printing Configuration"), + iconCls: 'icon-drill', + closable: true + }); + + PartKeepr.getApplication().addItem(j); + j.show(); } }); \ No newline at end of file diff --git a/src/frontend/js/Components/Printing/PageBasicLayoutEditor.js b/src/frontend/js/Components/Printing/PageBasicLayoutEditor.js @@ -0,0 +1,102 @@ +Ext.define('PartKeepr.Printing.PageBasicLayoutEditor', { + extend: 'PartKeepr.Editor', + alias: 'widget.Printing.PageBasicLayoutEditor', + saveText: i18n("Save Label Layout"), + + layout: 'column', + + initComponent: function () { + this.items = [{ + columnWidth: 1, + minWidth: 500, + layout: 'anchor', + xtype: 'container', + margin: '0 5 0 0', + items: [{ + xtype: 'textfield', + name: 'name', + anchor: '100%', + labelWidth: 130, + fieldLabel: i18n("Layout Name") + }, + { + xtype: 'textfield', + name: 'comment', + anchor: '100%', + labelWidth: 130, + fieldLabel: i18n("Comment") + }, + { + xtype: 'textfield', + name: 'paperSize', + anchor: '100%', + labelWidth: 130, + fieldLabel: i18n("Paper Sheet Size") + }, + { + xtype: 'checkbox', + hideEmptyLabel: false, + fieldLabel: i18n("Portrait Sheet"), + labelWidth: 130, + name: 'paperPortrait' + }, + { + xtype: 'numberfield', + fieldLabel: i18n("Number of Columns"), + labelWidth: 130, + minValue: 1, + allowDecimals: false, + name: 'columnCount' + }, + { + xtype: 'numberfield', + fieldLabel: i18n("Number of Rows"), + labelWidth: 130, + minValue: 1, + allowDecimals: false, + name: 'rowCount' + }, + { + xtype: 'numberfield', + fieldLabel: i18n("Cell Width"), + boxLabel: "mm", + labelWidth: 130, + minValue: 0, + name: 'cellWidthInMM' + }, + { + xtype: 'numberfield', + fieldLabel: i18n("Cell Height"), + boxLabel: "mm", + labelWidth: 130, + minValue: 0, + name: 'cellHeightInMM' + }, + { + xtype: 'numberfield', + fieldLabel: i18n("X Position of top left"), + boxLabel: "mm", + labelWidth: 130, + minValue: 0, + name: 'topLeftXInMM' + }, + { + xtype: 'numberfield', + fieldLabel: i18n("Y Position of top left"), + boxLabel: "mm", + labelWidth: 130, + minValue: 0, + name: 'topLeftYInMM' + } + ]} + ]; + + this.on("startEdit", this.onStartEdit, this); + this.callParent(); + }, + onStartEdit: function () { + this.store.getProxy().extraParams.PageBasicLayout = this.record.get("id"); + this.store.load(); + } + +});+ \ No newline at end of file diff --git a/src/frontend/js/Components/Printing/PageBasicLayoutEditorComponent.js b/src/frontend/js/Components/Printing/PageBasicLayoutEditorComponent.js @@ -0,0 +1,19 @@ +Ext.define('PartKeepr.Printing.PageBasicLayoutEditorComponent', { + extend: 'PartKeepr.EditorComponent', + alias: 'widget.Printing.PageBasicLayoutEditorComponent', + navigationClass: 'PartKeepr.Printing.PageBasicLayoutGrid', + editorClass: 'PartKeepr.Printing.PageBasicLayoutEditor', + newItemText: i18n("New Label Layout"), + model: 'PartKeepr.Printing.PageBasicLayout', + initComponent: function () { + this.createStore({ + sorters: [{ + proxy: PartKeepr.getRESTProxy("Printing.PageBasicLayout"), + property: 'name', + direction:'ASC' + }] + }); + + this.callParent(); + } +});+ \ No newline at end of file diff --git a/src/frontend/js/Components/Printing/PageBasicLayoutGrid.js b/src/frontend/js/Components/Printing/PageBasicLayoutGrid.js @@ -0,0 +1,14 @@ +Ext.define('PartKeepr.Printing.PageBasicLayoutGrid', { + extend: 'PartKeepr.EditorGrid', + alias: 'widget.Printing.PageBasicLayoutGrid', + + automaticPageSize: true, + + columns: [ + {header: i18n("Label Layout"), dataIndex: 'name', flex: 1} + ], + addButtonText: i18n("Add Label Layout"), + addButtonIcon: 'resources/fugue-icons/icons/wooden-box--plus.png', + deleteButtonText: i18n("Delete Label Layout"), + deleteButtonIcon: 'resources/fugue-icons/icons/wooden-box--minus.png' +});+ \ No newline at end of file diff --git a/src/frontend/js/Components/Printing/PrintStorageLocations.js b/src/frontend/js/Components/Printing/PrintStorageLocations.js @@ -0,0 +1,110 @@ +/** + * Represents the printing view for the storage locations + */ +Ext.define('PartKeepr.PrintStorageLocations', { + extend: 'Ext.panel.Panel', + alias: 'widget.PrintStorageLocations', + + bodyStyle: 'background:#DBDBDB;padding: 5px', + border: false, + + defaults: { + bodyStyle: 'padding:10px' + }, + + layout: 'border', + + initComponent: function () { + this.createStores(); + + this.printExecutor = Ext.create('PartKeepr.PrintingExecutor'); + + this.upperGridEditing = Ext.create('Ext.grid.plugin.CellEditing', { + clicksToEdit: 1 + }); + + this.locationList = Ext.create("PartKeepr.BaseGrid", { + selModel: { + mode: 'MULTI' + }, + selType: 'checkboxmodel', + flex: 1, + columns: [{ + header: i18n("Storage location name"), dataIndex: 'name', + flex: 1 + }], + store: this.storeStorageLocation, + plugins: [ this.upperGridEditing ] + }); + + this.editing = Ext.create('Ext.grid.plugin.CellEditing', { + clicksToEdit: 1 + }); + + this.printIt = Ext.create('Ext.button.Button', { + text: i18n("Print"), + width: 160, + listeners: { + scope: this, + click: this.onPrintClick + } + + }); + + this.items = [ + { + title: i18n("Choose locations to print label for"), + split: true, + minHeight: 300, + height: 300, + bodyStyle: 'background:#DBDBDB;padding: 10px;', + layout: { + type: 'vbox', + align : 'stretch', + pack : 'start' + }, + region: 'north', + items: [ + this.locationList, + { + layout: { + type: 'hbox', + pack: 'start' + }, + margins: { + top: 10 + }, + border: false, + bodyStyle: 'background:#DBDBDB', + items: [ this.printIt] + } + ] + }]; + + this.callParent(); + }, + onPrintClick: function () { + selection = this.locationList.getSelectionModel().getSelection(); + var ids = new Array(); + for (var i=0;i<selection.length;i++) { + ids.push(selection[i].get("id")); + } + + var val = Ext.create("PartKeepr.PrintingWindow"); + val.setObjectType('PartKeepr\\StorageLocation\\StorageLocation'); + val.setObjectIds(ids); + val.show(); + }, + /** + * Creates the store used in this view. + */ + createStores: function () { + var config = { + autoLoad: true, + model: "PartKeepr.StorageLocation", + pageSize: -1 + }; + + this.storeStorageLocation = Ext.create('Ext.data.Store', config); + } +});+ \ No newline at end of file diff --git a/src/frontend/js/Components/Printing/PrintingExecutor.js b/src/frontend/js/Components/Printing/PrintingExecutor.js @@ -0,0 +1,38 @@ +/** + * This class is responsible for executing a printing request and showing the + * response from the server. + */ +Ext.define('PartKeepr.PrintingExecutor', { + constructor: function () { + }, + /** + * Send the print request to the server and get the incoming response. + * Afterwards the requested file will be opened. + * + * @param configId The ID of the configuration to use for printing. + * @param objectType The object type (full path) the ids are from + * @param ids The ids of the objects which should be printed. + */ + executePrint: function (configId, objectType, ids, target) { + if (configId===null){ + Ext.Msg.alert(i18n("Error"),i18n("No Layout selected for printing. Please select a layout and try again.")); + return; + } + + var call = new PartKeepr.ServiceCall( + "Printing", + "startExport"); + call.setParameter("objectType",objectType); + call.setParameter("configuration",configId); + call.setParameter("target",target); + call.setParameter("ids",ids.join(",")); + + call.setHandler(Ext.bind(this.onPrintingFinished, this, [target === null], true )); + call.doCall(); + }, + onPrintingFinished: function(reply, downloadDirectly) { + if (downloadDirectly ) { + window.open('file.php?id=' + reply.fileid + '&type=Print'); + } + } +}); diff --git a/src/frontend/js/Components/Printing/PrintingJobConfigurationEditor.js b/src/frontend/js/Components/Printing/PrintingJobConfigurationEditor.js @@ -0,0 +1,185 @@ +Ext.define('PartKeepr.Printing.PrintingJobConfigurationEditor', { + extend: 'PartKeepr.Editor', + alias: 'widget.Printing.PrintingJobConfigurationEditor', + saveText: i18n("Save Configuration"), + + layout: 'column', + inSetRecordPhase: false, + + initComponent: function () { + this.createStores(); + + this.layoutSelector = Ext.create('Ext.form.field.ComboBox',{ + store: this.layoutStore, + name: 'pageLayout', + valueField: 'id', + displayField: 'name', + fieldLabel: i18n("Basic Page Layout"), + allowBlank: false, + labelWidth: 130 + } ); + + this.rendererSelector = Ext.create('Ext.form.field.ComboBox',{ + store: this.rendererStore, + name: 'exportRenderer', + valueField: 'id', + displayField: 'name', + fieldLabel: i18n("Renderer"), + allowBlank: false, + labelWidth: 130, + listeners: { + scope: this, + change: function (field, newValue) { + if (newValue){ + this.onChangeRenderer( newValue ); + } + } + } + } ); + + this.typeSelector = Ext.create('Ext.form.field.ComboBox',{ + store: this.typeStore, + name: 'objectType', + valueField: 'id', + displayField: 'name', + fieldLabel: i18n("Datatype"), + allowBlank: false, + labelWidth: 130 + } ); + + + this.items = [{ + columnWidth: 1, + minWidth: 500, + layout: 'anchor', + xtype: 'container', + margin: '0 5 0 0', + items: [{ + xtype: 'textfield', + name: 'name', + anchor: '100%', + labelWidth: 130, + fieldLabel: i18n("Name") + }, + { + xtype: 'textfield', + name: 'comment', + anchor: '100%', + labelWidth: 130, + fieldLabel: i18n("Comment") + }, + this.rendererSelector, + this.layoutSelector, + this.typeSelector, + { + xtype: 'textarea', + name: 'rendererConfiguration', + anchor: '100%', + labelWidth: 130, + fieldLabel: i18n("Additional Configuration") + } + ]} + ]; + + + this.on("startEdit", this.onStartEdit, this); + this.callParent(); + }, + editItem: function (record) { + this.inSetRecordPhase = true; + + // FIXME: How can a base method can be called in Javascript? + this.record = record; + this.getForm().loadRecord(this.record); + this.show(); + if (this.record.getRecordName() !== "") { + this._setTitle(this.record.getRecordName()); + } + + this.change = false; + this.fireEvent("startEdit", this); + + this.inSetRecordPhase = false; + }, + onChangeRenderer: function( renderer ) { + this.typeStore.getProxy().extraParams.renderer = renderer; + this.typeStore.load(); + if (!this.inSetRecordPhase) + this.typeSelector.clearValue(); + + var call = new PartKeepr.ServiceCall( + "Printing", + "getNeededParameters"); + call.setParameter("renderer",renderer); + + call.setHandler(Ext.bind(this.onReceivedNeededParameters, this)); + call.doCall(); + }, + onReceivedNeededParameters: function (data) { + var needPageBasicLayout = data.data.indexOf('PartKeepr\\Printing\\PageBasicLayout\\PageBasicLayout') != -1; + + if (needPageBasicLayout) { + this.layoutSelector.show(); + } else { + this.layoutSelector.hide(); + this.layoutSelector.clearValue(); + } + }, + onStartEdit: function () { + this.store.getProxy().extraParams.PrintingJobConfiguration = this.record.get("id"); + this.store.load(); + }, + createStores: function() { + this.layoutStore = Ext.create("Ext.data.Store", { + autoLoad: true, + model: 'PartKeepr.Printing.PageBasicLayout', + pageSize: -1 + }); + + this.typeStore = Ext.create("Ext.data.Store", { + model: 'PartKeepr.PrintingType', + proxy: { + type: 'ajax', + reader: { + type: 'json', + root: 'response.data' + }, + url : 'service.php', + extraParams: { + "service": "Printing", + "call": "getAvailableTypes", + "renderer": "" + }, + headers: { + session :PartKeepr.getApplication().getSession() + } + }, + autoLoad: false + }); + + + this.rendererStore = Ext.create("Ext.data.Store", { + model: 'PartKeepr.PrintingRenderer', + proxy: { + type: 'ajax', + reader: { + type: 'json', + root: 'response.data' + }, + url : 'service.php', + extraParams: { + "service": "Printing", + "call": "getAvailableRenderer" + }, + headers: { + session :PartKeepr.getApplication().getSession() + } + }, + autoLoad: true + }); + + + + + } +});+ \ No newline at end of file diff --git a/src/frontend/js/Components/Printing/PrintingJobConfigurationEditorComponent.js b/src/frontend/js/Components/Printing/PrintingJobConfigurationEditorComponent.js @@ -0,0 +1,19 @@ +Ext.define('PartKeepr.Printing.PrintingJobConfigurationEditorComponent', { + extend: 'PartKeepr.EditorComponent', + alias: 'widget.Printing.PrintingJobConfigurationEditorComponent', + navigationClass: 'PartKeepr.Printing.PrintingJobConfigurationGrid', + editorClass: 'PartKeepr.Printing.PrintingJobConfigurationEditor', + newItemText: i18n("New Label Layout"), + model: 'PartKeepr.Printing.PrintingJobConfiguration', + initComponent: function () { + this.createStore({ + sorters: [{ + proxy: PartKeepr.getRESTProxy("Printing.PrintingJobConfiguration"), + property: 'name', + direction:'ASC' + }] + }); + + this.callParent(); + } +});+ \ No newline at end of file diff --git a/src/frontend/js/Components/Printing/PrintingJobConfigurationGrid.js b/src/frontend/js/Components/Printing/PrintingJobConfigurationGrid.js @@ -0,0 +1,14 @@ +Ext.define('PartKeepr.Printing.PrintingJobConfigurationGrid', { + extend: 'PartKeepr.EditorGrid', + alias: 'widget.Printing.PrintingJobConfigurationGrid', + + automaticPageSize: true, + + columns: [ + {header: i18n("Printing Configuration"), dataIndex: 'name', flex: 1} + ], + addButtonText: i18n("Add Configuration"), + addButtonIcon: 'resources/fugue-icons/icons/wooden-box--plus.png', + deleteButtonText: i18n("Delete Configuration"), + deleteButtonIcon: 'resources/fugue-icons/icons/wooden-box--minus.png' +});+ \ No newline at end of file diff --git a/src/frontend/js/Components/Printing/PrintingWindow.js b/src/frontend/js/Components/Printing/PrintingWindow.js @@ -0,0 +1,142 @@ +/** + * The PrintingWindow can be used to configure a printing in more + * detail and execute the printing request with the right configuration. + */ +Ext.define('PartKeepr.PrintingWindow', { + extend: 'Ext.window.Window', + + constrainHeader: true, + layout: { + type: 'vbox', + align: 'left' + }, + buttonAlign:'center', + + width: 400, + minWidth: 400, + minHeight: 50, + height: 150, + + modal: true, + + executeText: i18n("Print"), + cancelText: i18n("Cancel"), + + title: i18n("Printing ..."), + + /** + * Creates the part editor and put it into the window. + */ + initComponent: function () { + this.objectType = null; + this.objectIds = null; + + this.configurationStore = Ext.create("Ext.data.Store", { + autoLoad: true, + model: 'PartKeepr.Printing.PrintingJobConfiguration', + pageSize: -1 + }); + + this.targetStore = Ext.create("Ext.data.Store", { + autoLoad: true, + model: 'PartKeepr.User', + pageSize: -1, + remoteFilter: true, + filters: [{ + property: 'lastSeenMax', + value: '60' + },{ + property: 'hideCurrentUser', + value: '1' + }], + listeners: { + scope: this, + load: function( xstore , record , option ) { + xstore.add({ id : '-1', username: i18n('Download as file') }, true); + } + } + }); + + this.configurationSelector = Ext.create('Ext.form.field.ComboBox',{ + store: this.configurationStore, + valueField: 'id', + displayField: 'name', + fieldLabel: i18n("Choose Configuration"), + allowBlank: false, + labelWidth: 140 + } ); + + + this.targetSelector = Ext.create('Ext.form.field.ComboBox',{ + store: this.targetStore, + valueField: 'id', + displayField: 'username', + fieldLabel: i18n("Choose Target"), + allowBlank: false, + labelWidth: 140 + } ); + this.items = [ this.configurationSelector, this.targetSelector ]; + + this.executeButton = Ext.create("Ext.button.Button", { + text: this.executeText, + icon: 'resources/fugue-icons/icons/disk.png', + handler: Ext.bind(this.onExecute, this) + }); + + this.cancelButton = Ext.create("Ext.button.Button", { + text: this.cancelText, + icon: 'resources/silkicons/cancel.png', + handler: Ext.bind(this.onCancel, this) + }); + + this.bottomToolbar = Ext.create("Ext.toolbar.Toolbar", { + enableOverflow: true, + defaults: {minWidth: 100}, + dock: 'bottom', + ui: 'footer', + pack: 'start', + items: [ this.executeButton, this.cancelButton ] + }); + + this.buttons = [ this.bottomToolbar ]; + + this.callParent(); + }, + /** + * Set the object type. They will be passed + * to the server later on. + */ + setObjectType: function( objectType ) { + this.objectType = objectType; + this.configurationStore.filter('objectType', objectType ); + + this.configurationSelector.setValue( PartKeepr.getApplication().getUserPreference("partkeepr.printing.lastUsedConfiguration."+objectType),'' ); + this.targetSelector.setValue( PartKeepr.getApplication().getUserPreference("partkeepr.printing.lastUsedTarget."+objectType),'' ); + }, + /** + * Set the ids of the objects which should be printed/exported + */ + setObjectIds: function( ids ) { + this.objectIds = ids; + }, + onCancel: function () { + this.close(); + }, + onExecute: function () { + executor = Ext.create('PartKeepr.PrintingExecutor'); + if (this.objectIds===null || this.objectType===null){ + Ext.Msg.alert(i18n("Error"),i18n("Unable to handle request: Missing data! This is a Bug, please report it!")); + } + config = this.configurationSelector.getValue(); + target = this.targetSelector.getValue() == -1 ? null : this.targetSelector.getValue(); + if (config!==null){ + executor.executePrint( config, this.objectType, this.objectIds, target); + if (this.objectType!==null){ + PartKeepr.getApplication().setUserPreference("partkeepr.printing.lastUsedConfiguration."+this.objectType, this.configurationSelector.getValue() ); + PartKeepr.getApplication().setUserPreference("partkeepr.printing.lastUsedTarget."+this.objectType, this.targetSelector.getValue() ); + } + + this.close(); + } + } +}); diff --git a/src/frontend/js/Components/Project/ProjectEditor.js b/src/frontend/js/Components/Project/ProjectEditor.js @@ -39,7 +39,7 @@ Ext.define('PartKeepr.ProjectEditor', { Ext.Object.merge(config, { autoLoad: false, model: "PartKeepr.ProjectPart", - autoSync: false, // Do not change. If true, new (empty) records would be immediately commited to the database. + autoSync: false, // Do not change. If true, new (empty) records would be immediately committed to the database. remoteFilter: false, remoteSort: false }); diff --git a/src/frontend/js/Models/PrintingPageBasicLayout.js b/src/frontend/js/Models/PrintingPageBasicLayout.js @@ -0,0 +1,20 @@ +Ext.define("PartKeepr.Printing.PageBasicLayout", { + extend: "Ext.data.Model", + fields: [ + { id: 'id', name: 'id', type: 'int' }, + { name: 'name', type: 'string'}, + { name: 'comment', type: 'string'}, + { name: 'columnCount', type: 'int'}, + { name: 'rowCount', type: 'int'}, + { name: 'paperSize', type: 'string'}, + { name: 'paperPortrait', type: 'boolean'}, + { name: 'cellWidthInMM', type: 'float'}, + { name: 'cellHeightInMM', type: 'float'}, + { name: 'topLeftXInMM', type: 'float'}, + { name: 'topLeftYInMM', type: 'float'} + ], + proxy: PartKeepr.getRESTProxy("Printing.PageBasicLayout"), + getRecordName: function () { + return this.get("name"); + } +});+ \ No newline at end of file diff --git a/src/frontend/js/Models/PrintingPrintingJobConfiguration.js b/src/frontend/js/Models/PrintingPrintingJobConfiguration.js @@ -0,0 +1,19 @@ +Ext.define("PartKeepr.Printing.PrintingJobConfiguration", { + extend: "Ext.data.Model", + fields: [ + { id: 'id', name: 'id', type: 'int' }, + { name: 'name', type: 'string'}, + { name: 'comment', type: 'string'}, + { name: 'objectType', type: 'string'}, + { name: 'exportRenderer', type: 'string'}, + { name: 'pageLayout', type: 'int'}, + { name: 'rendererConfiguration', type: 'string'} + ], + belongsTo: [ + { model: 'PartKeepr.Printing.PageBasicLayout', primaryKey: 'id', foreignKey: 'pageLayout'} + ], + proxy: PartKeepr.getRESTProxy("Printing.PrintingJobConfiguration"), + getRecordName: function () { + return this.get("name"); + } +});+ \ No newline at end of file diff --git a/src/frontend/js/Models/PrintingRenderer.js b/src/frontend/js/Models/PrintingRenderer.js @@ -0,0 +1,10 @@ +Ext.define("PartKeepr.PrintingRenderer", { + extend: "Ext.data.Model", + fields: [ + { name: 'id', type: 'string'}, + { name: 'name', type: 'string'} + ], + getRecordName: function () { + return this.get("name"); + } +});+ \ No newline at end of file diff --git a/src/frontend/js/Models/PrintingResponse.js b/src/frontend/js/Models/PrintingResponse.js @@ -0,0 +1,6 @@ +Ext.define("PartKeepr.PrintingResponse", { + extend: "Ext.data.Model", + fields: [ + { name: 'fileid', type: 'string'} + ] +});+ \ No newline at end of file diff --git a/src/frontend/js/Models/PrintingType.js b/src/frontend/js/Models/PrintingType.js @@ -0,0 +1,10 @@ +Ext.define("PartKeepr.PrintingType", { + extend: "Ext.data.Model", + fields: [ + { name: 'id', type: 'string'}, + { name: 'name', type: 'string'} + ], + getRecordName: function () { + return this.get("name"); + } +});+ \ No newline at end of file