From 1835caad7b57ba3012176595a7449715d54b9998 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 9 May 2016 08:53:09 +0200 Subject: [PATCH 01/61] Better translation --- htdocs/adherents/card.php | 21 ++++++++++++--------- htdocs/adherents/card_subscriptions.php | 12 ++++++++++-- htdocs/langs/en_US/members.lang | 1 + 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/htdocs/adherents/card.php b/htdocs/adherents/card.php index f17ca46299f..b3bfce7d9a1 100644 --- a/htdocs/adherents/card.php +++ b/htdocs/adherents/card.php @@ -1381,17 +1381,12 @@ else print $form->formconfirm("card.php?rowid=".$rowid,$langs->trans("DeleteMember"),$langs->trans("ConfirmDeleteMember"),"confirm_delete",$formquestion,0,1); } - /* - * Confirm add in spip - */ + // Confirm add in spip if ($action == 'add_spip') { print $form->formconfirm("card.php?rowid=".$rowid, $langs->trans('AddIntoSpip'), $langs->trans('AddIntoSpipConfirmation'), 'confirm_add_spip'); } - - /* - * Confirm removed from spip - */ + // Confirm removed from spip if ($action == 'del_spip') { print $form->formconfirm("card.php?rowid=$rowid", $langs->trans('DeleteIntoSpip'), $langs->trans('DeleteIntoSpipConfirmation'), 'confirm_del_spip'); @@ -1556,8 +1551,16 @@ else } else { - print $langs->trans("SubscriptionNotReceived"); - if ($object->statut > 0) print " ".img_warning($langs->trans("Late")); // Affiche picto retard uniquement si non brouillon et non resilie + if (! $adht->cotisation) + { + print $langs->trans("SubscriptionNotRecorded"); + if ($object->statut > 0) print " ".img_warning($langs->trans("Late")); // Affiche picto retard uniquement si non brouillon et non resilie + } + else + { + print $langs->trans("SubscriptionNotReceived"); + if ($object->statut > 0) print " ".img_warning($langs->trans("Late")); // Affiche picto retard uniquement si non brouillon et non resilie + } } print ''; diff --git a/htdocs/adherents/card_subscriptions.php b/htdocs/adherents/card_subscriptions.php index 998dc53d525..784f6d4dea7 100644 --- a/htdocs/adherents/card_subscriptions.php +++ b/htdocs/adherents/card_subscriptions.php @@ -722,8 +722,16 @@ if ($rowid > 0) } else { - print $langs->trans("SubscriptionNotReceived"); - if ($object->statut > 0) print " ".img_warning($langs->trans("Late")); // Affiche picto retard uniquement si non brouillon et non resilie + if (! $adht->cotisation) + { + print $langs->trans("SubscriptionNotRecorded"); + if ($object->statut > 0) print " ".img_warning($langs->trans("Late")); // Affiche picto retard uniquement si non brouillon et non resilie + } + else + { + print $langs->trans("SubscriptionNotReceived"); + if ($object->statut > 0) print " ".img_warning($langs->trans("Late")); // Affiche picto retard uniquement si non brouillon et non resilie + } } print ''; diff --git a/htdocs/langs/en_US/members.lang b/htdocs/langs/en_US/members.lang index 1844a22c227..500f25e4ae8 100644 --- a/htdocs/langs/en_US/members.lang +++ b/htdocs/langs/en_US/members.lang @@ -124,6 +124,7 @@ Int=Int DateAndTime=Date and time PublicMemberCard=Member public card MemberNotOrNoMoreExpectedToSubscribe=Member not or no more expected to subscribe +SubscriptionNotRecorded=Subscription not recorded AddSubscription=Create subscription ShowSubscription=Show subscription MemberModifiedInDolibarr=Member modified in Dolibarr From 03732fd1aa0cfe75564111883677099b0bb02fad Mon Sep 17 00:00:00 2001 From: Maxime Kohlhaas Date: Mon, 9 May 2016 15:31:44 +0200 Subject: [PATCH 02/61] Fix contract line creation was adding all proposal lines desc --- htdocs/contrat/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/contrat/card.php b/htdocs/contrat/card.php index 3fe0530ec77..3c38cdf4421 100644 --- a/htdocs/contrat/card.php +++ b/htdocs/contrat/card.php @@ -320,7 +320,7 @@ if ($action == 'add' && $user->rights->contrat->creer) } if ($conf->global->PRODUIT_DESC_IN_FORM) - $desc .= ($lines[$i]->desc && $lines[$i]->desc!=$lines[$i]->libelle)?dol_htmlentitiesbr($lines[$i]->desc):''; + $desc = ($lines[$i]->desc && $lines[$i]->desc!=$lines[$i]->libelle)?dol_htmlentitiesbr($lines[$i]->desc):''; } else { $desc = dol_htmlentitiesbr($lines[$i]->desc); From 6278610d0aad44063c20b274bf07e0e4d936ea7f Mon Sep 17 00:00:00 2001 From: Maxime Kohlhaas Date: Mon, 9 May 2016 15:32:21 +0200 Subject: [PATCH 03/61] Fix contract line creation should take desc even if not displayed on screen --- htdocs/contrat/card.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/htdocs/contrat/card.php b/htdocs/contrat/card.php index 3c38cdf4421..c55defb5ce8 100644 --- a/htdocs/contrat/card.php +++ b/htdocs/contrat/card.php @@ -319,8 +319,7 @@ if ($action == 'add' && $user->rights->contrat->creer) $label = $lines[$i]->product_label; } - if ($conf->global->PRODUIT_DESC_IN_FORM) - $desc = ($lines[$i]->desc && $lines[$i]->desc!=$lines[$i]->libelle)?dol_htmlentitiesbr($lines[$i]->desc):''; + $desc = ($lines[$i]->desc && $lines[$i]->desc!=$lines[$i]->libelle)?dol_htmlentitiesbr($lines[$i]->desc):''; } else { $desc = dol_htmlentitiesbr($lines[$i]->desc); From a8f50283c06de3af9f4e0f0c8db5e9fb64f42e7b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 9 May 2016 19:37:14 +0200 Subject: [PATCH 04/61] Fix bad translation --- htdocs/langs/en_US/bills.lang | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/langs/en_US/bills.lang b/htdocs/langs/en_US/bills.lang index 392050c6cd5..bfbe32b803e 100644 --- a/htdocs/langs/en_US/bills.lang +++ b/htdocs/langs/en_US/bills.lang @@ -321,8 +321,8 @@ PaymentConditionPT_5050=50%% in advance, 50%% on delivery FixAmount=Fix amount VarAmount=Variable amount (%% tot.) # PaymentType -PaymentTypeVIR=Bank deposit -PaymentTypeShortVIR=Bank deposit +PaymentTypeVIR=Bank transfer +PaymentTypeShortVIR=Bank transfer PaymentTypePRE=Bank's order PaymentTypeShortPRE=Bank's order PaymentTypeLIQ=Cash From 3607f1a296b14f8535248cbad5bf15b520a9cc85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Mon, 9 May 2016 22:15:00 +0200 Subject: [PATCH 05/61] Correction for Auguria Menu Accounting Expert Entry cause blank windows --- htdocs/core/menus/init_menu_auguria.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/menus/init_menu_auguria.sql b/htdocs/core/menus/init_menu_auguria.sql index 86b8a58e68f..2e37f989b0a 100644 --- a/htdocs/core/menus/init_menu_auguria.sql +++ b/htdocs/core/menus/init_menu_auguria.sql @@ -206,7 +206,7 @@ insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, left insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '$conf->tax->enabled && empty($conf->global->TAX_DISABLE_VAT_MENUS) && $leftmenu=="tax_vat"', __HANDLER__, 'left', 2303__+MAX_llx_menu__, 'accountancy', '', 2300__+MAX_llx_menu__, '/compta/tva/clients.php?leftmenu=tax_vat', 'ReportByCustomers', 2, 'companies', '$user->rights->tax->charges->lire', '', 0, 2, __ENTITY__); insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '$conf->tax->enabled && empty($conf->global->TAX_DISABLE_VAT_MENUS) && $leftmenu=="tax_vat"', __HANDLER__, 'left', 2304__+MAX_llx_menu__, 'accountancy', '', 2300__+MAX_llx_menu__, '/compta/tva/quadri_detail.php?leftmenu=tax_vat', 'ReportByQuarter', 2, 'companies', '$user->rights->tax->charges->lire', '', 0, 3, __ENTITY__); -- Accounting Expert -insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '$conf->accounting->enabled', __HANDLER__, 'left', 2400__+MAX_llx_menu__, 'accountancy', 'accounting', 6__+MAX_llx_menu__, '/accountancy/customer/index.php?leftmenu=accounting', 'MenuAccountancy', 0, 'accountancy', '(! empty($conf->accounting->enabled) || $user->rights->accounting->ventilation->read || $user->rights->accounting->ventilation->dispatch || $user->rights->compta->resultat->lire', '', 0, 7, __ENTITY__); +insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '$conf->accounting->enabled', __HANDLER__, 'left', 2400__+MAX_llx_menu__, 'accountancy', 'accounting', 6__+MAX_llx_menu__, '/accountancy/customer/index.php?leftmenu=accounting', 'MenuAccountancy', 0, 'accountancy', '! empty($conf->accounting->enabled) || $user->rights->accounting->ventilation->read || $user->rights->accounting->ventilation->dispatch || $user->rights->compta->resultat->lire', '', 0, 7, __ENTITY__); -- Dispatch insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '$conf->accounting->enabled', __HANDLER__, 'left', 2401__+MAX_llx_menu__, 'accountancy', 'dispatch_customer', 2400__+MAX_llx_menu__, '/accountancy/customer/index.php?leftmenu=dispatch_customer', 'CustomersVentilation', 1, 'accountancy', '$user->rights->accounting->ventilation->read', '', 0, 1, __ENTITY__); insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '$conf->accounting->enabled && $leftmenu=="dispatch_customer"', __HANDLER__, 'left', 2402__+MAX_llx_menu__, 'accountancy', '', 2401__+MAX_llx_menu__, '/accountancy/customer/list.php', 'ToDispatch', 2, 'accountancy', '$user->rights->accounting->ventilation->dispatch', '', 0, 2, __ENTITY__); From 84e75eed88371dcc7fdf49dbc785766bc74249ae Mon Sep 17 00:00:00 2001 From: gauthier Date: Tue, 10 May 2016 09:31:25 +0200 Subject: [PATCH 06/61] FIX : can't fetch by siret or siren because of first "if" --- htdocs/societe/class/societe.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index 864820602c8..efb038a84f0 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -1006,7 +1006,7 @@ class Societe extends CommonObject global $langs; global $conf; - if (empty($rowid) && empty($ref) && empty($ref_ext) && empty($ref_int)) return -1; + if (empty($rowid) && empty($ref) && empty($ref_ext) && empty($ref_int) && empty($idprof1) && empty($idprof2) && empty($idprof3) && empty($idprof4)) return -1; $sql = 'SELECT s.rowid, s.nom as name, s.name_alias, s.entity, s.ref_ext, s.ref_int, s.address, s.datec as date_creation, s.prefix_comm'; $sql .= ', s.status'; From a57e0f5fd38f4c5a3a318ef9002f68f9172c6eec Mon Sep 17 00:00:00 2001 From: phf Date: Tue, 10 May 2016 14:27:24 +0200 Subject: [PATCH 07/61] Fix update supplier ref on supplier invoice --- htdocs/fourn/facture/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/fourn/facture/card.php b/htdocs/fourn/facture/card.php index f1de6a68a78..635c9195386 100644 --- a/htdocs/fourn/facture/card.php +++ b/htdocs/fourn/facture/card.php @@ -240,7 +240,7 @@ if (empty($reshook)) { $object->ref_supplier = GETPOST('ref_supplier', 'alpha'); - if ($object->update() < 0) { + if ($object->update($user) < 0) { setEventMessages($object->error, $object->errors, 'errors'); } } From 1bba8166f8c2dd69f45899f684f0921a7c86f9fd Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 13 May 2016 23:12:53 +0200 Subject: [PATCH 08/61] FIX #5207 --- htdocs/fourn/facture/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/fourn/facture/card.php b/htdocs/fourn/facture/card.php index f1de6a68a78..635c9195386 100644 --- a/htdocs/fourn/facture/card.php +++ b/htdocs/fourn/facture/card.php @@ -240,7 +240,7 @@ if (empty($reshook)) { $object->ref_supplier = GETPOST('ref_supplier', 'alpha'); - if ($object->update() < 0) { + if ($object->update($user) < 0) { setEventMessages($object->error, $object->errors, 'errors'); } } From 4a3083a4c12aecb64f906c8e1adbb8c57f26e22f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 May 2016 19:34:24 +0200 Subject: [PATCH 09/61] FIX Remove PHP Warning: Creating default object from empty value. --- htdocs/expensereport/card.php | 45 +++++++++-------------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/htdocs/expensereport/card.php b/htdocs/expensereport/card.php index 54a67cc6ffc..f3174935cd5 100644 --- a/htdocs/expensereport/card.php +++ b/htdocs/expensereport/card.php @@ -333,7 +333,7 @@ if ($action == "confirm_save_from_refuse" && GETPOST("confirm") == "yes" && $id if ($result > 0) { - if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) + if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) // TODO Translate this so we can remove condition { // Send mail @@ -390,6 +390,7 @@ if ($action == "confirm_save_from_refuse" && GETPOST("confirm") == "yes" && $id else { $mesg=$mailfile->error; + setEventMessages($mesg, null, 'errors'); } // END - Send mail } @@ -438,7 +439,7 @@ if ($action == "confirm_approve" && GETPOST("confirm") == "yes" && $id > 0 && $u if ($result > 0) { - if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) + if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) // TODO Translate this so we can remove condition { // Send mail @@ -542,7 +543,7 @@ if ($action == "confirm_refuse" && GETPOST('confirm')=="yes" && $id > 0 && $user if ($result > 0) { - if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) + if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) // TODO Translate this so we can remove condition { // Send mail @@ -583,7 +584,6 @@ if ($action == "confirm_refuse" && GETPOST('confirm')=="yes" && $id > 0 && $user else { setEventMessages($langs->trans("ErrorFailedToSendMail",$emailFrom,$emailTo), null, 'errors'); - $mesg="Impossible d'envoyer l'email."; } // END - Send mail } @@ -627,7 +627,7 @@ if ($action == "confirm_cancel" && GETPOST('confirm')=="yes" && GETPOST('detail_ if ($result > 0) { - if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) + if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) // TODO Translate this so we can remove condition { // Send mail @@ -667,6 +667,7 @@ if ($action == "confirm_cancel" && GETPOST('confirm')=="yes" && GETPOST('detail_ else { $mesg="Impossible d'envoyer l'email."; + setEventMessages($mesg, null, 'errors'); } // END - Send mail } @@ -756,7 +757,7 @@ if ($action == 'set_paid' && $id > 0 && $user->rights->expensereport->to_paid) if ($result > 0) { - if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) + if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) // TODO Translate this so we can remove condition { // Send mail @@ -806,9 +807,8 @@ if ($action == 'set_paid' && $id > 0 && $user->rights->expensereport->to_paid) endif; else: - - $mesg="Impossible d'envoyer l'email."; - + $mesg="Impossible d'envoyer l'email."; + setEventMessages($mesg, null, 'errors'); endif; // END - Send mail } @@ -921,7 +921,7 @@ if ($action == 'confirm_delete_line' && GETPOST("confirm") == "yes") $total_ht = $object_ligne->total_ht; $total_tva = $object_ligne->total_tva; - $result=$object->deleteline(GETPOST("rowid")); + $result=$object->deleteline(GETPOST("rowid"), $user); if ($result >= 0) { if ($result > 0) @@ -961,7 +961,6 @@ if ($action == "updateligne" ) $rowid = $_POST['rowid']; $type_fees_id = GETPOST('fk_c_type_fees'); - $object_ligne->vatrate = price2num(GETPOST('vatrate')); $projet_id = $fk_projet; $comments = GETPOST('comments'); $qty = GETPOST('qty'); @@ -983,6 +982,7 @@ if ($action == "updateligne" ) if (! $error) { + // TODO Use update method of ExpenseReportLine $result = $object->updateline($rowid, $type_fees_id, $projet_id, $vatrate, $comments, $qty, $value_unit, $date, $id); if ($result >= 0) { @@ -1078,29 +1078,6 @@ $formfile = new FormFile($db); $formproject = new FormProjets($db); $projecttmp = new Project($db); -if (! empty($conf->global->DEPLACEMENT_TO_CLEAN)) -{ - if(!empty($_GET['mesg'])) - { - $text_mesg = explode(",",$_GET['mesg']); - - foreach($text_mesg as $text) - { - $mesg.= "- ".$langs->trans($text)."
"; - } - - print "
"; - print $langs->trans("LINE_NOT_ADDED")."
"; - print $mesg; - print "
"; - } - else - { - if ($mesg) print "
".$mesg."
"; - } -} - - // Create if ($action == 'create') { From 929787006e43801bdf755294519f07c0918f3f09 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 May 2016 22:40:29 +0200 Subject: [PATCH 10/61] Clean data --- htdocs/install/mysql/migration/repair.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/htdocs/install/mysql/migration/repair.sql b/htdocs/install/mysql/migration/repair.sql index 53034338932..2b87303f113 100755 --- a/htdocs/install/mysql/migration/repair.sql +++ b/htdocs/install/mysql/migration/repair.sql @@ -55,6 +55,7 @@ delete from llx_product_extrafields where fk_object not in (select rowid from ll --delete from llx_societe_commerciaux where fk_soc not in (select rowid from llx_societe); update llx_product_batch set batch = '' where batch = 'Non défini'; +update llx_product_batch set batch = '' where batch = 'Non défini'; -- Fix: delete category child with no category parent. drop table tmp_categorie; From 20a2e9394d3b96dea20730d9d9441efe4855ecb4 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 May 2016 22:44:11 +0200 Subject: [PATCH 11/61] Fix typo comment --- htdocs/product/stock/class/mouvementstock.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 944fb3f8dfb..a3cfaca9bd6 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -334,7 +334,7 @@ class MouvementStock extends CommonObject } } - // If stock is now 0, we can remove entry into llx_stock_product, but only if there is no child lines into llx_product_batch (detail of batch, because we can imagine + // If stock is now 0, we can remove entry into llx_product_stock, but only if there is no child lines into llx_product_batch (detail of batch, because we can imagine // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot. $sql="DELETE FROM ".MAIN_DB_PREFIX."product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM ".MAIN_DB_PREFIX."product_batch as pb)"; $resql=$this->db->query($sql); From 94c532dd8a4c99eefa31140929b9dd41b92170e4 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 May 2016 23:58:06 +0200 Subject: [PATCH 12/61] Clean splitted lines into llx_product_batch --- htdocs/install/mysql/migration/repair.sql | 28 ++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/htdocs/install/mysql/migration/repair.sql b/htdocs/install/mysql/migration/repair.sql index 2b87303f113..43e0ca21ee2 100755 --- a/htdocs/install/mysql/migration/repair.sql +++ b/htdocs/install/mysql/migration/repair.sql @@ -54,9 +54,34 @@ delete from llx_adherent_extrafields where fk_object not in (select rowid from l delete from llx_product_extrafields where fk_object not in (select rowid from llx_product); --delete from llx_societe_commerciaux where fk_soc not in (select rowid from llx_societe); + +-- Clean stocks + +-- Reference for qty is llx_product_stock (detail in llx_product_batch may be not complete) +-- qty in llx_product may be not up to date update llx_product_batch set batch = '' where batch = 'Non défini'; update llx_product_batch set batch = '' where batch = 'Non défini'; +DELETE FROM llx_product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM llx_product_batch as pb); + +-- Merge splitted lines into one in table llx_product_batch +DROP TABLE tmp_llx_product_batch; +DROP TABLE tmp_llx_product_batch2; +CREATE TABLE tmp_llx_product_batch AS select fk_product_stock, eatby, sellby, batch, SUM(qty) as qty, COUNT(rowid) as nb FROM llx_product_batch GROUP BY fk_product_stock, eatby, sellby, batch HAVING COUNT(rowid) > 1; +CREATE TABLE tmp_llx_product_batch2 AS select pb.rowid, pb.fk_product_stock, pb.eatby, pb.sellby, pb.batch, pb.qty from llx_product_batch as pb, tmp_llx_product_batch as tpb where pb.fk_product_stock = tpb.fk_product_stock and COALESCE(pb.eatby, '') = COALESCE(tpb.eatby,'') and COALESCE(pb.sellby, '') = COALESCE(tpb.sellby, '') and pb.batch = tpb.batch +--select * from tmp_llx_product_batch; +--select * from tmp_llx_product_batch2; +DELETE FROM llx_product_batch WHERE rowid IN (select rowid FROM tmp_llx_product_batch2); +INSERT INTO llx_product_batch(fk_product_stock, eatby, sellby, batch, qty) SELECT fk_product_stock, eatby, sellby, batch, qty FROM tmp_llx_product_batch; + +DELETE FROM llx_product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM llx_product_batch as pb); +DELETE FROM llx_product_batch WHERE qty = 0; + + +-- Stock calculation on product +UPDATE llx_product p SET p.stock= (SELECT SUM(ps.reel) FROM llx_product_stock ps WHERE ps.fk_product = p.rowid); + + -- Fix: delete category child with no category parent. drop table tmp_categorie; create table tmp_categorie as select * from llx_categorie; @@ -110,9 +135,6 @@ insert into llx_c_actioncomm (id, code, type, libelle, module, position) values insert into llx_c_actioncomm (id, code, type, libelle, module, position) values ( 50, 'AC_OTH', 'system', 'Other' ,NULL, 5); --- Stock calculation on product -UPDATE llx_product p SET p.stock= (SELECT SUM(ps.reel) FROM llx_product_stock ps WHERE ps.fk_product = p.rowid); - -- VMYSQL4.1 DELETE T1 FROM llx_boxes_def as T1, llx_boxes_def as T2 where T1.entity = T2.entity AND T1.file = T2.file AND T1.note = T2.note and T1.rowid > T2.rowid; -- VPGSQL8.2 DELETE FROM llx_boxes_def as T1 WHERE rowid NOT IN (SELECT min(rowid) FROM llx_boxes_def GROUP BY file, entity, note); From 95ea20eb442d999b416284f2bfd7b4accf39d860 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 01:10:11 +0200 Subject: [PATCH 13/61] Clean code for stock movement when lot are used. We should not get duplicate records into llx_product_batch --- htdocs/expedition/class/expedition.class.php | 19 ++--- .../install/mysql/migration/3.9.0-4.0.0.sql | 6 ++ .../mysql/tables/llx_product_batch.key.sql | 3 + htdocs/product/class/productbatch.class.php | 4 +- .../stock/class/mouvementstock.class.php | 75 ++++++++++--------- 5 files changed, 58 insertions(+), 49 deletions(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 35ef726be20..6d3f7b05966 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -705,7 +705,9 @@ class Expedition extends CommonObject // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record $result=$mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr",$numref)); if ($result < 0) { - $error++; break; + $error++; + $this->errors[]=$mouvS->error; + break; } } else @@ -713,17 +715,12 @@ class Expedition extends CommonObject // line with batch detail // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record - $result=$mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr",$numref), '', $obj->eatby, $obj->sellby, $obj->batch); + // ->fk_origin_stock = id into table llx_product_batch (may be rename into llx_product_stock_batch in another version) + $result=$mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr",$numref), '', $obj->eatby, $obj->sellby, $obj->batch, $obj->fk_origin_stock); if ($result < 0) { - $error++; break; - } - - // We update content of table llx_product_batch (will be rename into llx_product_stock_batch inantoher version) - // We can set livraison_batch to deprecated and adapt livraison to handle batch too (mouvS->_create also calls mouvS->_create_batch) - if (! empty($conf->productbatch->enabled)) - { - $result=$mouvS->livraison_batch($obj->fk_origin_stock, $qty); // ->fk_origin_stock = id into table llx_product_batch (will be rename into llx_product_stock_batch in another version) - if ($result < 0) { $error++; $this->errors[]=$mouvS->error; break; } + $error++; + $this->errors[]=$mouvS->error; + break; } } } diff --git a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql index 14849e6fa9c..c72ec02ca51 100644 --- a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql +++ b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql @@ -454,3 +454,9 @@ CREATE TABLE llx_advtargetemailing )ENGINE=InnoDB; ALTER TABLE llx_advtargetemailing ADD UNIQUE INDEX uk_advtargetemailing_name (name); + + + +-- At end +ALTER TABLE llx_product_batch ADD UNIQUE INDEX uk_product_batch (fk_product_stock, batch); + diff --git a/htdocs/install/mysql/tables/llx_product_batch.key.sql b/htdocs/install/mysql/tables/llx_product_batch.key.sql index d022beac743..100e092399d 100644 --- a/htdocs/install/mysql/tables/llx_product_batch.key.sql +++ b/htdocs/install/mysql/tables/llx_product_batch.key.sql @@ -19,3 +19,6 @@ ALTER TABLE llx_product_batch ADD INDEX idx_fk_product_stock(fk_product_stock); ALTER TABLE llx_product_batch ADD INDEX idx_batch(batch); ALTER TABLE llx_product_batch ADD CONSTRAINT fk_product_batch_fk_product_stock FOREIGN KEY (fk_product_stock) REFERENCES llx_product_stock (rowid); + +ALTER TABLE llx_product_batch ADD UNIQUE INDEX uk_product_batch (fk_product_stock, batch); + diff --git a/htdocs/product/class/productbatch.class.php b/htdocs/product/class/productbatch.class.php index 749da6d86e2..d4440dac0f3 100644 --- a/htdocs/product/class/productbatch.class.php +++ b/htdocs/product/class/productbatch.class.php @@ -429,8 +429,8 @@ class Productbatch extends CommonObject $sql.= " FROM ".MAIN_DB_PREFIX.self::$_table_element." as t"; $sql.= " WHERE fk_product_stock=".$fk_product_stock; - if (! empty($eatby)) array_push($where," eatby = '".$this->db->idate($eatby)."'"); - if (! empty($sellby)) array_push($where," sellby = '".$this->db->idate($sellby)."'"); + if (! empty($eatby)) array_push($where," eatby = '".$this->db->idate($eatby)."'"); // deprecated + if (! empty($sellby)) array_push($where," sellby = '".$this->db->idate($sellby)."'"); // deprecated if (! empty($batch_number)) $sql.= " AND batch = '".$this->db->escape($batch_number)."'"; if (! empty($where)) $sql.= " AND (".implode(" OR ",$where).")"; diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index a3cfaca9bd6..f1542d593b6 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -65,9 +65,10 @@ class MouvementStock extends CommonObject * @param date $sellby sell-by date * @param string $batch batch number * @param boolean $skip_batch If set to true, stock movement is done without impacting batch record + * @param int $id_product_batch Id product_batch (when skip_batch is flase and we already know which record of product_batch to use) * @return int <0 if KO, 0 if fk_product is null, >0 if OK */ - function _create($user, $fk_product, $entrepot_id, $qty, $type, $price=0, $label='', $inventorycode='', $datem='',$eatby='',$sellby='',$batch='',$skip_batch=false) + function _create($user, $fk_product, $entrepot_id, $qty, $type, $price=0, $label='', $inventorycode='', $datem='',$eatby='',$sellby='',$batch='',$skip_batch=false, $id_product_batch=0) { global $conf, $langs; @@ -125,9 +126,14 @@ class MouvementStock extends CommonObject return -2; } - // If a serial number is provided, we check that sellby and eatby match already existing serial - $sql = "SELECT pb.rowid, pb.batch, pb.eatby, pb.sellby FROM ".MAIN_DB_PREFIX."product_batch as pb, ".MAIN_DB_PREFIX."product_stock as ps"; - $sql.= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".$fk_product." AND pb.batch = '".$this->db->escape($batch)."'"; + // FIXME Code not complete to implement this + // Check table llx_product_lot from batchnumber for same product + // If found and eatby/sellby defined into table and provided and differs, return error + // If found and eatby/sellby not defined into table and provided, we update table + // If found and eatby/sellby defined into table and not provided, we take value from table + // If not found, we add record + $sql = "SELECT pb.rowid, pb.batch, pb.eatby, pb.sellby FROM ".MAIN_DB_PREFIX."product_lot as pb"; + $sql.= " WHERE pb.fk_product = ".$fk_product." AND pb.batch = '".$this->db->escape($batch)."'"; dol_syslog(get_class($this)."::_create scan serial for this product to check if eatby and sellby match", LOG_DEBUG); $resql = $this->db->query($sql); if ($resql) @@ -310,8 +316,15 @@ class MouvementStock extends CommonObject // Update detail stock for batch product if (! $error && ! empty($conf->productbatch->enabled) && $product->hasbatch() && ! $skip_batch) { - $param_batch=array('fk_product_stock' =>$fk_product_stock, 'eatby'=>$eatby, 'sellby'=>$sellby, 'batchnumber'=>$batch); - $result=$this->_create_batch($param_batch, $qty); + if ($id_product_batch > 0) + { + $result=$this->createBatch($id_product_batch, $qty); + } + else + { + $param_batch=array('fk_product_stock' =>$fk_product_stock, 'batchnumber'=>$batch); + $result=$this->createBatch($param_batch, $qty); + } if ($result<0) $error++; } @@ -436,24 +449,14 @@ class MouvementStock extends CommonObject * @param date $eatby eat-by date * @param date $sellby sell-by date * @param string $batch batch number + * @param int $id_product_batch Id product_batch * @return int <0 if KO, >0 if OK */ - function livraison($user, $fk_product, $entrepot_id, $qty, $price=0, $label='', $datem='', $eatby='', $sellby='', $batch='') + function livraison($user, $fk_product, $entrepot_id, $qty, $price=0, $label='', $datem='', $eatby='', $sellby='', $batch='', $id_product_batch=0) { - return $this->_create($user, $fk_product, $entrepot_id, (0 - $qty), 2, $price, $label, '', $datem, $eatby, $sellby, $batch, true); - } + $skip_batch = empty($conf->productbatch->enabled); - /** - * Decrease stock for batch record - * - * @param int $id_stock_dluo Id product_dluo - * @param int $qty Quantity - * @return int <0 if KO, >0 if OK - */ - function livraison_batch($id_stock_dluo, $qty) - { - $ret=$this->_create_batch($id_stock_dluo, (0 - $qty)); - return $ret; + return $this->_create($user, $fk_product, $entrepot_id, (0 - $qty), 2, $price, $label, '', $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch); } /** @@ -527,16 +530,18 @@ class MouvementStock extends CommonObject } /** - * Create or update batch record (update table llx_product_batch) + * Create or update batch record (update table llx_product_batch). No check is done here, done by parent. * - * @param array|int $dluo Could be either - * - int if row id of product_batch table - * - or complete array('fk_product_stock'=>, 'eatby'=>, 'sellby'=> , 'batchnumber'=>) - * @param int $qty Quantity of product with batch number. May be a negative amount. - * @return int <0 if KO, else return productbatch id + * @param array|int $dluo Could be either + * - int if row id of product_batch table + * - or complete array('fk_product_stock'=>, 'batchnumber'=>) + * @param int $qty Quantity of product with batch number. May be a negative amount. + * @return int <0 if KO, else return productbatch id */ - function _create_batch($dluo, $qty) + private function createBatch($dluo, $qty) { + global $user; + $pdluo=new Productbatch($this->db); $result=0; @@ -548,7 +553,7 @@ class MouvementStock extends CommonObject if (empty($pdluo->id)) { // We didn't find the line. May be it was deleted before by a previous move in same transaction. - $this->error = 'Error. You ask a move on a record for a serial that does not exists anymore. May be you take the same serial on samewarehouse several times in same shipment or it was used by another shipment. Remove this shipment and prepare another one.'; + $this->error = 'Error. You ask a move on a record for a serial that does not exists anymore. May be you take the same serial on same warehouse several times in same shipment or it was used by another shipment. Remove this shipment and prepare another one.'; $this->errors[] = $this->error; $result = -2; } @@ -558,21 +563,19 @@ class MouvementStock extends CommonObject if (isset($dluo['fk_product_stock'])) { $vfk_product_stock=$dluo['fk_product_stock']; - $veatby = $dluo['eatby']; - $vsellby = $dluo['sellby']; $vbatchnumber = $dluo['batchnumber']; - $result = $pdluo->find($vfk_product_stock,$veatby,$vsellby,$vbatchnumber); + $result = $pdluo->find($vfk_product_stock,'','',$vbatchnumber); // Search on batch number only (eatby and sellby are deprecated here) } else { - dol_syslog(get_class($this)."::_create_batch array param dluo must contain at least key fk_product_stock".$error, LOG_ERR); + dol_syslog(get_class($this)."::createBatch array param dluo must contain at least key fk_product_stock".$error, LOG_ERR); $result = -1; } } else { - dol_syslog(get_class($this)."::_create_batch error invalid param dluo".$error, LOG_ERR); + dol_syslog(get_class($this)."::createBatch error invalid param dluo".$error, LOG_ERR); $result = -1; } @@ -585,9 +588,9 @@ class MouvementStock extends CommonObject $pdluo->qty += $qty; if ($pdluo->qty == 0) { - $result=$pdluo->delete(0,1); + $result=$pdluo->delete($user,1); } else { - $result=$pdluo->update(0,1); + $result=$pdluo->update($user,1); } } else // product_batch record not found @@ -598,7 +601,7 @@ class MouvementStock extends CommonObject $pdluo->sellby = $vsellby; $pdluo->batch = $vbatchnumber; - $result=$pdluo->create(0,1); + $result=$pdluo->create($user,1); if ($result < 0) { $this->error=$pdluo->error; From 901c7c9517720d6291252100146302f5c2af11eb Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 02:16:28 +0200 Subject: [PATCH 14/61] Link "Backtolist" was missing on products --- htdocs/comm/card.php | 5 ----- htdocs/core/class/html.form.class.php | 2 +- htdocs/core/lib/company.lib.php | 4 ++-- htdocs/core/tpl/objectline_edit.tpl.php | 2 +- htdocs/margin/tabs/productMargins.php | 4 +++- htdocs/margin/tabs/thirdpartyMargins.php | 5 ----- htdocs/product/card.php | 4 +++- htdocs/product/composition/card.php | 4 +++- htdocs/product/document.php | 4 +++- htdocs/product/fournisseurs.php | 6 ++++-- htdocs/product/info.php | 2 +- htdocs/product/price.php | 4 +++- htdocs/product/stats/card.php | 4 +++- htdocs/product/stats/commande.php | 4 +++- htdocs/product/stats/commande_fournisseur.php | 4 +++- htdocs/product/stats/contrat.php | 4 +++- htdocs/product/stats/facture.php | 4 +++- htdocs/product/stats/facture_fournisseur.php | 4 +++- htdocs/product/stats/propal.php | 4 +++- htdocs/product/stock/product.php | 7 +++++-- htdocs/product/stock/tpl/stocktransfer.tpl.php | 2 +- htdocs/product/traduction.php | 8 ++++++-- htdocs/societe/agenda.php | 11 +++-------- htdocs/societe/consumption.php | 10 +++------- htdocs/societe/document.php | 13 ++++--------- htdocs/societe/note.php | 11 +++-------- htdocs/societe/project.php | 5 ----- htdocs/societe/soc.php | 12 ++++++------ 28 files changed, 76 insertions(+), 77 deletions(-) diff --git a/htdocs/comm/card.php b/htdocs/comm/card.php index 38447c17c5e..ce611528a62 100644 --- a/htdocs/comm/card.php +++ b/htdocs/comm/card.php @@ -233,11 +233,6 @@ if ($id > 0) print '
'; print ''; - // Alias name (commercial, trademark or alias name) - print '"; - // Prospect/Customer print ''; diff --git a/htdocs/margin/tabs/productMargins.php b/htdocs/margin/tabs/productMargins.php index b409d0c752f..e4a349382b0 100644 --- a/htdocs/margin/tabs/productMargins.php +++ b/htdocs/margin/tabs/productMargins.php @@ -82,7 +82,9 @@ if ($id > 0 || ! empty($ref)) $picto=($object->type== Product::TYPE_SERVICE?'service':'product'); dol_fiche_head($head, 'margin', $titre, 0, $picto); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/margin/tabs/thirdpartyMargins.php b/htdocs/margin/tabs/thirdpartyMargins.php index 32c39b63b4f..cd12539c2f7 100644 --- a/htdocs/margin/tabs/thirdpartyMargins.php +++ b/htdocs/margin/tabs/thirdpartyMargins.php @@ -95,11 +95,6 @@ if ($socid > 0) print '
'; print '
'.$langs->trans('AliasNames').''; - print $object->name_alias; - print "
'.$langs->trans('ProspectCustomer').''; print $object->getLibCustProspStatut(); diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 74cd27bc460..dcf593e6ccd 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -4297,7 +4297,7 @@ class Form } $return.= '>'.vatrate($rate['libtva']); //$return.=($rate['code']?' '.$rate['code']:''); - $return.= (empty($defaultcode) && $rate['nprtva']) ? ' *': ''; // We show the * (old behaviour only if new vat code is not used) + $return.= (empty($rate['code']) && $rate['nprtva']) ? ' *': ''; // We show the * (old behaviour only if new vat code is not used) $return.= ''; } diff --git a/htdocs/core/lib/company.lib.php b/htdocs/core/lib/company.lib.php index 8631c576576..b2b6d8274a5 100644 --- a/htdocs/core/lib/company.lib.php +++ b/htdocs/core/lib/company.lib.php @@ -186,9 +186,9 @@ function societe_prepare_head(Societe $object) if (! empty($conf->agenda->enabled) && (!empty($user->rights->agenda->myactions->read) || !empty($user->rights->agenda->allactions->read) )) { $head[$h][1] = $langs->trans("Events"); - $head[$h][1].= ' / '; + $head[$h][1].= '/'; } - $head[$h][1].= $langs->trans("Info"); + $head[$h][1].= $langs->trans("Agenda"); $head[$h][2] = 'agenda'; $h++; diff --git a/htdocs/core/tpl/objectline_edit.tpl.php b/htdocs/core/tpl/objectline_edit.tpl.php index 1f0756a666c..4c5b654a76f 100644 --- a/htdocs/core/tpl/objectline_edit.tpl.php +++ b/htdocs/core/tpl/objectline_edit.tpl.php @@ -117,7 +117,7 @@ $coldisplay=-1; // We remove first td } $coldisplay++; - print 'situation_counter > 1) print ' readonly'; print '>
'; - // Alias names (commercial, trademark or alias names) - print '"; - if ($object->client) { print ''; } else { print "\n".''; - print ''; + print ''; print ''; print '
'.$langs->trans('AliasNames').''; - print $object->name_alias; - print "
'; diff --git a/htdocs/product/card.php b/htdocs/product/card.php index d9863792546..5fdfa1f2337 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -1409,7 +1409,9 @@ else $picto=($object->type== Product::TYPE_SERVICE?'service':'product'); dol_fiche_head($head, 'card', $titre, 0, $picto); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/product/composition/card.php b/htdocs/product/composition/card.php index adfce7d6229..d0d26292251 100644 --- a/htdocs/product/composition/card.php +++ b/htdocs/product/composition/card.php @@ -190,7 +190,9 @@ if ($id > 0 || ! empty($ref)) */ if ($user->rights->produit->lire || $user->rights->service->lire) { - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print ''; diff --git a/htdocs/product/document.php b/htdocs/product/document.php index ca207f8429f..3030058fe9e 100644 --- a/htdocs/product/document.php +++ b/htdocs/product/document.php @@ -195,7 +195,9 @@ if ($object->id) } - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/product/fournisseurs.php b/htdocs/product/fournisseurs.php index 6a76055d3fc..ebc3b4eb6e5 100644 --- a/htdocs/product/fournisseurs.php +++ b/htdocs/product/fournisseurs.php @@ -297,8 +297,10 @@ if ($id > 0 || $ref) $titre=$langs->trans("CardProduct".$object->type); $picto=($object->type== Product::TYPE_SERVICE?'service':'product'); dol_fiche_head($head, 'suppliers', $titre, 0, $picto); - - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/product/info.php b/htdocs/product/info.php index 3ca96a2847d..51510b518a6 100644 --- a/htdocs/product/info.php +++ b/htdocs/product/info.php @@ -85,7 +85,7 @@ if ($id > 0 || $ref) $linkback = ''.$langs->trans("BackToList").''; - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); $object->info($object->id); diff --git a/htdocs/product/price.php b/htdocs/product/price.php index 5b024391b20..67bb3165fb6 100644 --- a/htdocs/product/price.php +++ b/htdocs/product/price.php @@ -625,7 +625,9 @@ $titre = $langs->trans("CardProduct" . $object->type); $picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product'); dol_fiche_head($head, 'price', $titre, 0, $picto); -dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); +$linkback = ''.$langs->trans("BackToList").''; + +dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/product/stats/card.php b/htdocs/product/stats/card.php index 49b700b46ca..7003690e3c8 100644 --- a/htdocs/product/stats/card.php +++ b/htdocs/product/stats/card.php @@ -106,7 +106,9 @@ if (! empty($id) || ! empty($ref) || GETPOST('id') == 'all') dol_fiche_head($head, 'stats', $titre, 0, $picto); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); dol_fiche_end(); } diff --git a/htdocs/product/stats/commande.php b/htdocs/product/stats/commande.php index 69334976141..f9b9d14d794 100644 --- a/htdocs/product/stats/commande.php +++ b/htdocs/product/stats/commande.php @@ -99,7 +99,9 @@ if ($id > 0 || ! empty($ref)) $reshook=$hookmanager->executeHooks('formObjectOptions',$parameters,$product,$action); // Note that $action and $object may have been modified by hook if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/product/stats/commande_fournisseur.php b/htdocs/product/stats/commande_fournisseur.php index 83f0912b3ee..4bdfdcc5c81 100644 --- a/htdocs/product/stats/commande_fournisseur.php +++ b/htdocs/product/stats/commande_fournisseur.php @@ -106,7 +106,9 @@ if ($id > 0 || ! empty($ref)) { if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/product/stats/contrat.php b/htdocs/product/stats/contrat.php index c2e6b25f7ab..ff91fbf299e 100644 --- a/htdocs/product/stats/contrat.php +++ b/htdocs/product/stats/contrat.php @@ -89,7 +89,9 @@ if ($id > 0 || ! empty($ref)) $reshook=$hookmanager->executeHooks('formObjectOptions',$parameters,$product,$action); // Note that $action and $object may have been modified by hook if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/product/stats/facture.php b/htdocs/product/stats/facture.php index 57c426dc3a1..6f3f8546454 100644 --- a/htdocs/product/stats/facture.php +++ b/htdocs/product/stats/facture.php @@ -102,7 +102,9 @@ if ($id > 0 || ! empty($ref)) $reshook=$hookmanager->executeHooks('formObjectOptions',$parameters,$product,$action); // Note that $action and $object may have been modified by hook if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/product/stats/facture_fournisseur.php b/htdocs/product/stats/facture_fournisseur.php index 27e5495a771..77bd4b73887 100644 --- a/htdocs/product/stats/facture_fournisseur.php +++ b/htdocs/product/stats/facture_fournisseur.php @@ -101,7 +101,9 @@ if ($id > 0 || ! empty($ref)) $reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $product, $action); // Note that $action and $object may have been modified by hook if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/product/stats/propal.php b/htdocs/product/stats/propal.php index 2fa69740e8b..d23853c174f 100644 --- a/htdocs/product/stats/propal.php +++ b/htdocs/product/stats/propal.php @@ -99,7 +99,9 @@ if ($id > 0 || ! empty($ref)) $reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $product, $action); // Note that $action and $object may have been modified by hook if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; diff --git a/htdocs/product/stock/product.php b/htdocs/product/stock/product.php index 05b0aecf5b6..499efd29847 100644 --- a/htdocs/product/stock/product.php +++ b/htdocs/product/stock/product.php @@ -405,7 +405,9 @@ if ($id > 0 || $ref) dol_htmloutput_events(); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); print '
'; @@ -746,12 +748,13 @@ if ($resql) print '
'; print '
'; print '
'; + print '
'; print img_picto($langs->trans("Tranfer"),'uparrow','class="hideonsmartphone"').' '; - print 'id.'">'.$langs->trans("StockTransfer").''; + print 'id.'">'.$langs->trans("StockTransfer").''; // Disabled, because edition of stock content must use the "Correct stock menu". // Do not use this, or data will be wrong (bad tracking of movement label, inventory code, ... //print 'id.'#'.$pdluo->id.'">'; diff --git a/htdocs/product/stock/tpl/stocktransfer.tpl.php b/htdocs/product/stock/tpl/stocktransfer.tpl.php index 1c0448192f1..a50fafca278 100644 --- a/htdocs/product/stock/tpl/stocktransfer.tpl.php +++ b/htdocs/product/stock/tpl/stocktransfer.tpl.php @@ -108,7 +108,7 @@ print ''; print ''; print ''.$langs->trans("InventoryCode").''.$langs->trans("InventoryCode").'
'; diff --git a/htdocs/product/traduction.php b/htdocs/product/traduction.php index 96c94e9efff..d83dfd238c7 100644 --- a/htdocs/product/traduction.php +++ b/htdocs/product/traduction.php @@ -185,7 +185,9 @@ if ($action == 'edit') dol_fiche_head($head, 'translation', $titre, 0, $picto); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); if (! empty($object->multilangs)) { @@ -227,7 +229,9 @@ else { dol_fiche_head($head, 'translation', $titre, 0, $picto); - dol_banner_tab($object, 'ref', '', ($user->societe_id?0:1), 'ref'); + $linkback = ''.$langs->trans("BackToList").''; + + dol_banner_tab($object, 'ref', $linkback, ($user->societe_id?0:1), 'ref'); $cnt_trans = 0; if (! empty($object->multilangs)) diff --git a/htdocs/societe/agenda.php b/htdocs/societe/agenda.php index 7bbba527ed8..4547c7d75da 100644 --- a/htdocs/societe/agenda.php +++ b/htdocs/societe/agenda.php @@ -88,19 +88,14 @@ if ($socid) print '
'; print ''; - // Alias names (commercial, trademark or alias names) - print '"; - if (! empty($conf->global->SOCIETE_USEPREFIX)) // Old not used prefix field { - print ''; + print ''; } if ($object->client) { - print '
'.$langs->trans('AliasNames').''; - print $object->name_alias; - print "
'.$langs->trans('Prefix').''.$object->prefix_comm.'
'.$langs->trans('Prefix').''.$object->prefix_comm.'
'; + print '
'; print $langs->trans('CustomerCode').''; print $object->code_client; if ($object->check_codeclient() <> 0) print ' ('.$langs->trans("WrongCustomerCode").')'; @@ -109,7 +104,7 @@ if ($socid) if ($object->fournisseur) { - print '
'; + print '
'; print $langs->trans('SupplierCode').''; print $object->code_fournisseur; if ($object->check_codefournisseur() <> 0) print ' ('.$langs->trans("WrongSupplierCode").')'; diff --git a/htdocs/societe/consumption.php b/htdocs/societe/consumption.php index 661483cf2a2..d5b68057ff1 100644 --- a/htdocs/societe/consumption.php +++ b/htdocs/societe/consumption.php @@ -121,20 +121,16 @@ print '
'; print '
'; print ''; -// Alias names (commercial, trademark or alias names) -print ''; -print ''; - if (! empty($conf->global->SOCIETE_USEPREFIX)) // Old not used prefix field { - print ''; + print ''; } //if ($conf->agenda->enabled && $user->rights->agenda->myactions->read) $elementTypeArray['action']=$langs->transnoentitiesnoconv('Events'); if ($object->client) { - print '
'.$object->name_alias.'
'.$langs->trans('Prefix').''.$object->prefix_comm.'
'.$langs->trans('Prefix').''.$object->prefix_comm.'
'; + print '
'; print $langs->trans('CustomerCode').''; print $object->code_client; if ($object->check_codeclient() <> 0) print ' ('.$langs->trans("WrongCustomerCode").')'; @@ -155,7 +151,7 @@ if ($object->client) if ($object->fournisseur) { - print '
'; + print '
'; print $langs->trans('SupplierCode').''; print $object->code_fournisseur; if ($object->check_codefournisseur() <> 0) print ' ('.$langs->trans("WrongSupplierCode").')'; diff --git a/htdocs/societe/document.php b/htdocs/societe/document.php index fff130629be..4ad8abc9a43 100644 --- a/htdocs/societe/document.php +++ b/htdocs/societe/document.php @@ -116,20 +116,15 @@ if ($object->id) print '
'; print ''; - // Alias names (commercial, trademark or alias names) - print '"; - // Prefix if (! empty($conf->global->SOCIETE_USEPREFIX)) // Old not used prefix field { - print ''; + print ''; } if ($object->client) { - print ''; + print ''; // Total size print ''; diff --git a/htdocs/societe/note.php b/htdocs/societe/note.php index acd7f32d375..156a4620634 100644 --- a/htdocs/societe/note.php +++ b/htdocs/societe/note.php @@ -82,19 +82,14 @@ if ($id > 0) print '
'; print '
'.$langs->trans('AliasNames').''; - print $object->name_alias; - print "
'.$langs->trans('Prefix').''.$object->prefix_comm.'
'.$langs->trans('Prefix').''.$object->prefix_comm.'
'; + print '
'; print $langs->trans('CustomerCode').''; print $object->code_client; if ($object->check_codeclient() <> 0) print ' ('.$langs->trans("WrongCustomerCode").')'; @@ -138,7 +133,7 @@ if ($object->id) if ($object->fournisseur) { - print '
'; + print '
'; print $langs->trans('SupplierCode').''; print $object->code_fournisseur; if ($object->check_codefournisseur() <> 0) print ' ('.$langs->trans("WrongSupplierCode").')'; @@ -146,7 +141,7 @@ if ($object->id) } // Number of files - print '
'.$langs->trans("NbOfAttachedFiles").''.count($filearray).'
'.$langs->trans("NbOfAttachedFiles").''.count($filearray).'
'.$langs->trans("TotalSizeOfAttachedFiles").''.$totalsize.' '.$langs->trans("bytes").'
'; - // Alias names (commercial, trademark or alias names) - print '"; - if (! empty($conf->global->SOCIETE_USEPREFIX)) // Old not used prefix field { - print ''; + print ''; } if ($object->client) { - print '
'.$langs->trans('AliasNames').''; - print $object->name_alias; - print "
'.$langs->trans('Prefix').''.$object->prefix_comm.'
'.$langs->trans('Prefix').''.$object->prefix_comm.'
'; + print '
'; print $langs->trans('CustomerCode').''; print $object->code_client; if ($object->check_codeclient() <> 0) print ' ('.$langs->trans("WrongCustomerCode").')'; @@ -103,7 +98,7 @@ if ($id > 0) if ($object->fournisseur) { - print '
'; + print '
'; print $langs->trans('SupplierCode').''; print $object->code_fournisseur; if ($object->check_codefournisseur() <> 0) print ' ('.$langs->trans("WrongSupplierCode").')'; diff --git a/htdocs/societe/project.php b/htdocs/societe/project.php index 6eb9765d034..eb3963d0f7a 100644 --- a/htdocs/societe/project.php +++ b/htdocs/societe/project.php @@ -87,11 +87,6 @@ if ($socid) print '
'; print ''; - // Alias names (commercial, trademark or alias names) - print '"; - if (! empty($conf->global->SOCIETE_USEPREFIX)) // Old not used prefix field { print ''; diff --git a/htdocs/societe/soc.php b/htdocs/societe/soc.php index 02543d46993..71df6128afb 100644 --- a/htdocs/societe/soc.php +++ b/htdocs/societe/soc.php @@ -986,8 +986,8 @@ else print ''; // Prospect/Customer - print ''; - print ''; + print ''; - print '
'.$langs->trans('AliasNames').''; - print $object->name_alias; - print "
'.$langs->trans('Prefix').''.$object->prefix_comm.'
'.fieldLabel('ProspectCustomer','customerprospect',1).''; + print '
'.fieldLabel('ProspectCustomer','customerprospect',1).''; $selected=isset($_POST['client'])?GETPOST('client'):$object->client; print ''.fieldLabel('CustomerCode','customer_code').''; + print ''.fieldLabel('CustomerCode','customer_code').''; print ''; + print ''; print ''; // Alias names (commercial, trademark or alias names) @@ -1519,8 +1519,8 @@ else } // Prospect/Customer - print ''; - print ''; + print ''; - print ''; + print ''; print ''; print ''; print "\n"; diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 9b6db71be9e..b9859f9ec9b 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -117,7 +117,6 @@ class MouvementStock extends CommonObject // Test if product require batch data. If yes, and there is not, we throw an error. if (! empty($conf->productbatch->enabled) && $product->hasbatch() && ! $skip_batch) { - //if (empty($batch) && empty($eatby) && empty($sellby)) if (empty($batch)) { $this->errors[]=$langs->trans("ErrorTryToMakeMoveOnProductRequiringBatchData", $product->name); @@ -130,8 +129,9 @@ class MouvementStock extends CommonObject // FIXME Code not complete to implement this // Check table llx_product_lot from batchnumber for same product // If found and eatby/sellby defined into table and provided and differs, return error - // If found and eatby/sellby not defined into table and provided, we update table // If found and eatby/sellby defined into table and not provided, we take value from table + // If found and eatby/sellby not defined into table and provided, we update table + // If found and eatby/sellby not defined into table and not provided, we do nothing // If not found, we add record $sql = "SELECT pb.rowid, pb.batch, pb.eatby, pb.sellby FROM ".MAIN_DB_PREFIX."product_lot as pb"; $sql.= " WHERE pb.fk_product = ".$fk_product." AND pb.batch = '".$this->db->escape($batch)."'"; @@ -158,6 +158,27 @@ class MouvementStock extends CommonObject return -3; } } + else + { + $eatby = $obj->eatby; // If found and eatby/sellby defined into table and not provided, we take value from table + } + } + else + { + if ($eatby) // If found and eatby/sellby not defined into table and provided, we update table + { + $productlot = new Productlot($this->db); + $result = $productlot->fetch($obj->rowid); + $productlot->eatby = $eatby; + $result = $productlot->update($user); + if ($result <= 0) + { + $this->error = $productlot->error; + $this->errors = $productlot->errors; + $this->db->rollback(); + return -5; + } + } } if ($obj->sellby) { @@ -171,8 +192,30 @@ class MouvementStock extends CommonObject return -3; } } + else + { + $sellby = $obj->sellby; // If found and eatby/sellby defined into table and not provided, we take value from table + } } - $i++; + else + { + if ($sellby) // If found and eatby/sellby not defined into table and provided, we update table + { + $productlot = new Productlot($this->db); + $result = $productlot->fetch($obj->rowid); + $productlot->sellby = $sellby; + $result = $productlot->update($user); + if ($result <= 0) + { + $this->error = $productlot->error; + $this->errors = $productlot->errors; + $this->db->rollback(); + return -5; + } + } + } + + $i++; } } else @@ -181,7 +224,7 @@ class MouvementStock extends CommonObject $productlot->fk_product = $fk_product; $productlot->batch = $batch; $result = $productlot->create($user); - if (! $result) + if ($result <= 0) { $this->error = $productlot->error; $this->errors = $productlot->errors; @@ -367,7 +410,7 @@ class MouvementStock extends CommonObject $sql = "UPDATE ".MAIN_DB_PREFIX."product as p SET p.pmp = ".$newpmp.", "; $sql.= " stock=(SELECT SUM(ps.reel) FROM ".MAIN_DB_PREFIX."product_stock ps WHERE ps.fk_product = p.rowid)"; $sql.= " WHERE rowid = ".$fk_product; - print $sql; + dol_syslog(get_class($this)."::_create", LOG_DEBUG); $resql=$this->db->query($sql); if (! $resql) diff --git a/htdocs/product/stock/class/productlot.class.php b/htdocs/product/stock/class/productlot.class.php index 26970a68e85..9b15ed31fcc 100644 --- a/htdocs/product/stock/class/productlot.class.php +++ b/htdocs/product/stock/class/productlot.class.php @@ -56,14 +56,15 @@ class Productlot extends CommonObject /** */ - public $tms = ''; + public $entity; public $fk_product; public $batch; public $eatby = ''; public $sellby = ''; - public $note_public; - public $note_private; - public $qty; + public $datec = ''; + public $tms = ''; + public $fk_user_creat; + public $fk_user_modif; public $import_key; /** @@ -96,20 +97,20 @@ class Productlot extends CommonObject // Clean parameters + if (isset($this->entity)) { + $this->entity = trim($this->entity); + } if (isset($this->fk_product)) { $this->fk_product = trim($this->fk_product); } if (isset($this->batch)) { $this->batch = trim($this->batch); } - if (isset($this->note_public)) { - $this->note_public = trim($this->note_public); + if (isset($this->fk_user_creat)) { + $this->fk_user_creat = trim($this->fk_user_creat); } - if (isset($this->note_private)) { - $this->note_private = trim($this->note_private); - } - if (isset($this->qty)) { - $this->qty = trim($this->qty); + if (isset($this->fk_user_modif)) { + $this->fk_user_modif = trim($this->fk_user_modif); } if (isset($this->import_key)) { $this->import_key = trim($this->import_key); @@ -123,26 +124,28 @@ class Productlot extends CommonObject // Insert request $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . $this->table_element . '('; + $sql.= 'entity,'; $sql.= 'fk_product,'; $sql.= 'batch,'; $sql.= 'eatby,'; $sql.= 'sellby,'; - $sql.= 'note_public,'; - $sql.= 'note_private,'; - $sql.= 'qty'; + $sql.= 'datec,'; + $sql.= 'fk_user_creat,'; + $sql.= 'fk_user_modif,'; $sql.= 'import_key'; $sql .= ') VALUES ('; + $sql .= ' '.(! isset($this->entity)?'NULL':$this->entity).','; $sql .= ' '.(! isset($this->fk_product)?'NULL':$this->fk_product).','; $sql .= ' '.(! isset($this->batch)?'NULL':"'".$this->db->escape($this->batch)."'").','; $sql .= ' '.(! isset($this->eatby) || dol_strlen($this->eatby)==0?'NULL':"'".$this->db->idate($this->eatby)."'").','; $sql .= ' '.(! isset($this->sellby) || dol_strlen($this->sellby)==0?'NULL':"'".$this->db->idate($this->sellby)."'").','; - $sql .= ' '.(! isset($this->note_public)?'NULL':"'".$this->db->escape($this->note_public)."'").','; - $sql .= ' '.(! isset($this->note_private)?'NULL':"'".$this->db->escape($this->note_private)."'").','; - $sql .= ' '.(! isset($this->qty)?'NULL':"'".$this->qty."'").','; - $sql .= ' '.(! isset($this->import_key)?'NULL':"'".$this->db->escape($this->import_key)."'"); + $sql .= ' '."'".$this->db->idate(dol_now())."'".','; + $sql .= ' '.(! isset($this->fk_user_creat)?'NULL':$this->fk_user_creat).','; + $sql .= ' '.(! isset($this->fk_user_modif)?'NULL':$this->fk_user_modif).','; + $sql .= ' '.(! isset($this->import_key)?'NULL':$this->import_key); $sql .= ')'; @@ -197,14 +200,15 @@ class Productlot extends CommonObject $sql = 'SELECT'; $sql .= ' t.rowid,'; - $sql .= " t.tms,"; + $sql .= " t.entity,"; $sql .= " t.fk_product,"; $sql .= " t.batch,"; $sql .= " t.eatby,"; $sql .= " t.sellby,"; - $sql .= " t.note_public,"; - $sql .= " t.note_private,"; - $sql .= " t.qty,"; + $sql .= " t.datec,"; + $sql .= " t.tms,"; + $sql .= " t.fk_user_creat,"; + $sql .= " t.fk_user_modif,"; $sql .= " t.import_key"; @@ -223,14 +227,15 @@ class Productlot extends CommonObject $this->id = $obj->rowid; - $this->tms = $this->db->jdate($obj->tms); + $this->entity = $obj->entity; $this->fk_product = $obj->fk_product; $this->batch = $obj->batch; $this->eatby = $this->db->jdate($obj->eatby); $this->sellby = $this->db->jdate($obj->sellby); - $this->note_public = $obj->note_public; - $this->note_private = $obj->note_private; - $this->qty = $obj->qty; + $this->datec = $this->db->jdate($obj->datec); + $this->tms = $this->db->jdate($obj->tms); + $this->fk_user_creat = $obj->fk_user_creat; + $this->fk_user_modif = $obj->fk_user_modif; $this->import_key = $obj->import_key; @@ -269,14 +274,15 @@ class Productlot extends CommonObject $sql = 'SELECT'; $sql .= ' t.rowid,'; - $sql .= " t.tms,"; + $sql .= " t.entity,"; $sql .= " t.fk_product,"; $sql .= " t.batch,"; $sql .= " t.eatby,"; $sql .= " t.sellby,"; - $sql .= " t.note_public,"; - $sql .= " t.note_private,"; - $sql .= " t.qty,"; + $sql .= " t.datec,"; + $sql .= " t.tms,"; + $sql .= " t.fk_user_creat,"; + $sql .= " t.fk_user_modif,"; $sql .= " t.import_key"; @@ -310,14 +316,15 @@ class Productlot extends CommonObject $line->id = $obj->rowid; - $line->tms = $this->db->jdate($obj->tms); + $line->entity = $obj->entity; $line->fk_product = $obj->fk_product; $line->batch = $obj->batch; $line->eatby = $this->db->jdate($obj->eatby); $line->sellby = $this->db->jdate($obj->sellby); - $line->note_public = $obj->note_public; - $line->note_private = $obj->note_private; - $line->qty = $obj->qty; + $line->datec = $this->db->jdate($obj->datec); + $line->tms = $this->db->jdate($obj->tms); + $line->fk_user_creat = $obj->fk_user_creat; + $line->fk_user_modif = $obj->fk_user_modif; $line->import_key = $obj->import_key; @@ -351,20 +358,20 @@ class Productlot extends CommonObject // Clean parameters + if (isset($this->entity)) { + $this->entity = trim($this->entity); + } if (isset($this->fk_product)) { $this->fk_product = trim($this->fk_product); } if (isset($this->batch)) { $this->batch = trim($this->batch); } - if (isset($this->note_public)) { - $this->note_public = trim($this->note_public); + if (isset($this->fk_user_creat)) { + $this->fk_user_creat = trim($this->fk_user_creat); } - if (isset($this->note_private)) { - $this->note_private = trim($this->note_private); - } - if (isset($this->qty)) { - $this->qty = trim($this->qty); + if (isset($this->fk_user_modif)) { + $this->fk_user_modif = trim($this->fk_user_modif); } if (isset($this->import_key)) { $this->import_key = trim($this->import_key); @@ -378,15 +385,16 @@ class Productlot extends CommonObject // Update request $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element . ' SET'; - $sql .= ' tms = '.(dol_strlen($this->tms) != 0 ? "'".$this->db->idate($this->tms)."'" : "'".$this->db->idate(dol_now())."'").','; + $sql .= ' entity = '.(isset($this->entity)?$this->entity:"null").','; $sql .= ' fk_product = '.(isset($this->fk_product)?$this->fk_product:"null").','; $sql .= ' batch = '.(isset($this->batch)?"'".$this->db->escape($this->batch)."'":"null").','; $sql .= ' eatby = '.(! isset($this->eatby) || dol_strlen($this->eatby) != 0 ? "'".$this->db->idate($this->eatby)."'" : 'null').','; $sql .= ' sellby = '.(! isset($this->sellby) || dol_strlen($this->sellby) != 0 ? "'".$this->db->idate($this->sellby)."'" : 'null').','; - $sql .= ' note_public = '.(isset($this->note_public)?"'".$this->db->escape($this->note_public)."'":"null").','; - $sql .= ' note_private = '.(isset($this->note_private)?"'".$this->db->escape($this->note_private)."'":"null").','; - $sql .= ' qty = '.(isset($this->qty)?$this->qty:"null").','; - $sql .= ' import_key = '.(isset($this->import_key)?"'".$this->db->escape($this->import_key)."'":"null"); + $sql .= ' datec = '.(! isset($this->datec) || dol_strlen($this->datec) != 0 ? "'".$this->db->idate($this->datec)."'" : 'null').','; + $sql .= ' tms = '.(dol_strlen($this->tms) != 0 ? "'".$this->db->idate($this->tms)."'" : "'".$this->db->idate(dol_now())."'").','; + $sql .= ' fk_user_creat = '.(isset($this->fk_user_creat)?$this->fk_user_creat:"null").','; + $sql .= ' fk_user_modif = '.(isset($this->fk_user_modif)?$this->fk_user_modif:"null").','; + $sql .= ' import_key = '.(isset($this->import_key)?$this->import_key:"null"); $sql .= ' WHERE rowid=' . $this->id; @@ -626,14 +634,15 @@ class Productlot extends CommonObject { $this->id = 0; - $this->tms = ''; + $this->entity = ''; $this->fk_product = ''; $this->batch = ''; $this->eatby = ''; $this->sellby = ''; - $this->note_public = ''; - $this->note_private = ''; - $this->qty = ''; + $this->datec = ''; + $this->tms = ''; + $this->fk_user_creat = ''; + $this->fk_user_modif = ''; $this->import_key = ''; @@ -654,14 +663,15 @@ class ProductlotLine * @var mixed Sample line property 1 */ - public $tms = ''; + public $entity; public $fk_product; public $batch; public $eatby = ''; public $sellby = ''; - public $note_public; - public $note_private; - public $qty; + public $datec = ''; + public $tms = ''; + public $fk_user_creat; + public $fk_user_modif; public $import_key; /** diff --git a/htdocs/product/stock/mouvement.php b/htdocs/product/stock/mouvement.php index 29ee33c56eb..b18ca146a25 100644 --- a/htdocs/product/stock/mouvement.php +++ b/htdocs/product/stock/mouvement.php @@ -367,7 +367,7 @@ if (! empty($search_movement)) $sql.= " AND m.label LIKE '%".$db->escape($s if (! empty($search_inventorycode)) $sql.= " AND m.inventorycode LIKE '%".$db->escape($search_inventorycode)."%'"; if (! empty($search_product_ref)) $sql.= " AND p.ref LIKE '%".$db->escape($search_product_ref)."%'"; if (! empty($search_product)) $sql.= " AND p.label LIKE '%".$db->escape($search_product)."%'"; -if (! empty($search_warehouse)) $sql.= " AND e.label LIKE '%".$db->escape($search_warehouse)."%'"; +if (! empty($search_warehouse)) $sql.= " AND e.rowid = '".$db->escape($search_warehouse)."'"; if (! empty($search_user)) $sql.= " AND u.login LIKE '%".$db->escape($search_user)."%'"; if (! empty($search_batch)) $sql.= " AND m.batch LIKE '%".$db->escape($search_batch)."%'"; @@ -702,7 +702,8 @@ if ($resql) if (! $id > 0) { print ''; } // Author diff --git a/htdocs/product/stock/product.php b/htdocs/product/stock/product.php index 499efd29847..1b4c159b1d6 100644 --- a/htdocs/product/stock/product.php +++ b/htdocs/product/stock/product.php @@ -273,6 +273,10 @@ if ($action == "transfert_stock" && ! $cancel) $eatby,$sellby,$batch, GETPOST('inventorycode') ); + if ($result1 < 0) $error++; + } + if (! $error) + { // Add stock $result2=$object->correct_stock_batch( $user, @@ -284,31 +288,39 @@ if ($action == "transfert_stock" && ! $cancel) $eatby,$sellby,$batch, GETPOST('inventorycode') ); + if ($result2 < 0) $error++; } } else { - // Remove stock - $result1=$object->correct_stock( - $user, - GETPOST("id_entrepot"), - GETPOST("nbpiece"), - 1, - GETPOST("label"), - $pricesrc, - GETPOST('inventorycode') - ); - - // Add stock - $result2=$object->correct_stock( - $user, - GETPOST("id_entrepot_destination"), - GETPOST("nbpiece"), - 0, - GETPOST("label"), - $pricedest, - GETPOST('inventorycode') - ); + if (! $error) + { + // Remove stock + $result1=$object->correct_stock( + $user, + GETPOST("id_entrepot"), + GETPOST("nbpiece"), + 1, + GETPOST("label"), + $pricesrc, + GETPOST('inventorycode') + ); + if ($result1 < 0) $error++; + } + if (! $error) + { + // Add stock + $result2=$object->correct_stock( + $user, + GETPOST("id_entrepot_destination"), + GETPOST("nbpiece"), + 0, + GETPOST("label"), + $pricedest, + GETPOST('inventorycode') + ); + if ($result2 < 0) $error++; + } } if (! $error && $result1 >= 0 && $result2 >= 0) { @@ -589,7 +601,7 @@ if ($id > 0 || $ref) if ($action == "correction") { include DOL_DOCUMENT_ROOT.'/product/stock/tpl/stockcorrection.tpl.php'; - print '
'; + print '

'; } /* @@ -598,7 +610,7 @@ if ($id > 0 || $ref) if ($action == "transfert") { include DOL_DOCUMENT_ROOT.'/product/stock/tpl/stocktransfer.tpl.php'; - print '
'; + print '

'; } /* @@ -657,10 +669,10 @@ if (empty($action) && $object->id) /* - * Stock detail (by warehouse). Do not go down into batch. + * Stock detail (by warehouse). May go down into batch details. */ -print '
'; $tmpcode=$object->code_client; if (empty($tmpcode) && ! empty($modCodeClient->code_auto)) $tmpcode=$modCodeClient->getNextValue($object,0); @@ -1494,7 +1494,7 @@ else } // Name - print '
'.fieldLabel('ThirdPartyName','name',1).'
'.fieldLabel('ThirdPartyName','name',1).'
'.fieldLabel('ProspectCustomer','customerprospect',1).'
'.fieldLabel('ProspectCustomer','customerprospect',1).''; @@ -218,24 +219,24 @@ if ($resql) print ''; print ''; - print_barre_liste($langs->trans("ReceivedCustomersPayments"), $page, $_SERVER["PHP_SELF"],$paramlist,$sortfield,$sortorder,'',$num, $nbtotalofrecords,'title_accountancy.png', 0, '', '', $limit); + print_barre_liste($langs->trans("ReceivedCustomersPayments"), $page, $_SERVER["PHP_SELF"],$param,$sortfield,$sortorder,'',$num, $nbtotalofrecords,'title_accountancy.png', 0, '', '', $limit); print ''; print ''; - print_liste_field_titre($langs->trans("RefPayment"),$_SERVER["PHP_SELF"],"p.rowid","",$paramlist,"",$sortfield,$sortorder); - print_liste_field_titre($langs->trans("Date"),$_SERVER["PHP_SELF"],"dp","",$paramlist,'align="center"',$sortfield,$sortorder); - print_liste_field_titre($langs->trans("ThirdParty"),$_SERVER["PHP_SELF"],"s.nom","",$paramlist,"",$sortfield,$sortorder); - print_liste_field_titre($langs->trans("Type"),$_SERVER["PHP_SELF"],"c.libelle","",$paramlist,"",$sortfield,$sortorder); - print_liste_field_titre($langs->trans("Numero"),$_SERVER["PHP_SELF"],"p.num_paiement","",$paramlist,"",$sortfield,$sortorder); - print_liste_field_titre($langs->trans("Account"),$_SERVER["PHP_SELF"],"ba.label","",$paramlist,"",$sortfield,$sortorder); - print_liste_field_titre($langs->trans("Amount"),$_SERVER["PHP_SELF"],"p.amount","",$paramlist,'align="right"',$sortfield,$sortorder); - //print_liste_field_titre($langs->trans("Invoices"),"","","",$paramlist,'align="left"',$sortfield,$sortorder); + print_liste_field_titre($langs->trans("RefPayment"),$_SERVER["PHP_SELF"],"p.rowid","",$param,"",$sortfield,$sortorder); + print_liste_field_titre($langs->trans("Date"),$_SERVER["PHP_SELF"],"dp","",$param,'align="center"',$sortfield,$sortorder); + print_liste_field_titre($langs->trans("ThirdParty"),$_SERVER["PHP_SELF"],"s.nom","",$param,"",$sortfield,$sortorder); + print_liste_field_titre($langs->trans("Type"),$_SERVER["PHP_SELF"],"c.libelle","",$param,"",$sortfield,$sortorder); + print_liste_field_titre($langs->trans("Numero"),$_SERVER["PHP_SELF"],"p.num_paiement","",$param,"",$sortfield,$sortorder); + print_liste_field_titre($langs->trans("Account"),$_SERVER["PHP_SELF"],"ba.label","",$param,"",$sortfield,$sortorder); + print_liste_field_titre($langs->trans("Amount"),$_SERVER["PHP_SELF"],"p.amount","",$param,'align="right"',$sortfield,$sortorder); + //print_liste_field_titre($langs->trans("Invoices"),"","","",$param,'align="left"',$sortfield,$sortorder); $parameters=array(); $reshook=$hookmanager->executeHooks('printFieldListTitle',$parameters); // Note that $action and $object may have been modified by hook print $hookmanager->resPrint; - if (! empty($conf->global->BILL_ADD_PAYMENT_VALIDATION)) print_liste_field_titre($langs->trans("Status"),$_SERVER["PHP_SELF"],"p.statut","",$paramlist,'align="right"',$sortfield,$sortorder); + if (! empty($conf->global->BILL_ADD_PAYMENT_VALIDATION)) print_liste_field_titre($langs->trans("Status"),$_SERVER["PHP_SELF"],"p.statut","",$param,'align="right"',$sortfield,$sortorder); print_liste_field_titre('',$_SERVER["PHP_SELF"],"",'','','',$sortfield,$sortorder,'maxwidthsearch '); print "\n"; diff --git a/htdocs/product/reassort.php b/htdocs/product/reassort.php index e330e9d8b75..56dcc876d3e 100644 --- a/htdocs/product/reassort.php +++ b/htdocs/product/reassort.php @@ -255,7 +255,8 @@ if ($resql) $formProduct->loadWarehouses(); $warehouses_list = $formProduct->cache_warehouses; $nb_warehouse = count($warehouses_list); - $colspan_warehouse = $nb_warehouse > 1 ? $nb_warehouse+1 : 1; + $colspan_warehouse = 1; + if (! empty($conf->global->STOCK_DETAIL_ON_WAREHOUSE)) { $colspan_warehouse = $nb_warehouse > 1 ? $nb_warehouse+1 : 1; } print '
'; diff --git a/htdocs/product/stock/card.php b/htdocs/product/stock/card.php index f34f4482a49..659bea1f5a8 100644 --- a/htdocs/product/stock/card.php +++ b/htdocs/product/stock/card.php @@ -185,7 +185,7 @@ if ($action == 'create') print '
'; // Ref - print ''; + print ''; print ''; @@ -209,7 +209,7 @@ if ($action == 'create') print ''; // Country - print ''; diff --git a/htdocs/theme/eldy/style.css.php b/htdocs/theme/eldy/style.css.php index 162310f4697..3a82b25deeb 100644 --- a/htdocs/theme/eldy/style.css.php +++ b/htdocs/theme/eldy/style.css.php @@ -3142,19 +3142,19 @@ table.cal_month { border-spacing: 0px; } table.cal_month td:first-child { border-left: 0px; } table.cal_month td:last-child { border-right: 0px; } .cal_current_month { border-top: 0; border-left: solid 1px #E0E0E0; border-right: 0; border-bottom: solid 1px #E0E0E0; } -.cal_current_month_peruserleft { border-top: 0; border-left: solid 3px #6C7C7B; border-right: 0; border-bottom: solid 1px #E0E0E0; } +.cal_current_month_peruserleft { border-top: 0; border-left: solid 2px #6C7C7B; border-right: 0; border-bottom: solid 1px #E0E0E0; } .cal_current_month_oneday { border-right: solid 1px #E0E0E0; } .cal_other_month { border-top: 0; border-left: solid 1px #C0C0C0; border-right: 0; border-bottom: solid 1px #C0C0C0; } -.cal_other_month_peruserleft { border-top: 0; border-left: solid 3px #6C7C7B !important; border-right: 0; } +.cal_other_month_peruserleft { border-top: 0; border-left: solid 2px #6C7C7B !important; border-right: 0; } .cal_current_month_right { border-right: solid 1px #E0E0E0; } .cal_other_month_right { border-right: solid 1px #C0C0C0; } .cal_other_month { opacity: 0.6; background: #EAEAEA; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } .cal_past_month { opacity: 0.6; background: #EEEEEE; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } .cal_current_month { background: #FFFFFF; border-left: solid 1px #E0E0E0; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } -.cal_current_month_peruserleft { background: #FFFFFF; border-left: solid 3px #6C7C7B; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } +.cal_current_month_peruserleft { background: #FFFFFF; border-left: solid 2px #6C7C7B; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } .cal_today { background: #FDFDF0; border-left: solid 1px #E0E0E0; border-bottom: solid 1px #E0E0E0; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } .cal_today_peruser { background: #FDFDF0; border-right: solid 1px #E0E0E0; border-bottom: solid 1px #E0E0E0; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } -.cal_today_peruser_peruserleft { background: #FDFDF0; border-left: solid 3px #6C7C7B; border-right: solid 1px #E0E0E0; border-bottom: solid 1px #E0E0E0; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } +.cal_today_peruser_peruserleft { background: #FDFDF0; border-left: solid 2px #6C7C7B; border-right: solid 1px #E0E0E0; border-bottom: solid 1px #E0E0E0; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } .cal_past { } .cal_peruser { padding: 0px; } .cal_impair { background: #F8F8F8; } @@ -3642,9 +3642,9 @@ div.dolEventError h1, div.dolEventError h2 { /* ============================================================================== */ .divmap, #google-visualization-geomap-embed-0, #google-visualization-geomap-embed-1, #google-visualization-geomap-embed-2 { - -moz-box-shadow: 0px 0px 10px #AAA; +/* -moz-box-shadow: 0px 0px 10px #AAA; -webkit-box-shadow: 0px 0px 10px #AAA; - box-shadow: 0px 0px 10px #AAA; + box-shadow: 0px 0px 10px #AAA; */ } diff --git a/htdocs/theme/md/style.css.php b/htdocs/theme/md/style.css.php index 3de39efb50f..4f9e1c24554 100644 --- a/htdocs/theme/md/style.css.php +++ b/htdocs/theme/md/style.css.php @@ -3019,19 +3019,19 @@ table.cal_month { border-spacing: 0px; } table.cal_month td:first-child { border-left: 0px; } table.cal_month td:last-child { border-right: 0px; } .cal_current_month { border-top: 0; border-left: solid 1px #E0E0E0; border-right: 0; border-bottom: solid 1px #E0E0E0; } -.cal_current_month_peruserleft { border-top: 0; border-left: solid 3px #6C7C7B; border-right: 0; border-bottom: solid 1px #E0E0E0; } +.cal_current_month_peruserleft { border-top: 0; border-left: solid 2px #6C7C7B; border-right: 0; border-bottom: solid 1px #E0E0E0; } .cal_current_month_oneday { border-right: solid 1px #E0E0E0; } .cal_other_month { border-top: 0; border-left: solid 1px #C0C0C0; border-right: 0; border-bottom: solid 1px #C0C0C0; } -.cal_other_month_peruserleft { border-top: 0; border-left: solid 3px #6C7C7B !important; border-right: 0; } +.cal_other_month_peruserleft { border-top: 0; border-left: solid 2px #6C7C7B !important; border-right: 0; } .cal_current_month_right { border-right: solid 1px #E0E0E0; } .cal_other_month_right { border-right: solid 1px #C0C0C0; } .cal_other_month { opacity: 0.6; background: #EAEAEA; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } .cal_past_month { opacity: 0.6; background: #EEEEEE; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } .cal_current_month { background: #FFFFFF; border-left: solid 1px #E0E0E0; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } -.cal_current_month_peruserleft { background: #FFFFFF; border-left: solid 3px #6C7C7B; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } +.cal_current_month_peruserleft { background: #FFFFFF; border-left: solid 2px #6C7C7B; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } .cal_today { background: #FDFDF0; border-left: solid 1px #E0E0E0; border-bottom: solid 1px #E0E0E0; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } .cal_today_peruser { background: #FDFDF0; border-right: solid 1px #E0E0E0; border-bottom: solid 1px #E0E0E0; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } -.cal_today_peruser_peruserleft { background: #FDFDF0; border-left: solid 3px #6C7C7B; border-right: solid 1px #E0E0E0; border-bottom: solid 1px #E0E0E0; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } +.cal_today_peruser_peruserleft { background: #FDFDF0; border-left: solid 2px #6C7C7B; border-right: solid 1px #E0E0E0; border-bottom: solid 1px #E0E0E0; padding-: 2px; padding-: 1px; padding-top: 0px; padding-bottom: 0px; } .cal_past { } .cal_peruser { padding: 0px; } .cal_impair { background: #F8F8F8; } @@ -3518,9 +3518,9 @@ div.dolEventError h1, div.dolEventError h2 { /* ============================================================================== */ .divmap, #google-visualization-geomap-embed-0, #google-visualization-geomap-embed-1, google-visualization-geomap-embed-2 { - -moz-box-shadow: 0px 0px 10px #AAA; +/* -moz-box-shadow: 0px 0px 10px #AAA; -webkit-box-shadow: 0px 0px 10px #AAA; - box-shadow: 0px 0px 10px #AAA; + box-shadow: 0px 0px 10px #AAA; */ } From 4e98f573855c645cc68b36320aff9ef69a151c02 Mon Sep 17 00:00:00 2001 From: florian HENRY Date: Tue, 17 May 2016 09:02:22 +0200 Subject: [PATCH 16/61] FIX : php strict warning --- .../commande/doc/pdf_proforma.modules.php | 4 ++-- htdocs/core/modules/modExpedition.class.php | 18 ++++++++++++------ .../societe/class/companybankaccount.class.php | 6 +++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/htdocs/core/modules/commande/doc/pdf_proforma.modules.php b/htdocs/core/modules/commande/doc/pdf_proforma.modules.php index 1434399d225..5c77b9b7b32 100644 --- a/htdocs/core/modules/commande/doc/pdf_proforma.modules.php +++ b/htdocs/core/modules/commande/doc/pdf_proforma.modules.php @@ -49,7 +49,7 @@ class pdf_proforma extends pdf_einstein function __construct($db) { global $conf,$langs,$mysoc; - + parent::__construct($db); $this->name = "proforma"; @@ -66,7 +66,7 @@ class pdf_proforma extends pdf_einstein * @param Translate $outputlangs Object lang for output * @return void */ - function _pagehead(&$pdf, $object, $showaddress, $outputlangs) + function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $titlekey="InvoiceProForma") { global $conf,$langs,$hookmanager; diff --git a/htdocs/core/modules/modExpedition.class.php b/htdocs/core/modules/modExpedition.class.php index ea44daee18e..4f7baff0c64 100644 --- a/htdocs/core/modules/modExpedition.class.php +++ b/htdocs/core/modules/modExpedition.class.php @@ -83,7 +83,7 @@ class modExpedition extends DolibarrModules // Constants $this->const = array(); $r=0; - + $this->const[$r][0] = "EXPEDITION_ADDON_PDF"; $this->const[$r][1] = "chaine"; $this->const[$r][2] = "rouget"; @@ -125,14 +125,14 @@ class modExpedition extends DolibarrModules $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; - + $this->const[$r][0] = "MAIN_SUBMODULE_EXPEDITION"; $this->const[$r][1] = "chaine"; $this->const[$r][2] = "1"; $this->const[$r][3] = "Enable shipments"; $this->const[$r][4] = 0; $r++; - + // Boxes $this->boxes = array(); @@ -224,8 +224,14 @@ class modExpedition extends DolibarrModules include_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php'; $shipment=new Commande($this->db); - $idcontacts=join(',',array_keys($shipment->liste_type_contact('external','',0,0,''))); - + $contact_arrays=$shipment->liste_type_contact('external','',0,0,''); + if (is_array($contact_arrays) && count($contact_arrays)>0){ + $idcontacts=join(',',array_keys($shipment->liste_type_contact('external','',0,0,''))); + } else { + $idcontacts=0; + } + + $r++; $this->export_code[$r]=$this->rights_class.'_'.$r; $this->export_label[$r]='Shipments'; // Translation key (used only if key ExportDataset_xxx_z not found) @@ -237,7 +243,7 @@ class modExpedition extends DolibarrModules $this->export_entities_array[$r]=array('s.rowid'=>"company",'s.nom'=>'company','s.address'=>'company','s.zip'=>'company','s.town'=>'company','d.nom'=>'company','co.label'=>'company','co.code'=>'company','s.fk_pays'=>'company','s.phone'=>'company','s.siren'=>'company','s.ape'=>'company','s.siret'=>'company','s.idprof4'=>'company','s.idprof5'=>'company','s.idprof6'=>'company','c.rowid'=>"shipment",'c.ref'=>"shipment",'c.ref_customer'=>"shipment",'c.fk_soc'=>"shipment",'c.date_creation'=>"shipment",'c.date_delivery'=>"shipment",'c.tracking_number'=>'shipment','c.height'=>"shipment",'c.width'=>"shipment",'c.size'=>'shipment','c.size_units'=>'shipment','c.weight'=>"shipment",'c.weight_units'=>'shipment','c.fk_statut'=>"shipment",'c.note_public'=>"shipment",'ed.rowid'=>'shipment_line','cd.description'=>'shipment_line','ed.qty'=>"shipment_line",'p.rowid'=>'product','p.ref'=>'product','p.label'=>'product','p.weight'=>'product','p.weight_units'=>'product','p.volume'=>'product','p.volume_units'=>'product'); if ($idcontacts && ! empty($conf->global->SHIPMENT_ADD_CONTACTS_IN_EXPORT)) $this->export_entities_array[$r]+=array('sp.rowid'=>'contact','sp.lastname'=>'contact','sp.firstname'=>'contact','sp.note_public'=>'contact'); $this->export_dependencies_array[$r]=array('shipment_line'=>'ed.rowid','product'=>'ed.rowid'); // To add unique key if we ask a field of a child to avoid the DISTINCT to discard them - if ($idcontacts && ! empty($conf->global->SHIPMENT_ADD_CONTACTS_IN_EXPORT)) + if ($idcontacts && ! empty($conf->global->SHIPMENT_ADD_CONTACTS_IN_EXPORT)) { $keyforselect='socpeople'; $keyforelement='contact'; $keyforaliasextra='extra3'; include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php'; diff --git a/htdocs/societe/class/companybankaccount.class.php b/htdocs/societe/class/companybankaccount.class.php index fe34f57fd6d..9fa62dea587 100644 --- a/htdocs/societe/class/companybankaccount.class.php +++ b/htdocs/societe/class/companybankaccount.class.php @@ -64,7 +64,7 @@ class CompanyBankAccount extends Account * @param User $user User * @return int <0 if KO, >= 0 if OK */ - function create($user='') + function create(User $user = null) { $now=dol_now(); @@ -102,7 +102,7 @@ class CompanyBankAccount extends Account * @param User $user Object user * @return int <=0 if KO, >0 if OK */ - function update($user='') + function update(User $user = null) { global $conf; @@ -205,7 +205,7 @@ class CompanyBankAccount extends Account * @param User $user User deleting * @return int <0 if KO, >0 if OK */ - function delete($user='') + function delete(User $user = null) { global $conf; From 63d0710842fdc93dd54b3142845550b2f66242c3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 10:39:35 +0200 Subject: [PATCH 17/61] FIX Creation of the second ressource type fails. --- htdocs/install/mysql/migration/3.8.0-3.9.0.sql | 3 +++ htdocs/resource/class/html.formresource.class.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/htdocs/install/mysql/migration/3.8.0-3.9.0.sql b/htdocs/install/mysql/migration/3.8.0-3.9.0.sql index 9d77a87fd7c..74f89441f64 100755 --- a/htdocs/install/mysql/migration/3.8.0-3.9.0.sql +++ b/htdocs/install/mysql/migration/3.8.0-3.9.0.sql @@ -607,3 +607,6 @@ INSERT INTO llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) VALUES (14 INSERT INTO llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) VALUES (1482, 148, '7','0','VAT reduced rate',1); INSERT INTO llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) VALUES (1483, 148, '5','0','VAT super-reduced rate', 1); INSERT INTO llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) VALUES (1484, 148, '0','0','VAT Rate 0', 1); + +-- VMYSQL4.1 ALTER TABLE llx_c_type_resource CHANGE COLUMN rowid rowid integer NOT NULL AUTO_INCREMENT; + diff --git a/htdocs/resource/class/html.formresource.class.php b/htdocs/resource/class/html.formresource.class.php index 4128de6fead..18119b0cf17 100644 --- a/htdocs/resource/class/html.formresource.class.php +++ b/htdocs/resource/class/html.formresource.class.php @@ -96,7 +96,7 @@ class FormResource } // Construct $out and $outarray - $out.= ''."\n"; if ($showempty) $out.= ''."\n"; $num = count($resourcestat->lines); From 3b99fb01ee2bb61b94f1452d87ee1bc5c56d9aa2 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 10:33:27 +0200 Subject: [PATCH 18/61] Fix duplicate table --- dev/skeletons/build_class_from_table.php | 10 +- htdocs/comm/card.php | 4 +- htdocs/fourn/card.php | 2 +- .../install/mysql/migration/3.9.0-4.0.0.sql | 24 +- .../install/mysql/tables/llx_product_lot.sql | 21 +- .../mysql/tables/llx_stock_lotserial.sql | 34 - .../stock/class/mouvementstock.class.php | 64 +- .../product/stock/class/productlot.class.php | 671 ++++++++++++++++++ htdocs/product/stock/productlot_card.php | 376 ++++++++++ htdocs/product/stock/productlot_list.php | 549 ++++++++++++++ 10 files changed, 1676 insertions(+), 79 deletions(-) delete mode 100644 htdocs/install/mysql/tables/llx_stock_lotserial.sql create mode 100644 htdocs/product/stock/class/productlot.class.php create mode 100644 htdocs/product/stock/productlot_card.php create mode 100644 htdocs/product/stock/productlot_list.php diff --git a/dev/skeletons/build_class_from_table.php b/dev/skeletons/build_class_from_table.php index 4de319dabc9..800bafe6318 100755 --- a/dev/skeletons/build_class_from_table.php +++ b/dev/skeletons/build_class_from_table.php @@ -180,7 +180,7 @@ if (! $sourcecontent) } // Define output variables -$outfile='out.'.$classmin.'.class.php'; +$outfile=$classmin.'.class.php'; $targetcontent=$sourcecontent; // Substitute module name @@ -455,9 +455,9 @@ else $error++; //-------------------------------------------------------------------- $skeletonfiles=array( - $path.'skeleton_script.php' => 'out.'.$classmin.'_script.php', - $path.'skeleton_list.php' => 'out.'.$classmin.'_list.php', - $path.'skeleton_card.php' => 'out.'.$classmin.'_card.php' + $path.'skeleton_script.php' => $classmin.'_script.php', + $path.'skeleton_list.php' => $classmin.'_list.php', + $path.'skeleton_card.php' => $classmin.'_card.php' ); foreach ($skeletonfiles as $skeletonfile => $outfile) @@ -670,5 +670,5 @@ foreach ($skeletonfiles as $skeletonfile => $outfile) // -------------------- END OF BUILD_CLASS_FROM_TABLE SCRIPT -------------------- -print "You can now rename generated files by removing the 'out.' prefix in their name and store them into directory /yourmodule/class (for .class.php file) or /yourmodule.\n"; +print "You can now move generated files to store them into directory /yourmodule/class (for .class.php file) or /yourmodule.\n"; return $error; diff --git a/htdocs/comm/card.php b/htdocs/comm/card.php index ce611528a62..e1828e911c7 100644 --- a/htdocs/comm/card.php +++ b/htdocs/comm/card.php @@ -456,8 +456,8 @@ if ($id > 0) } // Categories - if (!empty( $conf->categorie->enabled ) && !empty( $user->rights->categorie->lire )) { - print ''; + if (!empty($conf->categorie->enabled) && !empty($user->rights->categorie->lire)) { + print ''; print '"; diff --git a/htdocs/fourn/card.php b/htdocs/fourn/card.php index b64a8ee77e4..8c533f0f547 100644 --- a/htdocs/fourn/card.php +++ b/htdocs/fourn/card.php @@ -231,7 +231,7 @@ if ($object->id > 0) // Categories if (! empty($conf->categorie->enabled)) { - print ''; + print ''; print '"; diff --git a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql index c72ec02ca51..93469ff5a8f 100644 --- a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql +++ b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql @@ -39,19 +39,23 @@ ALTER TABLE llx_opensurvey_sondage ADD COLUMN status integer DEFAULT 1 after dat ALTER TABLE llx_expedition ADD COLUMN billed smallint DEFAULT 0; +-- DROP TABLE llx_product_lot; CREATE TABLE llx_product_lot ( - rowid integer AUTO_INCREMENT PRIMARY KEY, - tms timestamp, - fk_product integer NOT NULL, - batch varchar(30) NOT NULL, - eatby datetime DEFAULT NULL, - sellby datetime DEFAULT NULL, - note_public text, - note_private text, - qty double NOT NULL DEFAULT 0, - import_key varchar(14) DEFAULT NULL + rowid integer AUTO_INCREMENT PRIMARY KEY, + entity integer, + fk_product integer NOT NULL, -- Id of product + batch varchar(30) DEFAULT NULL, -- Lot or serial number + eatby date DEFAULT NULL, -- Eatby date + sellby date DEFAULT NULL, -- Sellby date + datec datetime, + tms timestamp, + fk_user_creat integer, + fk_user_modif integer, + import_key integer ) ENGINE=InnoDB; +DROP TABLE llx_stock_serial; + ALTER TABLE llx_product ADD COLUMN note_public text; ALTER TABLE llx_user ADD COLUMN note_public text; diff --git a/htdocs/install/mysql/tables/llx_product_lot.sql b/htdocs/install/mysql/tables/llx_product_lot.sql index 718cb11744c..498b6039977 100644 --- a/htdocs/install/mysql/tables/llx_product_lot.sql +++ b/htdocs/install/mysql/tables/llx_product_lot.sql @@ -18,14 +18,15 @@ -- ============================================================================ CREATE TABLE llx_product_lot ( - rowid integer AUTO_INCREMENT PRIMARY KEY, - tms timestamp, - fk_product integer NOT NULL, - batch varchar(30) NOT NULL, - eatby datetime DEFAULT NULL, - sellby datetime DEFAULT NULL, - note_public text, - note_private text, - qty double NOT NULL DEFAULT 0, - import_key varchar(14) DEFAULT NULL + rowid integer AUTO_INCREMENT PRIMARY KEY, + entity integer, + fk_product integer NOT NULL, -- Id of product + batch varchar(30) DEFAULT NULL, -- Lot or serial number + eatby date DEFAULT NULL, -- Eatby date + sellby date DEFAULT NULL, -- Sellby date + datec datetime, + tms timestamp, + fk_user_creat integer, + fk_user_modif integer, + import_key integer ) ENGINE=InnoDB; diff --git a/htdocs/install/mysql/tables/llx_stock_lotserial.sql b/htdocs/install/mysql/tables/llx_stock_lotserial.sql deleted file mode 100644 index ed8a14e49d0..00000000000 --- a/htdocs/install/mysql/tables/llx_stock_lotserial.sql +++ /dev/null @@ -1,34 +0,0 @@ --- ============================================================================ --- Copyright (C) 2015 Laurent Destailleur --- --- This program is free software; you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation; either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . --- --- This table may be used to store eatby and sellby date for a couple --- product-batch number. --- ============================================================================ - -create table llx_stock_lotserial -( - rowid integer AUTO_INCREMENT PRIMARY KEY, - entity integer, - fk_product integer NOT NULL, -- Id of product - batch varchar(30) DEFAULT NULL, -- Lot or serial number - eatby date DEFAULT NULL, -- Eatby date - sellby date DEFAULT NULL, -- Sellby date - datec datetime, - tms timestamp, - fk_user_creat integer, - fk_user_modif integer, - import_key integer -) ENGINE=innodb; diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index f1542d593b6..9b6db71be9e 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -73,6 +73,7 @@ class MouvementStock extends CommonObject global $conf, $langs; require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; + require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php'; $error = 0; dol_syslog(get_class($this)."::_create start userid=$user->id, fk_product=$fk_product, warehouse=$entrepot_id, qty=$qty, type=$type, price=$price, label=$label, inventorycode=$inventorycode, datem=".$datem.", eatby=".$eatby.", sellby=".$sellby.", batch=".$batch.", skip_batch=".$skip_batch); @@ -140,24 +141,53 @@ class MouvementStock extends CommonObject { $num = $this->db->num_rows($resql); $i=0; - while ($i < $num) + if ($num > 0) { - $obj = $this->db->fetch_object($resql); - if ($this->db->jdate($obj->eatby) != $eatby) - { - $this->errors[]=$langs->trans("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->eatby)), dol_print_date($eatby)); - dol_syslog($langs->transnoentities("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->eatby)), dol_print_date($eatby)), LOG_ERR); - $this->db->rollback(); - return -3; - } - if ($this->db->jdate($obj->sellby) != $sellby) - { - $this->errors[]=$langs->trans("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->sellby)), dol_print_date($sellby)); - dol_syslog($langs->transnoentities("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->sellby)), dol_print_date($sellby)), LOG_ERR); - $this->db->rollback(); - return -3; - } - $i++; + while ($i < $num) + { + $obj = $this->db->fetch_object($resql); + if ($obj->eatby) + { + if ($eatby) + { + if ($this->db->jdate($obj->eatby) != $eatby) // If found and eatby/sellby defined into table and provided and differs, return error + { + $this->errors[]=$langs->trans("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->eatby)), dol_print_date($eatby)); + dol_syslog($langs->transnoentities("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->eatby)), dol_print_date($eatby)), LOG_ERR); + $this->db->rollback(); + return -3; + } + } + } + if ($obj->sellby) + { + if ($sellby) + { + if ($this->db->jdate($obj->sellby) != $sellby) // If found and eatby/sellby defined into table and provided and differs, return error + { + $this->errors[]=$langs->trans("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->sellby)), dol_print_date($sellby)); + dol_syslog($langs->transnoentities("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->sellby)), dol_print_date($sellby)), LOG_ERR); + $this->db->rollback(); + return -3; + } + } + } + $i++; + } + } + else + { + $productlot = new Productlot($this->db); + $productlot->fk_product = $fk_product; + $productlot->batch = $batch; + $result = $productlot->create($user); + if (! $result) + { + $this->error = $productlot->error; + $this->errors = $productlot->errors; + $this->db->rollback(); + return -4; + } } } else diff --git a/htdocs/product/stock/class/productlot.class.php b/htdocs/product/stock/class/productlot.class.php new file mode 100644 index 00000000000..26970a68e85 --- /dev/null +++ b/htdocs/product/stock/class/productlot.class.php @@ -0,0 +1,671 @@ + + * Copyright (C) 2014 Juanjo Menent + * Copyright (C) 2015 Florian Henry + * Copyright (C) 2015 Raphaël Doursenaud + * Copyright (C) ---Put here your own copyright and developer email--- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file stock/productlot.class.php + * \ingroup stock + * \brief This file is an example for a CRUD class file (Create/Read/Update/Delete) + * Put some comments here + */ + +// Put here all includes required by your class file +require_once DOL_DOCUMENT_ROOT . '/core/class/commonobject.class.php'; +//require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php'; +//require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php'; + +/** + * Class Productlot + * + * Put here description of your class + * @see CommonObject + */ +class Productlot extends CommonObject +{ + /** + * @var string Id to identify managed objects + */ + public $element = 'productlot'; + /** + * @var string Name of table without prefix where object is stored + */ + public $table_element = 'product_lot'; + + /** + * @var ProductlotLine[] Lines + */ + public $lines = array(); + + /** + */ + + public $tms = ''; + public $fk_product; + public $batch; + public $eatby = ''; + public $sellby = ''; + public $note_public; + public $note_private; + public $qty; + public $import_key; + + /** + */ + + + /** + * Constructor + * + * @param DoliDb $db Database handler + */ + public function __construct(DoliDB $db) + { + $this->db = $db; + } + + /** + * Create object into database + * + * @param User $user User that creates + * @param bool $notrigger false=launch triggers after, true=disable triggers + * + * @return int <0 if KO, Id of created object if OK + */ + public function create(User $user, $notrigger = false) + { + dol_syslog(__METHOD__, LOG_DEBUG); + + $error = 0; + + // Clean parameters + + if (isset($this->fk_product)) { + $this->fk_product = trim($this->fk_product); + } + if (isset($this->batch)) { + $this->batch = trim($this->batch); + } + if (isset($this->note_public)) { + $this->note_public = trim($this->note_public); + } + if (isset($this->note_private)) { + $this->note_private = trim($this->note_private); + } + if (isset($this->qty)) { + $this->qty = trim($this->qty); + } + if (isset($this->import_key)) { + $this->import_key = trim($this->import_key); + } + + + + // Check parameters + // Put here code to add control on parameters values + + // Insert request + $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . $this->table_element . '('; + + $sql.= 'fk_product,'; + $sql.= 'batch,'; + $sql.= 'eatby,'; + $sql.= 'sellby,'; + $sql.= 'note_public,'; + $sql.= 'note_private,'; + $sql.= 'qty'; + $sql.= 'import_key'; + + + $sql .= ') VALUES ('; + + $sql .= ' '.(! isset($this->fk_product)?'NULL':$this->fk_product).','; + $sql .= ' '.(! isset($this->batch)?'NULL':"'".$this->db->escape($this->batch)."'").','; + $sql .= ' '.(! isset($this->eatby) || dol_strlen($this->eatby)==0?'NULL':"'".$this->db->idate($this->eatby)."'").','; + $sql .= ' '.(! isset($this->sellby) || dol_strlen($this->sellby)==0?'NULL':"'".$this->db->idate($this->sellby)."'").','; + $sql .= ' '.(! isset($this->note_public)?'NULL':"'".$this->db->escape($this->note_public)."'").','; + $sql .= ' '.(! isset($this->note_private)?'NULL':"'".$this->db->escape($this->note_private)."'").','; + $sql .= ' '.(! isset($this->qty)?'NULL':"'".$this->qty."'").','; + $sql .= ' '.(! isset($this->import_key)?'NULL':"'".$this->db->escape($this->import_key)."'"); + + + $sql .= ')'; + + $this->db->begin(); + + $resql = $this->db->query($sql); + if (!$resql) { + $error ++; + $this->errors[] = 'Error ' . $this->db->lasterror(); + dol_syslog(__METHOD__ . ' ' . join(',', $this->errors), LOG_ERR); + } + + if (!$error) { + $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . $this->table_element); + + if (!$notrigger) { + // Uncomment this and change MYOBJECT to your own tag if you + // want this action to call a trigger. + + //// Call triggers + //$result=$this->call_trigger('MYOBJECT_CREATE',$user); + //if ($result < 0) $error++; + //// End call triggers + } + } + + // Commit or rollback + if ($error) { + $this->db->rollback(); + + return - 1 * $error; + } else { + $this->db->commit(); + + return $this->id; + } + } + + /** + * Load object in memory from the database + * + * @param int $id Id object + * @param string $ref Ref + * + * @return int <0 if KO, 0 if not found, >0 if OK + */ + public function fetch($id, $ref = null) + { + dol_syslog(__METHOD__, LOG_DEBUG); + + $sql = 'SELECT'; + $sql .= ' t.rowid,'; + + $sql .= " t.tms,"; + $sql .= " t.fk_product,"; + $sql .= " t.batch,"; + $sql .= " t.eatby,"; + $sql .= " t.sellby,"; + $sql .= " t.note_public,"; + $sql .= " t.note_private,"; + $sql .= " t.qty,"; + $sql .= " t.import_key"; + + + $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element . ' as t'; + if (null !== $ref) { + $sql .= ' WHERE t.ref = ' . '\'' . $ref . '\''; + } else { + $sql .= ' WHERE t.rowid = ' . $id; + } + + $resql = $this->db->query($sql); + if ($resql) { + $numrows = $this->db->num_rows($resql); + if ($numrows) { + $obj = $this->db->fetch_object($resql); + + $this->id = $obj->rowid; + + $this->tms = $this->db->jdate($obj->tms); + $this->fk_product = $obj->fk_product; + $this->batch = $obj->batch; + $this->eatby = $this->db->jdate($obj->eatby); + $this->sellby = $this->db->jdate($obj->sellby); + $this->note_public = $obj->note_public; + $this->note_private = $obj->note_private; + $this->qty = $obj->qty; + $this->import_key = $obj->import_key; + + + } + $this->db->free($resql); + + if ($numrows) { + return 1; + } else { + return 0; + } + } else { + $this->errors[] = 'Error ' . $this->db->lasterror(); + dol_syslog(__METHOD__ . ' ' . join(',', $this->errors), LOG_ERR); + + return - 1; + } + } + + /** + * Load object in memory from the database + * + * @param string $sortorder Sort Order + * @param string $sortfield Sort field + * @param int $limit offset limit + * @param int $offset offset limit + * @param array $filter filter array + * @param string $filtermode filter mode (AND or OR) + * + * @return int <0 if KO, >0 if OK + */ + public function fetchAll($sortorder='', $sortfield='', $limit=0, $offset=0, array $filter = array(), $filtermode='AND') + { + dol_syslog(__METHOD__, LOG_DEBUG); + + $sql = 'SELECT'; + $sql .= ' t.rowid,'; + + $sql .= " t.tms,"; + $sql .= " t.fk_product,"; + $sql .= " t.batch,"; + $sql .= " t.eatby,"; + $sql .= " t.sellby,"; + $sql .= " t.note_public,"; + $sql .= " t.note_private,"; + $sql .= " t.qty,"; + $sql .= " t.import_key"; + + + $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element. ' as t'; + + // Manage filter + $sqlwhere = array(); + if (count($filter) > 0) { + foreach ($filter as $key => $value) { + $sqlwhere [] = $key . ' LIKE \'%' . $this->db->escape($value) . '%\''; + } + } + if (count($sqlwhere) > 0) { + $sql .= ' WHERE ' . implode(' '.$filtermode.' ', $sqlwhere); + } + + if (!empty($sortfield)) { + $sql .= $this->db->order($sortfield,$sortorder); + } + if (!empty($limit)) { + $sql .= ' ' . $this->db->plimit($limit + 1, $offset); + } + $this->lines = array(); + + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + + while ($obj = $this->db->fetch_object($resql)) { + $line = new ProductlotLine(); + + $line->id = $obj->rowid; + + $line->tms = $this->db->jdate($obj->tms); + $line->fk_product = $obj->fk_product; + $line->batch = $obj->batch; + $line->eatby = $this->db->jdate($obj->eatby); + $line->sellby = $this->db->jdate($obj->sellby); + $line->note_public = $obj->note_public; + $line->note_private = $obj->note_private; + $line->qty = $obj->qty; + $line->import_key = $obj->import_key; + + + + $this->lines[$line->id] = $line; + } + $this->db->free($resql); + + return $num; + } else { + $this->errors[] = 'Error ' . $this->db->lasterror(); + dol_syslog(__METHOD__ . ' ' . join(',', $this->errors), LOG_ERR); + + return - 1; + } + } + + /** + * Update object into database + * + * @param User $user User that modifies + * @param bool $notrigger false=launch triggers after, true=disable triggers + * + * @return int <0 if KO, >0 if OK + */ + public function update(User $user, $notrigger = false) + { + $error = 0; + + dol_syslog(__METHOD__, LOG_DEBUG); + + // Clean parameters + + if (isset($this->fk_product)) { + $this->fk_product = trim($this->fk_product); + } + if (isset($this->batch)) { + $this->batch = trim($this->batch); + } + if (isset($this->note_public)) { + $this->note_public = trim($this->note_public); + } + if (isset($this->note_private)) { + $this->note_private = trim($this->note_private); + } + if (isset($this->qty)) { + $this->qty = trim($this->qty); + } + if (isset($this->import_key)) { + $this->import_key = trim($this->import_key); + } + + + + // Check parameters + // Put here code to add a control on parameters values + + // Update request + $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element . ' SET'; + + $sql .= ' tms = '.(dol_strlen($this->tms) != 0 ? "'".$this->db->idate($this->tms)."'" : "'".$this->db->idate(dol_now())."'").','; + $sql .= ' fk_product = '.(isset($this->fk_product)?$this->fk_product:"null").','; + $sql .= ' batch = '.(isset($this->batch)?"'".$this->db->escape($this->batch)."'":"null").','; + $sql .= ' eatby = '.(! isset($this->eatby) || dol_strlen($this->eatby) != 0 ? "'".$this->db->idate($this->eatby)."'" : 'null').','; + $sql .= ' sellby = '.(! isset($this->sellby) || dol_strlen($this->sellby) != 0 ? "'".$this->db->idate($this->sellby)."'" : 'null').','; + $sql .= ' note_public = '.(isset($this->note_public)?"'".$this->db->escape($this->note_public)."'":"null").','; + $sql .= ' note_private = '.(isset($this->note_private)?"'".$this->db->escape($this->note_private)."'":"null").','; + $sql .= ' qty = '.(isset($this->qty)?$this->qty:"null").','; + $sql .= ' import_key = '.(isset($this->import_key)?"'".$this->db->escape($this->import_key)."'":"null"); + + + $sql .= ' WHERE rowid=' . $this->id; + + $this->db->begin(); + + $resql = $this->db->query($sql); + if (!$resql) { + $error ++; + $this->errors[] = 'Error ' . $this->db->lasterror(); + dol_syslog(__METHOD__ . ' ' . join(',', $this->errors), LOG_ERR); + } + + if (!$error && !$notrigger) { + // Uncomment this and change MYOBJECT to your own tag if you + // want this action calls a trigger. + + //// Call triggers + //$result=$this->call_trigger('MYOBJECT_MODIFY',$user); + //if ($result < 0) { $error++; //Do also what you must do to rollback action if trigger fail} + //// End call triggers + } + + // Commit or rollback + if ($error) { + $this->db->rollback(); + + return - 1 * $error; + } else { + $this->db->commit(); + + return 1; + } + } + + /** + * Delete object in database + * + * @param User $user User that deletes + * @param bool $notrigger false=launch triggers after, true=disable triggers + * + * @return int <0 if KO, >0 if OK + */ + public function delete(User $user, $notrigger = false) + { + dol_syslog(__METHOD__, LOG_DEBUG); + + $error = 0; + + $this->db->begin(); + + if (!$error) { + if (!$notrigger) { + // Uncomment this and change MYOBJECT to your own tag if you + // want this action calls a trigger. + + //// Call triggers + //$result=$this->call_trigger('MYOBJECT_DELETE',$user); + //if ($result < 0) { $error++; //Do also what you must do to rollback action if trigger fail} + //// End call triggers + } + } + + if (!$error) { + $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . $this->table_element; + $sql .= ' WHERE rowid=' . $this->id; + + $resql = $this->db->query($sql); + if (!$resql) { + $error ++; + $this->errors[] = 'Error ' . $this->db->lasterror(); + dol_syslog(__METHOD__ . ' ' . join(',', $this->errors), LOG_ERR); + } + } + + // Commit or rollback + if ($error) { + $this->db->rollback(); + + return - 1 * $error; + } else { + $this->db->commit(); + + return 1; + } + } + + /** + * Load an object from its id and create a new one in database + * + * @param int $fromid Id of object to clone + * + * @return int New id of clone + */ + public function createFromClone($fromid) + { + dol_syslog(__METHOD__, LOG_DEBUG); + + global $user; + $error = 0; + $object = new Productlot($this->db); + + $this->db->begin(); + + // Load source object + $object->fetch($fromid); + // Reset object + $object->id = 0; + + // Clear fields + // ... + + // Create clone + $result = $object->create($user); + + // Other options + if ($result < 0) { + $error ++; + $this->errors = $object->errors; + dol_syslog(__METHOD__ . ' ' . join(',', $this->errors), LOG_ERR); + } + + // End + if (!$error) { + $this->db->commit(); + + return $object->id; + } else { + $this->db->rollback(); + + return - 1; + } + } + + /** + * Return a link to the user card (with optionaly the picto) + * Use this->id,this->lastname, this->firstname + * + * @param int $withpicto Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto) + * @param string $option On what the link point to + * @param integer $notooltip 1=Disable tooltip + * @param int $maxlen Max length of visible user name + * @param string $morecss Add more css on link + * @return string String with URL + */ + function getNomUrl($withpicto=0, $option='', $notooltip=0, $maxlen=24, $morecss='') + { + global $langs, $conf, $db; + global $dolibarr_main_authentication, $dolibarr_main_demo; + global $menumanager; + + + $result = ''; + $companylink = ''; + + $label = '' . $langs->trans("MyModule") . ''; + $label.= '
'; + $label.= '' . $langs->trans('Ref') . ': ' . $this->ref; + + $link = 'ref . $linkend; + return $result; + } + + /** + * Retourne le libelle du status d'un user (actif, inactif) + * + * @param int $mode 0=libelle long, 1=libelle court, 2=Picto + Libelle court, 3=Picto, 4=Picto + Libelle long, 5=Libelle court + Picto + * @return string Label of status + */ + function getLibStatut($mode=0) + { + return $this->LibStatut($this->status,$mode); + } + + /** + * Renvoi le libelle d'un status donne + * + * @param int $status Id status + * @param int $mode 0=libelle long, 1=libelle court, 2=Picto + Libelle court, 3=Picto, 4=Picto + Libelle long, 5=Libelle court + Picto + * @return string Label of status + */ + function LibStatut($status,$mode=0) + { + global $langs; + + if ($mode == 0) + { + $prefix=''; + if ($status == 1) return $langs->trans('Enabled'); + if ($status == 0) return $langs->trans('Disabled'); + } + if ($mode == 1) + { + if ($status == 1) return $langs->trans('Enabled'); + if ($status == 0) return $langs->trans('Disabled'); + } + if ($mode == 2) + { + if ($status == 1) return img_picto($langs->trans('Enabled'),'statut4').' '.$langs->trans('Enabled'); + if ($status == 0) return img_picto($langs->trans('Disabled'),'statut5').' '.$langs->trans('Disabled'); + } + if ($mode == 3) + { + if ($status == 1) return img_picto($langs->trans('Enabled'),'statut4'); + if ($status == 0) return img_picto($langs->trans('Disabled'),'statut5'); + } + if ($mode == 4) + { + if ($status == 1) return img_picto($langs->trans('Enabled'),'statut4').' '.$langs->trans('Enabled'); + if ($status == 0) return img_picto($langs->trans('Disabled'),'statut5').' '.$langs->trans('Disabled'); + } + if ($mode == 5) + { + if ($status == 1) return $langs->trans('Enabled').' '.img_picto($langs->trans('Enabled'),'statut4'); + if ($status == 0) return $langs->trans('Disabled').' '.img_picto($langs->trans('Disabled'),'statut5'); + } + } + + + /** + * Initialise object with example values + * Id must be 0 if object instance is a specimen + * + * @return void + */ + public function initAsSpecimen() + { + $this->id = 0; + + $this->tms = ''; + $this->fk_product = ''; + $this->batch = ''; + $this->eatby = ''; + $this->sellby = ''; + $this->note_public = ''; + $this->note_private = ''; + $this->qty = ''; + $this->import_key = ''; + + + } + +} + +/** + * Class ProductlotLine + */ +class ProductlotLine +{ + /** + * @var int ID + */ + public $id; + /** + * @var mixed Sample line property 1 + */ + + public $tms = ''; + public $fk_product; + public $batch; + public $eatby = ''; + public $sellby = ''; + public $note_public; + public $note_private; + public $qty; + public $import_key; + + /** + * @var mixed Sample line property 2 + */ + +} diff --git a/htdocs/product/stock/productlot_card.php b/htdocs/product/stock/productlot_card.php new file mode 100644 index 00000000000..2e8620797c7 --- /dev/null +++ b/htdocs/product/stock/productlot_card.php @@ -0,0 +1,376 @@ + + * Copyright (C) ---Put here your own copyright and developer email--- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file stock/productlot_card.php + * \ingroup stock + * \brief This file is an example of a php page + * Initialy built by build_class_from_table on 2016-05-17 10:33 + */ + +//if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); +//if (! defined('NOREQUIREDB')) define('NOREQUIREDB','1'); +//if (! defined('NOREQUIRESOC')) define('NOREQUIRESOC','1'); +//if (! defined('NOREQUIRETRAN')) define('NOREQUIRETRAN','1'); +//if (! defined('NOCSRFCHECK')) define('NOCSRFCHECK','1'); // Do not check anti CSRF attack test +//if (! defined('NOSTYLECHECK')) define('NOSTYLECHECK','1'); // Do not check style html tag into posted data +//if (! defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL','1'); // Do not check anti POST attack test +//if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU','1'); // If there is no need to load and show top and left menu +//if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); // If we don't need to load the html.form.class.php +//if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX','1'); +//if (! defined("NOLOGIN")) define("NOLOGIN",'1'); // If this page is public (can be called outside logged session) + +// Change this following line to use the correct relative path (../, ../../, etc) +$res=0; +if (! $res && file_exists("../main.inc.php")) $res=@include '../main.inc.php'; // to work if your module directory is into dolibarr root htdocs directory +if (! $res && file_exists("../../main.inc.php")) $res=@include '../../main.inc.php'; // to work if your module directory is into a subdir of root htdocs directory +if (! $res && file_exists("../../../dolibarr/htdocs/main.inc.php")) $res=@include '../../../dolibarr/htdocs/main.inc.php'; // Used on dev env only +if (! $res && file_exists("../../../../dolibarr/htdocs/main.inc.php")) $res=@include '../../../../dolibarr/htdocs/main.inc.php'; // Used on dev env only +if (! $res) die("Include of main fails"); +// Change this following line to use the correct relative path from htdocs +include_once(DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'); +dol_include_once('/stock/class/productlot.class.php'); + +// Load traductions files requiredby by page +$langs->load("stock"); +$langs->load("other"); + +// Get parameters +$id = GETPOST('id','int'); +$action = GETPOST('action','alpha'); +$backtopage = GETPOST('backtopage'); +$myparam = GETPOST('myparam','alpha'); + + +$search_fk_product=GETPOST('search_fk_product','int'); +$search_batch=GETPOST('search_batch','alpha'); +$search_note_public=GETPOST('search_note_public','alpha'); +$search_note_private=GETPOST('search_note_private','alpha'); +$search_qty=GETPOST('search_qty','alpha'); +$search_import_key=GETPOST('search_import_key','alpha'); + + + +// Protection if external user +if ($user->societe_id > 0) +{ + //accessforbidden(); +} + +if (empty($action) && empty($id) && empty($ref)) $action='list'; + +// Load object if id or ref is provided as parameter +$object=new Productlot($db); +if (($id > 0 || ! empty($ref)) && $action != 'add') +{ + $result=$object->fetch($id,$ref); + if ($result < 0) dol_print_error($db); +} + +// Initialize technical object to manage hooks of modules. Note that conf->hooks_modules contains array array +$hookmanager->initHooks(array('productlot')); +$extrafields = new ExtraFields($db); + + + +/******************************************************************* +* ACTIONS +* +* Put here all code to do according to value of "action" parameter +********************************************************************/ + +$parameters=array(); +$reshook=$hookmanager->executeHooks('doActions',$parameters,$object,$action); // Note that $action and $object may have been modified by some hooks +if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); + +if (empty($reshook)) +{ + // Action to add record + if ($action == 'add') + { + if (GETPOST('cancel')) + { + $urltogo=$backtopage?$backtopage:dol_buildpath('/stock/list.php',1); + header("Location: ".$urltogo); + exit; + } + + $error=0; + + /* object_prop_getpost_prop */ + + $object->fk_product=GETPOST('fk_product','int'); + $object->batch=GETPOST('batch','alpha'); + $object->note_public=GETPOST('note_public','alpha'); + $object->note_private=GETPOST('note_private','alpha'); + $object->qty=GETPOST('qty','alpha'); + $object->import_key=GETPOST('import_key','alpha'); + + + + if (empty($object->ref)) + { + $error++; + setEventMessages($langs->trans("ErrorFieldRequired",$langs->transnoentitiesnoconv("Ref")), null, 'errors'); + } + + if (! $error) + { + $result=$object->create($user); + if ($result > 0) + { + // Creation OK + $urltogo=$backtopage?$backtopage:dol_buildpath('/stock/list.php',1); + header("Location: ".$urltogo); + exit; + } + { + // Creation KO + if (! empty($object->errors)) setEventMessages(null, $object->errors, 'errors'); + else setEventMessages($object->error, null, 'errors'); + $action='create'; + } + } + else + { + $action='create'; + } + } + + // Cancel + if ($action == 'update' && GETPOST('cancel')) $action='view'; + + // Action to update record + if ($action == 'update' && ! GETPOST('cancel')) + { + $error=0; + + + $object->fk_product=GETPOST('fk_product','int'); + $object->batch=GETPOST('batch','alpha'); + $object->note_public=GETPOST('note_public','alpha'); + $object->note_private=GETPOST('note_private','alpha'); + $object->qty=GETPOST('qty','alpha'); + $object->import_key=GETPOST('import_key','alpha'); + + + + if (empty($object->ref)) + { + $error++; + setEventMessages($langs->transnoentitiesnoconv("ErrorFieldRequired",$langs->transnoentitiesnoconv("Ref")), null, 'errors'); + } + + if (! $error) + { + $result=$object->update($user); + if ($result > 0) + { + $action='view'; + } + else + { + // Creation KO + if (! empty($object->errors)) setEventMessages(null, $object->errors, 'errors'); + else setEventMessages($object->error, null, 'errors'); + $action='edit'; + } + } + else + { + $action='edit'; + } + } + + // Action to delete + if ($action == 'confirm_delete') + { + $result=$object->delete($user); + if ($result > 0) + { + // Delete OK + setEventMessages("RecordDeleted", null, 'mesgs'); + header("Location: ".dol_buildpath('/stock/list.php',1)); + exit; + } + else + { + if (! empty($object->errors)) setEventMessages(null, $object->errors, 'errors'); + else setEventMessages($object->error, null, 'errors'); + } + } +} + + + + +/*************************************************** +* VIEW +* +* Put here all code to build page +****************************************************/ + +llxHeader('','MyPageName',''); + +$form=new Form($db); + + +// Put here content of your page + +// Example : Adding jquery code +print ''; + + +// Part to create +if ($action == 'create') +{ + print load_fiche_titre($langs->trans("NewMyModule")); + + print '
'; + print ''; + print ''; + + dol_fiche_head(); + + print '
'.$langs->trans("Ref").'
'.$langs->trans("Ref").'
'.$langs->trans("LocationSummary").'
'.$langs->trans('Country').''; + print '
'.$langs->trans('Country').''; print $form->select_country((!empty($object->country_id)?$object->country_id:$mysoc->country_code),'country_id'); if ($user->admin) print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"),1); print '
' . $langs->trans( "Categories" ) . '
' . $langs->trans("CustomersCategoriesShort") . ''; print $form->showCategories( $object->id, 'customer', 1 ); print "
' . $langs->trans("Categories") . '
' . $langs->trans("SuppliersCategoriesShort") . ''; print $form->showCategories($object->id, 'supplier', 1); print "
'."\n"; + // print ''; + // +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + + print '
'.$langs->trans("Label").'
'.$langs->trans("Fieldfk_product").'
'.$langs->trans("Fieldbatch").'
'.$langs->trans("Fieldnote_public").'
'.$langs->trans("Fieldnote_private").'
'.$langs->trans("Fieldqty").'
'.$langs->trans("Fieldimport_key").'
'."\n"; + + dol_fiche_end(); + + print '
 
'; + + print ''; +} + + + +// Part to edit record +if (($id || $ref) && $action == 'edit') +{ + print load_fiche_titre($langs->trans("MyModule")); + + print '
'; + print ''; + print ''; + print ''; + + dol_fiche_head(); + + print ''."\n"; + // print ''; + // +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + + print '
'.$langs->trans("Label").'
'.$langs->trans("Fieldfk_product").'
'.$langs->trans("Fieldbatch").'
'.$langs->trans("Fieldnote_public").'
'.$langs->trans("Fieldnote_private").'
'.$langs->trans("Fieldqty").'
'.$langs->trans("Fieldimport_key").'
'; + + dol_fiche_end(); + + print '
'; + print '   '; + print '
'; + + print '
'; +} + + + +// Part to show record +if ($id && (empty($action) || $action == 'view' || $action == 'delete')) +{ + print load_fiche_titre($langs->trans("MyModule")); + + dol_fiche_head(); + + if ($action == 'delete') { + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"] . '?id=' . $object->id, $langs->trans('DeleteMyOjbect'), $langs->trans('ConfirmDeleteMyObject'), 'confirm_delete', '', 0, 1); + print $formconfirm; + } + + print ''."\n"; + // print ''; + // +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + + print '
'.$langs->trans("Label").'
'.$langs->trans("Fieldfk_product").'$object->fk_product
'.$langs->trans("Fieldbatch").'$object->batch
'.$langs->trans("Fieldnote_public").'$object->note_public
'.$langs->trans("Fieldnote_private").'$object->note_private
'.$langs->trans("Fieldqty").'$object->qty
'.$langs->trans("Fieldimport_key").'$object->import_key
'; + + dol_fiche_end(); + + + // Buttons + print '
'."\n"; + + + // Example 2 : Adding links to objects + //$somethingshown=$form->showLinkedObjectBlock($object); + //$linktoelem = $form->showLinkToObjectBlock($object); + //if ($linktoelem) print '
'.$linktoelem; + +} + + +// End of page +llxFooter(); +$db->close(); diff --git a/htdocs/product/stock/productlot_list.php b/htdocs/product/stock/productlot_list.php new file mode 100644 index 00000000000..ded98827e31 --- /dev/null +++ b/htdocs/product/stock/productlot_list.php @@ -0,0 +1,549 @@ + + * Copyright (C) ---Put here your own copyright and developer email--- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file stock/productlot_list.php + * \ingroup stock + * \brief This file is an example of a php page + * Initialy built by build_class_from_table on 2016-05-17 10:33 + */ + +//if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); +//if (! defined('NOREQUIREDB')) define('NOREQUIREDB','1'); +//if (! defined('NOREQUIRESOC')) define('NOREQUIRESOC','1'); +//if (! defined('NOREQUIRETRAN')) define('NOREQUIRETRAN','1'); +//if (! defined('NOCSRFCHECK')) define('NOCSRFCHECK','1'); // Do not check anti CSRF attack test +//if (! defined('NOSTYLECHECK')) define('NOSTYLECHECK','1'); // Do not check style html tag into posted data +//if (! defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL','1'); // Do not check anti POST attack test +//if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU','1'); // If there is no need to load and show top and left menu +//if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); // If we don't need to load the html.form.class.php +//if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX','1'); +//if (! defined("NOLOGIN")) define("NOLOGIN",'1'); // If this page is public (can be called outside logged session) + +// Change this following line to use the correct relative path (../, ../../, etc) +$res=0; +if (! $res && file_exists("../main.inc.php")) $res=@include '../main.inc.php'; // to work if your module directory is into dolibarr root htdocs directory +if (! $res && file_exists("../../main.inc.php")) $res=@include '../../main.inc.php'; // to work if your module directory is into a subdir of root htdocs directory +if (! $res && file_exists("../../../dolibarr/htdocs/main.inc.php")) $res=@include '../../../dolibarr/htdocs/main.inc.php'; // Used on dev env only +if (! $res && file_exists("../../../../dolibarr/htdocs/main.inc.php")) $res=@include '../../../../dolibarr/htdocs/main.inc.php'; // Used on dev env only +if (! $res) die("Include of main fails"); +// Change this following line to use the correct relative path from htdocs +require_once(DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'); +require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +dol_include_once('/stock/class/productlot.class.php'); + +// Load traductions files requiredby by page +$langs->load("stock"); +$langs->load("other"); + +// Get parameters +$id = GETPOST('id','int'); +$action = GETPOST('action','alpha'); +$backtopage = GETPOST('backtopage'); +$myparam = GETPOST('myparam','alpha'); + + +$search_fk_product=GETPOST('search_fk_product','int'); +$search_batch=GETPOST('search_batch','alpha'); +$search_note_public=GETPOST('search_note_public','alpha'); +$search_note_private=GETPOST('search_note_private','alpha'); +$search_qty=GETPOST('search_qty','alpha'); +$search_import_key=GETPOST('search_import_key','alpha'); + + +$search_myfield=GETPOST('search_myfield'); +$optioncss = GETPOST('optioncss','alpha'); + +// Load variable for pagination +$limit = GETPOST("limit")?GETPOST("limit","int"):$conf->liste_limit; +$sortfield = GETPOST('sortfield','alpha'); +$sortorder = GETPOST('sortorder','alpha'); +$page = GETPOST('page','int'); +if ($page == -1) { $page = 0; } +$offset = $limit * $page; +$pageprev = $page - 1; +$pagenext = $page + 1; +if (! $sortfield) $sortfield="t.rowid"; // Set here default search field +if (! $sortorder) $sortorder="ASC"; + +// Protection if external user +$socid=0; +if ($user->societe_id > 0) +{ + $socid = $user->societe_id; + //accessforbidden(); +} + +// Initialize technical object to manage hooks. Note that conf->hooks_modules contains array +$hookmanager->initHooks(array('productlotlist')); +$extrafields = new ExtraFields($db); + +// fetch optionals attributes and labels +$extralabels = $extrafields->fetch_name_optionals_label('stock'); +$search_array_options=$extrafields->getOptionalsFromPost($extralabels,'','search_'); + +// Load object if id or ref is provided as parameter +$object=new Productlot($db); +if (($id > 0 || ! empty($ref)) && $action != 'add') +{ + $result=$object->fetch($id,$ref); + if ($result < 0) dol_print_error($db); +} + +// Definition of fields for list +$arrayfields=array( + +'t.fk_product'=>array('label'=>$langs->trans("Fieldfk_product"), 'checked'=>1), +'t.batch'=>array('label'=>$langs->trans("Fieldbatch"), 'checked'=>1), +'t.note_public'=>array('label'=>$langs->trans("Fieldnote_public"), 'checked'=>1), +'t.note_private'=>array('label'=>$langs->trans("Fieldnote_private"), 'checked'=>1), +'t.qty'=>array('label'=>$langs->trans("Fieldqty"), 'checked'=>1), +'t.import_key'=>array('label'=>$langs->trans("Fieldimport_key"), 'checked'=>1), + + + //'t.entity'=>array('label'=>$langs->trans("Entity"), 'checked'=>1, 'enabled'=>(! empty($conf->multicompany->enabled) && empty($conf->multicompany->transverse_mode))), + 't.datec'=>array('label'=>$langs->trans("DateCreation"), 'checked'=>0, 'position'=>500), + 't.tms'=>array('label'=>$langs->trans("DateModificationShort"), 'checked'=>0, 'position'=>500), + //'t.statut'=>array('label'=>$langs->trans("Status"), 'checked'=>1, 'position'=>1000), +); +// Extra fields +if (is_array($extrafields->attribute_label) && count($extrafields->attribute_label)) +{ + foreach($extrafields->attribute_label as $key => $val) + { + $arrayfields["ef.".$key]=array('label'=>$extrafields->attribute_label[$key], 'checked'=>$extrafields->attribute_list[$key], 'position'=>$extrafields->attribute_pos[$key], 'enabled'=>$extrafields->attribute_perms[$key]); + } +} + + + + +/******************************************************************* +* ACTIONS +* +* Put here all code to do according to value of "action" parameter +********************************************************************/ + +if (GETPOST('cancel')) { $action='list'; $massaction=''; } +if (! GETPOST('confirmmassaction')) { $massaction=''; } + +$parameters=array(); +$reshook=$hookmanager->executeHooks('doActions',$parameters,$object,$action); // Note that $action and $object may have been modified by some hooks +if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); + +include DOL_DOCUMENT_ROOT.'/core/actions_changeselectedfields.inc.php'; + +if (GETPOST("button_removefilter_x") || GETPOST("button_removefilter.x") ||GETPOST("button_removefilter")) // All test are required to be compatible with all browsers +{ + +$search_fk_product=''; +$search_batch=''; +$search_note_public=''; +$search_note_private=''; +$search_qty=''; +$search_import_key=''; + + + $search_date_creation=''; + $search_date_update=''; + $search_array_options=array(); +} + + +if (empty($reshook)) +{ + // Mass actions. Controls on number of lines checked + $maxformassaction=1000; + if (! empty($massaction) && count($toselect) < 1) + { + $error++; + setEventMessages($langs->trans("NoLineChecked"), null, "warnings"); + } + if (! $error && count($toselect) > $maxformassaction) + { + setEventMessages($langs->trans('TooManyRecordForMassAction',$maxformassaction), null, 'errors'); + $error++; + } + + // Action to delete + if ($action == 'confirm_delete') + { + $result=$object->delete($user); + if ($result > 0) + { + // Delete OK + setEventMessages("RecordDeleted", null, 'mesgs'); + header("Location: ".dol_buildpath('/stock/list.php',1)); + exit; + } + else + { + if (! empty($object->errors)) setEventMessages(null,$object->errors,'errors'); + else setEventMessages($object->error,null,'errors'); + } + } +} + + + + +/*************************************************** +* VIEW +* +* Put here all code to build page +****************************************************/ + +llxHeader('','MyPageName',''); + +$form=new Form($db); + +// Put here content of your page +$title = $langs->trans('MyModuleListTitle'); + +// Example : Adding jquery code +print ''; + + +$sql = "SELECT"; +$sql.= " t.rowid,"; + + $sql .= " t.tms,"; + $sql .= " t.fk_product,"; + $sql .= " t.batch,"; + $sql .= " t.eatby,"; + $sql .= " t.sellby,"; + $sql .= " t.note_public,"; + $sql .= " t.note_private,"; + $sql .= " t.qty,"; + $sql .= " t.import_key"; + + +// Add fields for extrafields +foreach ($extrafields->attribute_list as $key => $val) $sql.=",ef.".$key.' as options_'.$key; +// Add fields from hooks +$parameters=array(); +$reshook=$hookmanager->executeHooks('printFieldListSelect',$parameters); // Note that $action and $object may have been modified by hook +$sql.=$hookmanager->resPrint; +$sql.= " FROM ".MAIN_DB_PREFIX."product_lot as t"; +if (is_array($extrafields->attribute_label) && count($extrafields->attribute_label)) $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lot_extrafields as ef on (u.rowid = ef.fk_object)"; +$sql.= " WHERE 1 = 1"; +//$sql.= " WHERE u.entity IN (".getEntity('mytable',1).")"; + +if ($search_fk_product) $sql.= natural_search("fk_product",$search_fk_product); +if ($search_batch) $sql.= natural_search("batch",$search_batch); +if ($search_note_public) $sql.= natural_search("note_public",$search_note_public); +if ($search_note_private) $sql.= natural_search("note_private",$search_note_private); +if ($search_qty) $sql.= natural_search("qty",$search_qty); +if ($search_import_key) $sql.= natural_search("import_key",$search_import_key); + + +if ($sall) $sql.= natural_search(array_keys($fieldstosearchall), $sall); +// Add where from extra fields +foreach ($search_array_options as $key => $val) +{ + $crit=$val; + $tmpkey=preg_replace('/search_options_/','',$key); + $typ=$extrafields->attribute_type[$tmpkey]; + $mode=0; + if (in_array($typ, array('int','double'))) $mode=1; // Search on a numeric + if ($val && ( ($crit != '' && ! in_array($typ, array('select'))) || ! empty($crit))) + { + $sql .= natural_search('ef.'.$tmpkey, $crit, $mode); + } +} +// Add where from hooks +$parameters=array(); +$reshook=$hookmanager->executeHooks('printFieldListWhere',$parameters); // Note that $action and $object may have been modified by hook +$sql.=$hookmanager->resPrint; +$sql.=$db->order($sortfield,$sortorder); +//$sql.= $db->plimit($conf->liste_limit+1, $offset); + +// Count total nb of records +$nbtotalofrecords = 0; +if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) +{ + $result = $db->query($sql); + $nbtotalofrecords = $db->num_rows($result); +} + +$sql.= $db->plimit($limit+1, $offset); + + +dol_syslog($script_file, LOG_DEBUG); +$resql=$db->query($sql); +if ($resql) +{ + $num = $db->num_rows($resql); + + $params=''; + if ($limit > 0 && $limit != $conf->liste_limit) $param.='&limit='.$limit; + +if ($search_fk_product != '') $params.= '&search_fk_product='.urlencode($search_fk_product); +if ($search_batch != '') $params.= '&search_batch='.urlencode($search_batch); +if ($search_note_public != '') $params.= '&search_note_public='.urlencode($search_note_public); +if ($search_note_private != '') $params.= '&search_note_private='.urlencode($search_note_private); +if ($search_qty != '') $params.= '&search_qty='.urlencode($search_qty); +if ($search_import_key != '') $params.= '&search_import_key='.urlencode($search_import_key); + + + if ($optioncss != '') $param.='&optioncss='.$optioncss; + // Add $param from extra fields + foreach ($search_array_options as $key => $val) + { + $crit=$val; + $tmpkey=preg_replace('/search_options_/','',$key); + if ($val != '') $param.='&search_options_'.$tmpkey.'='.urlencode($val); + } + + print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $params, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'title_companies', 0, '', '', $limit); + + + print '
'; + if ($optioncss != '') print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + if ($sall) + { + foreach($fieldstosearchall as $key => $val) $fieldstosearchall[$key]=$langs->trans($val); + print $langs->trans("FilterOnInto", $all) . join(', ',$fieldstosearchall); + } + + $moreforfilter = ''; + $moreforfilter.='
'; + $moreforfilter.= $langs->trans('MyFilter') . ': '; + $moreforfilter.= '
'; + + if (! empty($moreforfilter)) + { + print '
'; + print $moreforfilter; + $parameters=array(); + $reshook=$hookmanager->executeHooks('printFieldPreListTitle',$parameters); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; + print '
'; + } + + $varpage=empty($contextpage)?$_SERVER["PHP_SELF"]:$contextpage; + $selectedfields=$form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // This also change content of $arrayfields + + print ''; + + // Fields title + print ''; + if (! empty($arrayfields['t.field1']['checked'])) print_liste_field_titre($arrayfields['t.field1']['label'],$_SERVER['PHP_SELF'],'t.field1','',$param,'',$sortfield,$sortorder); + if (! empty($arrayfields['t.field2']['checked'])) print_liste_field_titre($arrayfields['t.field2']['label'],$_SERVER['PHP_SELF'],'t.field2','',$param,'',$sortfield,$sortorder); + // Extra fields + if (is_array($extrafields->attribute_label) && count($extrafields->attribute_label)) + { + foreach($extrafields->attribute_label as $key => $val) + { + if (! empty($arrayfields["ef.".$key]['checked'])) + { + $align=$extrafields->getAlignFlag($key); + print_liste_field_titre($extralabels[$key],$_SERVER["PHP_SELF"],"ef.".$key,"",$param,($align?'align="'.$align.'"':''),$sortfield,$sortorder); + } + } + } + // Hook fields + $parameters=array('arrayfields'=>$arrayfields); + $reshook=$hookmanager->executeHooks('printFieldListTitle',$parameters); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; + if (! empty($arrayfields['t.datec']['checked'])) print_liste_field_titre($arrayfields['t.datec']['label'],$_SERVER["PHP_SELF"],"t.datec","",$param,'align="center" class="nowrap"',$sortfield,$sortorder); + if (! empty($arrayfields['t.tms']['checked'])) print_liste_field_titre($arrayfields['t.tms']['label'],$_SERVER["PHP_SELF"],"t.tms","",$param,'align="center" class="nowrap"',$sortfield,$sortorder); + //if (! empty($arrayfields['t.status']['checked'])) print_liste_field_titre($langs->trans("Status"),$_SERVER["PHP_SELF"],"t.status","",$param,'align="center"',$sortfield,$sortorder); + print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"],"",'','','align="right"',$sortfield,$sortorder,'maxwidthsearch '); + print ''."\n"; + + // Fields title search + print ''; + +if (! empty($arrayfields['t.fk_product']['checked'])) print ''; +if (! empty($arrayfields['t.batch']['checked'])) print ''; +if (! empty($arrayfields['t.note_public']['checked'])) print ''; +if (! empty($arrayfields['t.note_private']['checked'])) print ''; +if (! empty($arrayfields['t.qty']['checked'])) print ''; +if (! empty($arrayfields['t.import_key']['checked'])) print ''; + + + // Extra fields + if (is_array($extrafields->attribute_label) && count($extrafields->attribute_label)) + { + foreach($extrafields->attribute_label as $key => $val) + { + if (! empty($arrayfields["ef.".$key]['checked'])) + { + $align=$extrafields->getAlignFlag($key); + $typeofextrafield=$extrafields->attribute_type[$key]; + print ''; + } + } + } + // Fields from hook + $parameters=array('arrayfields'=>$arrayfields); + $reshook=$hookmanager->executeHooks('printFieldListOption',$parameters); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; + if (! empty($arrayfields['t.datec']['checked'])) + { + // Date creation + print ''; + } + if (! empty($arrayfields['t.tms']['checked'])) + { + // Date modification + print ''; + } + /*if (! empty($arrayfields['u.statut']['checked'])) + { + // Status + print ''; + }*/ + // Action column + print ''; + print ''."\n"; + + + $i=0; + $var=true; + $totalarray=array(); + while ($i < min($num, $limit)) + { + $obj = $db->fetch_object($resql); + if ($obj) + { + // You can use here results + print ''; + if (! empty($arrayfields['t.field1']['checked'])) + { + print ''; + if (! $i) $totalarray['nbfield']++; + } + if (! empty($arrayfields['t.field2']['checked'])) + { + print ''; + if (! $i) $totalarray['nbfield']++; + } + // Extra fields + if (is_array($extrafields->attribute_label) && count($extrafields->attribute_label)) + { + foreach($extrafields->attribute_label as $key => $val) + { + if (! empty($arrayfields["ef.".$key]['checked'])) + { + print 'getAlignFlag($key); + if ($align) print ' align="'.$align.'"'; + print '>'; + $tmpkey='options_'.$key; + print $extrafields->showOutputField($key, $obj->$tmpkey, '', 1); + print ''; + if (! $i) $totalarray['nbfield']++; + } + } + } + // Fields from hook + $parameters=array('arrayfields'=>$arrayfields, 'obj'=>$obj); + $reshook=$hookmanager->executeHooks('printFieldListValue',$parameters); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; + // Date creation + if (! empty($arrayfields['t.datec']['checked'])) + { + print ''; + if (! $i) $totalarray['nbfield']++; + } + // Date modification + if (! empty($arrayfields['t.tms']['checked'])) + { + print ''; + if (! $i) $totalarray['nbfield']++; + } + // Status + /* + if (! empty($arrayfields['u.statut']['checked'])) + { + $userstatic->statut=$obj->statut; + print ''; + }*/ + + // Action column + print ''; + if (! $i) $totalarray['nbfield']++; + + print ''; + } + $i++; + } + + $db->free($resql); + + $parameters=array('sql' => $sql); + $reshook=$hookmanager->executeHooks('printFieldListFooter',$parameters); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; + + print "
'; + if (in_array($typeofextrafield, array('varchar', 'int', 'double', 'select'))) + { + $crit=$val; + $tmpkey=preg_replace('/search_options_/','',$key); + $searchclass=''; + if (in_array($typeofextrafield, array('varchar', 'select'))) $searchclass='searchstring'; + if (in_array($typeofextrafield, array('int', 'double'))) $searchclass='searchnum'; + print ''; + } + print ''; + print ''; + print ''; + print $form->selectarray('search_statut', array('-1'=>'','0'=>$langs->trans('Disabled'),'1'=>$langs->trans('Enabled')),$search_statut); + print ''; + $searchpitco=$form->showFilterAndCheckAddButtons(0); + print $searchpitco; + print '
'.$obj->field1.''.$obj->field2.''; + print dol_print_date($db->jdate($obj->date_creation), 'dayhour'); + print ''; + print dol_print_date($db->jdate($obj->date_update), 'dayhour'); + print ''.$userstatic->getLibStatut(3).'
\n"; + print "
\n"; + + $db->free($result); +} +else +{ + $error++; + dol_print_error($db); +} + + +// End of page +llxFooter(); +$db->close(); From 668df6cb7d1c13da37c124e282b6bebb1473e7c9 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 10:51:13 +0200 Subject: [PATCH 19/61] Debug resource module --- .../resource/class/html.formresource.class.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/htdocs/resource/class/html.formresource.class.php b/htdocs/resource/class/html.formresource.class.php index 82b0ebe7d1a..a086f3c7e07 100644 --- a/htdocs/resource/class/html.formresource.class.php +++ b/htdocs/resource/class/html.formresource.class.php @@ -89,15 +89,15 @@ class FormResource if ($resourcestat) { - if (! empty($conf->use_javascript_ajax) && ! empty($conf->global->COMPANY_USE_SEARCH_TO_SELECT) && ! $forcecombo) + if (! empty($conf->use_javascript_ajax) && ! empty($conf->global->RESOURCE_USE_SEARCH_TO_SELECT) && ! $forcecombo) { - //$minLength = (is_numeric($conf->global->COMPANY_USE_SEARCH_TO_SELECT)?$conf->global->COMPANY_USE_SEARCH_TO_SELECT:2); - $out.= ajax_combobox($htmlname, $event, $conf->global->COMPANY_USE_SEARCH_TO_SELECT); + //$minLength = (is_numeric($conf->global->RESOURCE_USE_SEARCH_TO_SELECT)?$conf->global->RESOURCE_USE_SEARCH_TO_SELECT:2); + $out.= ajax_combobox($htmlname, $event, $conf->global->RESOURCE_USE_SEARCH_TO_SELECT); } // Construct $out and $outarray - $out.= ''."\n"; + if ($showempty) $out.= ''."\n"; $num = count($resourcestat->lines); //var_dump($resourcestat->lines); @@ -106,9 +106,11 @@ class FormResource { while ( $i < $num) { - $label=$langs->trans(ucfirst($resourcestat->lines[$i]->element)).' : '; - $label.=$resourcestat->lines[$i]->ref?$resourcestat->lines[$i]->ref:''.$resourcestat->lines[$i]->label; - + $resourceclass=ucfirst($resourcestat->lines[$i]->element); + + $label=$resourcestat->lines[$i]->ref?$resourcestat->lines[$i]->ref:''.$resourcestat->lines[$i]->label; + if ($resourceclass != 'Dolresource') $label.=' ('.$langs->trans($resourceclass).')'; + if ($selected > 0 && $selected == $resourcestat->lines[$i]->id) { $out.= ''; From 0e040b166dbe69bd83710e78462b38caa31037c8 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 17:55:41 +0200 Subject: [PATCH 20/61] Fix: When we edit a product to use lot/serial number, we initialize records for unknown lots to generic value 'Undefined'. --- htdocs/product/class/product.class.php | 35 ++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 3cc72ac9b97..fce31e31923 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -666,25 +666,45 @@ class Product extends CommonObject $org->fetch($this->id); $this->oldcopy=$org; } - // test if batch management is activated on existing product + + // Test if batch management is activated on existing product + // If yes, we create missing entries into product_batch if ($this->hasbatch() && !$this->oldcopy->hasbatch()) { + $valueforundefinedlot = 'Undefined'; + + dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch"); + $this->load_stock(); - foreach ($this->stock_warehouse as $idW => $ObjW) + foreach ($this->stock_warehouse as $idW => $ObjW) // For each warehouse where we have stocks defined for this product (for each lines in product_stock) { $qty_batch = 0; - foreach ($ObjW->detail_batch as $detail) + foreach ($ObjW->detail_batch as $detail) // Each lines of detail in product_batch of the current $ObjW = product_stock { + if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') + { + // We discard this line, we will create it later + $sqlclean="DELETE FROM ".MAIN_DB_PREFIX."product_batch WHERE batch in('Undefined', '".$valueforundefinedlot."') AND fk_product_stock = ".$ObjW->id; + $result = $this->db->query($sqlclean); + if (! $result) + { + dol_print_error($this->db); + exit; + } + continue; + } + $qty_batch += $detail->qty; } - // Quantities in batch details are not same same as stock quantity - // So we add a default batch record + // Quantities in batch details are not same as stock quantity, + // so we add a default batch record to complete and get same qty in parent and child table if ($ObjW->real <> $qty_batch) { $ObjBatch = new Productbatch($this->db); - $ObjBatch->batch = $langs->trans('BatchDefaultNumber'); - $ObjBatch->qty = $ObjW->real - $qty_batch; + $ObjBatch->batch = $valueforundefinedlot; + $ObjBatch->qty = ($ObjW->real - $qty_batch); $ObjBatch->fk_product_stock = $ObjW->id; + if ($ObjBatch->create($user,1) < 0) { $error++; @@ -693,6 +713,7 @@ class Product extends CommonObject } } } + // For automatic creation if ($this->barcode == -1) $this->barcode = $this->get_barcode($this,$this->barcode_type_code); From 79be93d998bee5295148dfe26501708bb8358d32 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 14:02:18 +0200 Subject: [PATCH 21/61] FIX The table llx_product_lot is now filled/used to store eatby/sellby dates. --- dev/skeletons/build_class_from_table.php | 2 +- .../install/mysql/migration/3.9.0-4.0.0.sql | 7 ++ htdocs/langs/en_US/productbatch.lang | 1 - htdocs/product/class/product.class.php | 42 +++++-- htdocs/product/class/productbatch.class.php | 58 +++++---- htdocs/product/reassortlot.php | 20 +-- .../stock/class/mouvementstock.class.php | 53 +++++++- .../product/stock/class/productlot.class.php | 118 ++++++++++-------- htdocs/product/stock/mouvement.php | 5 +- htdocs/product/stock/product.php | 68 +++++----- htdocs/product/stock/productlot_card.php | 44 +++---- htdocs/product/stock/productlot_list.php | 49 ++++---- .../product/stock/tpl/stocktransfer.tpl.php | 11 +- 13 files changed, 301 insertions(+), 177 deletions(-) diff --git a/dev/skeletons/build_class_from_table.php b/dev/skeletons/build_class_from_table.php index 800bafe6318..ecdbbd09dc4 100755 --- a/dev/skeletons/build_class_from_table.php +++ b/dev/skeletons/build_class_from_table.php @@ -259,7 +259,7 @@ foreach($property as $key => $prop) if ($addfield) { $varprop.="\t\t\$sql.= '".$prop['field']; - if ($i <= count($property)-$no_output_field) $varprop.=","; + if ($i < (count($property)-$no_output_field)) $varprop.=","; $varprop.="';"; $varprop.="\n"; } diff --git a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql index 93469ff5a8f..f34e52dafe6 100644 --- a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql +++ b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql @@ -54,6 +54,8 @@ CREATE TABLE llx_product_lot ( import_key integer ) ENGINE=InnoDB; +ALTER TABLE llx_product_lot ADD UNIQUE INDEX uk_product_lot(fk_product, batch); + DROP TABLE llx_stock_serial; ALTER TABLE llx_product ADD COLUMN note_public text; @@ -460,6 +462,11 @@ CREATE TABLE llx_advtargetemailing ALTER TABLE llx_advtargetemailing ADD UNIQUE INDEX uk_advtargetemailing_name (name); +update llx_product_batch set batch = '000000' where batch = 'Non défini'; +update llx_product_batch set batch = '000000' where batch = 'Non défini'; +update llx_product_batch set batch = '000000' where batch = 'Undefined'; +update llx_product_lot set batch = '000000' where batch = 'Undefined'; +update llx_stock_mouvement set batch = '000000' where batch = 'Undefined'; -- At end ALTER TABLE llx_product_batch ADD UNIQUE INDEX uk_product_batch (fk_product_stock, batch); diff --git a/htdocs/langs/en_US/productbatch.lang b/htdocs/langs/en_US/productbatch.lang index 37ceaa49b38..c3d78f7ff7f 100644 --- a/htdocs/langs/en_US/productbatch.lang +++ b/htdocs/langs/en_US/productbatch.lang @@ -17,6 +17,5 @@ printEatby=Eat-by: %s printSellby=Sell-by: %s printQty=Qty: %d AddDispatchBatchLine=Add a line for Shelf Life dispatching -BatchDefaultNumber=Undefined WhenProductBatchModuleOnOptionAreForced=When module Lot/Serial is on, increase/decrease stock mode is forced to last choice and can't be edited. Other options can be defined as you want. ProductDoesNotUseBatchSerial=This product does not use lot/serial number diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index e8b32f978cb..31aa35aba69 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -607,7 +607,8 @@ class Product extends CommonObject } /** - * Update a record into database + * Update a record into database. + * If batch flag is set to on, we create records into llx_product_batch * * @param int $id Id of product * @param User $user Object user making update @@ -672,25 +673,46 @@ class Product extends CommonObject $org->fetch($this->id); $this->oldcopy=$org; } - // test if batch management is activated on existing product + + // Test if batch management is activated on existing product + // If yes, we create missing entries into product_batch if ($this->hasbatch() && !$this->oldcopy->hasbatch()) { + //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower + $valueforundefinedlot = '000000'; + + dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch"); + $this->load_stock(); - foreach ($this->stock_warehouse as $idW => $ObjW) + foreach ($this->stock_warehouse as $idW => $ObjW) // For each warehouse where we have stocks defined for this product (for each lines in product_stock) { $qty_batch = 0; - foreach ($ObjW->detail_batch as $detail) + foreach ($ObjW->detail_batch as $detail) // Each lines of detail in product_batch of the current $ObjW = product_stock { + if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') + { + // We discard this line, we will create it later + $sqlclean="DELETE FROM ".MAIN_DB_PREFIX."product_batch WHERE batch in('Undefined', '".$valueforundefinedlot."') AND fk_product_stock = ".$ObjW->id; + $result = $this->db->query($sqlclean); + if (! $result) + { + dol_print_error($this->db); + exit; + } + continue; + } + $qty_batch += $detail->qty; } - // Quantities in batch details are not same same as stock quantity - // So we add a default batch record + // Quantities in batch details are not same as stock quantity, + // so we add a default batch record to complete and get same qty in parent and child table if ($ObjW->real <> $qty_batch) { $ObjBatch = new Productbatch($this->db); - $ObjBatch->batch = $langs->trans('BatchDefaultNumber'); - $ObjBatch->qty = $ObjW->real - $qty_batch; + $ObjBatch->batch = $valueforundefinedlot; + $ObjBatch->qty = ($ObjW->real - $qty_batch); $ObjBatch->fk_product_stock = $ObjW->id; + if ($ObjBatch->create($user,1) < 0) { $error++; @@ -699,6 +721,7 @@ class Product extends CommonObject } } } + // For automatic creation if ($this->barcode == -1) $this->barcode = $this->get_barcode($this,$this->barcode_type_code); @@ -744,6 +767,7 @@ class Product extends CommonObject $sql.= ", price_autogen = " . (!$this->price_autogen ? 0 : 1); $sql.= ", fk_price_expression = ".($this->fk_price_expression != 0 ? $this->fk_price_expression : 'NULL'); $sql.= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL'); + // stock field is not here because it is a denormalized value from product_stock. $sql.= " WHERE rowid = " . $id; dol_syslog(get_class($this)."::update", LOG_DEBUG); @@ -1597,7 +1621,7 @@ class Product extends CommonObject * @param int $ignore_expression Ignores the math expression for calculating price and uses the db value instead * @return int <0 if KO, 0 if not found, >0 if OK */ - function fetch($id='', $ref='', $ref_ext='', $ignore_expression = 0) + function fetch($id='', $ref='', $ref_ext='', $ignore_expression=0) { include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; diff --git a/htdocs/product/class/productbatch.class.php b/htdocs/product/class/productbatch.class.php index d4440dac0f3..ed03c3e729e 100644 --- a/htdocs/product/class/productbatch.class.php +++ b/htdocs/product/class/productbatch.class.php @@ -40,7 +40,7 @@ class Productbatch extends CommonObject var $batch=''; var $qty; public $warehouseid; - + public $fk_product; @@ -75,7 +75,7 @@ class Productbatch extends CommonObject // Put here code to add control on parameters values // Insert request - $sql = "INSERT INTO ".MAIN_DB_PREFIX.self::$_table_element." ("; + $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_batch ("; $sql.= "fk_product_stock,"; $sql.= "sellby,"; $sql.= "eatby,"; @@ -143,15 +143,18 @@ class Productbatch extends CommonObject $sql.= " t.tms,"; $sql.= " t.fk_product_stock,"; - $sql.= " t.sellby,"; - $sql.= " t.eatby,"; + $sql.= " t.sellby as oldsellby,"; + $sql.= " t.eatby as oldeatby,"; $sql.= " t.batch,"; $sql.= " t.qty,"; $sql.= " t.import_key,"; - $sql.= " w.fk_entrepot"; + $sql.= " w.fk_entrepot,"; + $sql.= " w.fk_product,"; + $sql.= " pl.eatby,"; + $sql.= " pl.sellby"; - $sql.= " FROM ".MAIN_DB_PREFIX.self::$_table_element." as t"; - $sql.= " INNER JOIN ".MAIN_DB_PREFIX."product_stock w on t.fk_product_stock=w.rowid"; + $sql.= " FROM ".MAIN_DB_PREFIX."product_batch as t INNER JOIN ".MAIN_DB_PREFIX."product_stock w on t.fk_product_stock = w.rowid"; + $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lot as pl on pl.fk_product = w.fk_product and pl.batch = t.batch"; $sql.= " WHERE t.rowid = ".$id; dol_syslog(get_class($this)."::fetch", LOG_DEBUG); @@ -165,12 +168,13 @@ class Productbatch extends CommonObject $this->id = $obj->rowid; $this->tms = $this->db->jdate($obj->tms); $this->fk_product_stock = $obj->fk_product_stock; - $this->sellby = $this->db->jdate($obj->sellby); - $this->eatby = $this->db->jdate($obj->eatby); + $this->sellby = $this->db->jdate($obj->sellby?$obj->sellby:$obj->oldsellby); + $this->eatby = $this->db->jdate($obj->eatby?$obj->eatby:$obj->oldeatby); $this->batch = $obj->batch; $this->qty = $obj->qty; $this->import_key = $obj->import_key; $this->warehouseid= $obj->fk_entrepot; + $this->fk_product= $obj->fk_product; } $this->db->free($resql); @@ -408,9 +412,9 @@ class Productbatch extends CommonObject * Find first detail record that match eather eat-by or sell-by or batch within given warehouse * * @param int $fk_product_stock id product_stock for objet - * @param date $eatby eat-by date for objet - deprecated: a search must be done on batch number - * @param date $sellby sell-by date for objet - deprecated: a search must be done on batch number - * @param string $batch_number batch number for objet + * @param date $eatby eat-by date for object - deprecated: a search must be done on batch number + * @param date $sellby sell-by date for object - deprecated: a search must be done on batch number + * @param string $batch_number batch number for object * @return int <0 if KO, >0 if OK */ function find($fk_product_stock=0, $eatby='',$sellby='',$batch_number='') @@ -421,8 +425,8 @@ class Productbatch extends CommonObject $sql.= " t.rowid,"; $sql.= " t.tms,"; $sql.= " t.fk_product_stock,"; - $sql.= " t.sellby,"; - $sql.= " t.eatby,"; + $sql.= " t.sellby,"; // deprecated + $sql.= " t.eatby,"; // deprecated $sql.= " t.batch,"; $sql.= " t.qty,"; $sql.= " t.import_key"; @@ -431,6 +435,7 @@ class Productbatch extends CommonObject if (! empty($eatby)) array_push($where," eatby = '".$this->db->idate($eatby)."'"); // deprecated if (! empty($sellby)) array_push($where," sellby = '".$this->db->idate($sellby)."'"); // deprecated + if (! empty($batch_number)) $sql.= " AND batch = '".$this->db->escape($batch_number)."'"; if (! empty($where)) $sql.= " AND (".implode(" OR ",$where).")"; @@ -468,10 +473,11 @@ class Productbatch extends CommonObject * * @param DoliDB $db database object * @param int $fk_product_stock id product_stock for objet - * @param int $with_qty doesn't return line with 0 quantity + * @param int $with_qty 1 = doesn't return line with 0 quantity + * @param int $fk_product If set to a product id, get eatby and sellby from table llx_product_lot * @return array <0 if KO, array of batch */ - public static function findAll($db,$fk_product_stock,$with_qty=0) + public static function findAll($db, $fk_product_stock, $with_qty=0, $fk_product=0) { global $langs; $ret = array(); @@ -480,14 +486,22 @@ class Productbatch extends CommonObject $sql.= " t.rowid,"; $sql.= " t.tms,"; $sql.= " t.fk_product_stock,"; - $sql.= " t.sellby,"; - $sql.= " t.eatby,"; + $sql.= " t.sellby as oldsellby,"; // deprecated but may not be migrated into new table + $sql.= " t.eatby as oldeatby,"; // deprecated but may not be migrated into new table $sql.= " t.batch,"; $sql.= " t.qty,"; $sql.= " t.import_key"; + if ($fk_product > 0) + { + $sql.= ", pl.eatby as eatby, pl.sellby as sellby"; + } $sql.= " FROM ".MAIN_DB_PREFIX."product_batch as t"; + if ($fk_product > 0) + { + $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lot as pl ON pl.fk_product = ".$fk_product." AND pl.batch = t.batch"; + } $sql.= " WHERE fk_product_stock=".$fk_product_stock; - if ($with_qty) $sql.= " AND qty<>0"; + if ($with_qty) $sql.= " AND t.qty <> 0"; dol_syslog("productbatch::findAll", LOG_DEBUG); $resql=$db->query($sql); @@ -499,12 +513,12 @@ class Productbatch extends CommonObject { $obj = $db->fetch_object($resql); - $tmp = new productbatch($db); + $tmp = new Productbatch($db); $tmp->id = $obj->rowid; $tmp->tms = $db->jdate($obj->tms); $tmp->fk_product_stock = $obj->fk_product_stock; - $tmp->sellby = $db->jdate($obj->sellby); - $tmp->eatby = $db->jdate($obj->eatby); + $tmp->sellby = $db->jdate($obj->sellby ? $obj->sellby : $obj->oldsellby); + $tmp->eatby = $db->jdate($obj->eatby ? $obj->eatby : $obj->oldeatby); $tmp->batch = $obj->batch; $tmp->qty = $obj->qty; $tmp->import_key = $obj->import_key; diff --git a/htdocs/product/reassortlot.php b/htdocs/product/reassortlot.php index b23a1cbf25c..f7d5e60ca72 100644 --- a/htdocs/product/reassortlot.php +++ b/htdocs/product/reassortlot.php @@ -105,15 +105,17 @@ $htmlother=new FormOther($db); $title=$langs->trans("ProductsAndServices"); -$sql = 'SELECT p.rowid, p.ref, p.label, p.barcode, p.price, p.price_ttc, p.price_base_type,p.entity,'; +$sql = 'SELECT p.rowid, p.ref, p.label, p.barcode, p.price, p.price_ttc, p.price_base_type, p.entity,'; $sql.= ' p.fk_product_type, p.tms as datem,'; -$sql.= ' p.duration, p.tosell as statut, p.tobuy, p.seuil_stock_alerte, p.desiredstock,'; +$sql.= ' p.duration, p.tosell as statut, p.tobuy, p.seuil_stock_alerte, p.desiredstock, p.stock, p.tobatch,'; $sql.= ' s.fk_entrepot,'; -$sql.= ' pb.batch, pb.eatby, pb.sellby,'; -$sql.= ' SUM(pb.qty) as stock_physique'; +$sql.= ' pb.batch, pb.eatby as oldeatby, pb.sellby as oldsellby,'; +$sql.= ' pl.eatby, pl.sellby,'; +$sql.= ' SUM(pb.qty) as stock_physique, COUNT(pb.rowid) as nbinbatchtable'; $sql.= ' FROM '.MAIN_DB_PREFIX.'product as p'; $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product_stock as s on p.rowid = s.fk_product'; $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product_batch as pb on pb.fk_product_stock = s.rowid'; +$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product_lot as pl on pl.fk_product = p.rowid AND pl.batch = pb.batch'; // We'll need this table joined to the select in order to filter by categ if ($search_categ) $sql.= ", ".MAIN_DB_PREFIX."categorie_product as cp"; $sql.= " WHERE p.entity IN (".getEntity('product', 1).")"; @@ -166,10 +168,12 @@ if ($search_batch) { $sql .= " AND pb.batch LIKE '%".$db->escape($search_batch)."%'"; } -$sql.= " GROUP BY p.rowid, p.ref, p.label, p.barcode, p.price, p.price_ttc, p.price_base_type,"; -$sql.= " p.fk_product_type, p.tms, p.duration, p.tosell, p.tobuy, p.seuil_stock_alerte, p.desiredstock,"; +$sql.= " GROUP BY p.rowid, p.ref, p.label, p.barcode, p.price, p.price_ttc, p.price_base_type, p.entity,"; +$sql.= " p.fk_product_type, p.tms,"; +$sql.= " p.duration, p.tosell, p.tobuy, p.seuil_stock_alerte, p.desiredstock, p.stock, p.tobatch,"; $sql.= " s.fk_entrepot,"; -$sql.= " pb.batch, pb.eatby, pb.sellby"; +$sql.= " pb.batch, pb.eatby, pb.sellby,"; +$sql.= " pl.eatby, pl.sellby"; if ($toolowstock) $sql.= " HAVING SUM(".$db->ifsql('s.reel IS NULL', '0', 's.reel').") < p.seuil_stock_alerte"; // Not used yet $sql.= $db->order($sortfield,$sortorder); $sql.= $db->plimit($limit + 1, $offset); @@ -364,7 +368,7 @@ if ($resql) //if ($objp->seuil_stock_alerte && ($objp->stock_physique < $objp->seuil_stock_alerte)) print img_warning($langs->trans("StockTooLow")).' '; print $objp->stock_physique; print '
'.$langs->trans("Movements").''.$langs->trans("Movements").''.$product_static->LibStatut($objp->statut,5,0).''.$product_static->LibStatut($objp->tobuy,5,1).'
'; - print ''; + //print ''; + print $formproduct->selectWarehouses($search_warehouse, 'search_warehouse', '', 1); print '
'; +print '
'; print ''; print ''; print ''; @@ -726,14 +738,14 @@ if ($resql) if (price2num($object->pmp)) $totalwithpmp += $obj->reel; $totalvalue = $totalvalue + ($object->pmp*$obj->reel); $totalvaluesell = $totalvaluesell + ($object->price*$obj->reel); - //Batch Detail + // Batch Detail if ((! empty($conf->productbatch->enabled)) && $object->hasbatch()) { - $details=Productbatch::findAll($db,$obj->product_stock_id); + $details=Productbatch::findAll($db, $obj->product_stock_id, 0, $object->id); if ($details<0) dol_print_error($db); foreach ($details as $pdluo) { - if ( $action == 'editline' && GETPOST('lineid','int')==$pdluo->id ) + if ($action == 'editline' && GETPOST('lineid','int') == $pdluo->id) { //Current line edit print "\n".''; +if (! empty($arrayfields['t.entity']['checked'])) print ''; if (! empty($arrayfields['t.fk_product']['checked'])) print ''; if (! empty($arrayfields['t.batch']['checked'])) print ''; -if (! empty($arrayfields['t.note_public']['checked'])) print ''; -if (! empty($arrayfields['t.note_private']['checked'])) print ''; -if (! empty($arrayfields['t.qty']['checked'])) print ''; +if (! empty($arrayfields['t.fk_user_creat']['checked'])) print ''; +if (! empty($arrayfields['t.fk_user_modif']['checked'])) print ''; if (! empty($arrayfields['t.import_key']['checked'])) print ''; diff --git a/htdocs/product/stock/tpl/stocktransfer.tpl.php b/htdocs/product/stock/tpl/stocktransfer.tpl.php index a50fafca278..824eda16d30 100644 --- a/htdocs/product/stock/tpl/stocktransfer.tpl.php +++ b/htdocs/product/stock/tpl/stocktransfer.tpl.php @@ -88,7 +88,16 @@ { print ''; print 'element == 'stock'?'': ' class="fieldrequired"').'>'.$langs->trans("batch_number").''; print ''; print ''; if ($conf->productbatch->enabled) { print ''; } // In warehouse @@ -390,7 +390,7 @@ print ''; // Qty -print ''; +print ''; // Button to add line print ''; diff --git a/htdocs/product/stock/replenish.php b/htdocs/product/stock/replenish.php index 7af08ee6575..21f492e0c00 100644 --- a/htdocs/product/stock/replenish.php +++ b/htdocs/product/stock/replenish.php @@ -75,13 +75,15 @@ if (! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || ! empty($conf->global { $virtualdiffersfromphysical=1; // According to increase/decrease stock options, virtual and physical stock may differs. } +$usevirtualstock=0; +if ($mode == 'virtual') $usevirtualstock=1; /* * Actions */ -if (isset($_POST['button_removefilter']) || isset($_POST['valid'])) +if (GETPOST("button_removefilter_x") || GETPOST("button_removefilter.x") || GETPOST("button_removefilter") || isset($_POST['valid'])) // Both test are required to be compatible with all browsers { $sref = ''; $snom = ''; From 47f003c3fae5d899e8752e192f3d69d921ca97e3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 19:40:58 +0200 Subject: [PATCH 25/61] Restore phpunit tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 74d951bf7bf..0f14e0a49ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -291,7 +291,7 @@ script: echo "Unit testing" # Ensure we catch errors. Set this to +e if you want to go to the end to see log file. set -e - #phpunit -d memory_limit=-1 -c test/phpunit/phpunittest.xml test/phpunit/AllTests.php + phpunit -d memory_limit=-1 -c test/phpunit/phpunittest.xml test/phpunit/AllTests.php set +e - | From 86076c2a0198c154e1508b4c313bab047bce31a4 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 19:49:37 +0200 Subject: [PATCH 26/61] Fix doxygen --- htdocs/core/menus/standard/auguria.lib.php | 4 ++-- htdocs/core/menus/standard/eldy.lib.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/core/menus/standard/auguria.lib.php b/htdocs/core/menus/standard/auguria.lib.php index cbd4b035c4e..5a853baad51 100644 --- a/htdocs/core/menus/standard/auguria.lib.php +++ b/htdocs/core/menus/standard/auguria.lib.php @@ -31,8 +31,8 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/menubase.class.php'; * @param DoliDB $db Database handler * @param string $atarget Target * @param int $type_user 0=Menu for backoffice, 1=Menu for front office - * @param array $tabMenu If array with menu entries already loaded, we put this array here (in most cases, it's empty) - * @param array $menu Object Menu to return back list of menu entries + * @param array $tabMenu If array with menu entries already loaded, we put this array here (in most cases, it's empty) + * @param Menu $menu Object Menu to return back list of menu entries * @param int $noout Disable output (Initialise &$menu only). * @return int 0 */ diff --git a/htdocs/core/menus/standard/eldy.lib.php b/htdocs/core/menus/standard/eldy.lib.php index 1ce08a25bba..7164f52393f 100644 --- a/htdocs/core/menus/standard/eldy.lib.php +++ b/htdocs/core/menus/standard/eldy.lib.php @@ -34,7 +34,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/menubase.class.php'; * @param string $atarget Target (Example: '' or '_top') * @param int $type_user 0=Menu for backoffice, 1=Menu for front office * @param array $tabMenu If array with menu entries already loaded, we put this array here (in most cases, it's empty) - * @param array $menu Object Menu to return back list of menu entries + * @param Menu $menu Object Menu to return back list of menu entries * @param int $noout 1=Disable output (Initialise &$menu only). * @return int 0 */ From 92e7177bcb1537e135574505ca7f0410ecc96fed Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 19:58:29 +0200 Subject: [PATCH 27/61] Fix New into new --- htdocs/admin/dict.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/admin/dict.php b/htdocs/admin/dict.php index 797b35f7c75..7045695c1c2 100644 --- a/htdocs/admin/dict.php +++ b/htdocs/admin/dict.php @@ -1553,7 +1553,7 @@ function fieldList($fieldlist, $obj='', $tabname='', $context='') $formadmin = new FormAdmin($db); $formcompany = new FormCompany($db); - if (! empty($conf->accounting->enabled)) $formaccountancy = New FormVentilation($db); + if (! empty($conf->accounting->enabled)) $formaccountancy = new FormVentilation($db); foreach ($fieldlist as $field => $value) { From a36b780c53a3a1891f4af6aded32cdbead02c52d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 20:30:04 +0200 Subject: [PATCH 28/61] Fix phpunit --- htdocs/install/mysql/migration/3.9.0-4.0.0.sql | 4 ++-- htdocs/install/mysql/tables/llx_c_accounting_category.sql | 4 ++-- test/phpunit/SqlTest.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql index 136a7114eb0..f996ca3677a 100644 --- a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql +++ b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql @@ -364,8 +364,8 @@ CREATE TABLE llx_c_accounting_category ( code varchar(16) NOT NULL, label varchar(255) NOT NULL, range_account varchar(255) NOT NULL, - sens tinyint(1) NOT NULL DEFAULT '0', -- For international accounting 0 : credit - debit / 1 : debit - credit - category_type tinyint(1) NOT NULL DEFAULT '0', -- Field calculated or not + sens tinyint NOT NULL DEFAULT '0', -- For international accounting 0 : credit - debit / 1 : debit - credit + category_type tinyint NOT NULL DEFAULT '0', -- Field calculated or not formula varchar(255) NOT NULL, -- Example : 1 + 2 (rowid of the category) position integer DEFAULT 0, fk_country integer DEFAULT NULL, -- This category is dedicated to a country diff --git a/htdocs/install/mysql/tables/llx_c_accounting_category.sql b/htdocs/install/mysql/tables/llx_c_accounting_category.sql index a5ca2a0352b..683478aceef 100644 --- a/htdocs/install/mysql/tables/llx_c_accounting_category.sql +++ b/htdocs/install/mysql/tables/llx_c_accounting_category.sql @@ -23,8 +23,8 @@ CREATE TABLE llx_c_accounting_category ( code varchar(16) NOT NULL, label varchar(255) NOT NULL, range_account varchar(255) NOT NULL, - sens tinyint(1) NOT NULL DEFAULT '0', -- For international accounting 0 : credit - debit / 1 : debit - credit - category_type tinyint(1) NOT NULL DEFAULT '0', -- Field calculated or not + sens tinyint NOT NULL DEFAULT '0', -- For international accounting 0 : credit - debit / 1 : debit - credit + category_type tinyint NOT NULL DEFAULT '0', -- Field calculated or not formula varchar(255) NOT NULL, -- Example : 1 + 2 (rowid of the category) position integer DEFAULT 0, fk_country integer DEFAULT NULL, -- This category is dedicated to a country diff --git a/test/phpunit/SqlTest.php b/test/phpunit/SqlTest.php index 887d2aa89bb..0a9acc50220 100644 --- a/test/phpunit/SqlTest.php +++ b/test/phpunit/SqlTest.php @@ -162,7 +162,7 @@ class SqlTest extends PHPUnit_Framework_TestCase $result=strpos($filecontent,'int('); print __METHOD__." Result for checking we don't have 'int(' instead of 'integer' = ".$result."\n"; - $this->assertTrue($result===false, 'Found int(x) instead of integer into '.$file.'. Bad.'); + $this->assertTrue($result===false, 'Found int(x) or tinyint(x) instead of integer or tinyint into '.$file.'. Bad.'); $result=strpos($filecontent,'ON DELETE CASCADE'); print __METHOD__." Result for checking we don't have 'ON DELETE CASCADE' = ".$result."\n"; From 89d37ccc62cbfd56cf9620e14f1fcc791df8102d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 21:55:39 +0200 Subject: [PATCH 29/61] Fix translation --- htdocs/langs/en_US/main.lang | 1 + htdocs/product/card.php | 2 +- htdocs/product/class/product.class.php | 3 ++- htdocs/projet/class/project.class.php | 3 ++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index 75023bf9184..b92c0db4d49 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -137,6 +137,7 @@ Disabled=Disabled Add=Add AddLink=Add link RemoveLink=Remove link +AddToDraft=Add to draft Update=Update AddActionToDo=Add event to do AddActionDone=Add event done diff --git a/htdocs/product/card.php b/htdocs/product/card.php index 5fdfa1f2337..ca6e6b14544 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -1876,7 +1876,7 @@ if ($object->id && ($action == '' || $action == 'view') && $object->status) print ''; print ''; - print load_fiche_titre($langs->trans("Add"),'',''); + print load_fiche_titre($langs->trans("AddToDraft"),'',''); dol_fiche_head(''); diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 32a474d924b..98684d5f980 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -829,7 +829,8 @@ class Product extends CommonObject $res = @rename($olddir, $newdir); if (! $res) { - $this->error='ErrorFailToMoveDir'; + $langs->load("errors"); + $this->error=$langs->trans('ErrorFailToRenameDir',$olddir,$newdir); $error++; } } diff --git a/htdocs/projet/class/project.class.php b/htdocs/projet/class/project.class.php index 8e699e8ee91..1de8027dd0e 100644 --- a/htdocs/projet/class/project.class.php +++ b/htdocs/projet/class/project.class.php @@ -297,7 +297,8 @@ class Project extends CommonObject $res=dol_move($olddir, $newdir); if (! $res) { - $this->error='ErrorFailToMoveDir'; + $langs->load("errors"); + $this->error=$langs->trans('ErrorFailToRenameDir',$olddir,$newdir); $error++; } } From 4e46a01d513bf00650e5b1ac0823dafeade30873 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 22:59:46 +0200 Subject: [PATCH 30/61] Fix regression introduced with option SUPPLIERORDER_WITH_NOPRICEDEFINED --- .../class/fournisseur.commande.class.php | 64 ++++--- .../fourn/class/fournisseur.product.class.php | 174 +++++++++--------- htdocs/product/class/product.class.php | 17 +- test/phpunit/CommandeFournisseurTest.php | 2 +- 4 files changed, 127 insertions(+), 130 deletions(-) diff --git a/htdocs/fourn/class/fournisseur.commande.class.php b/htdocs/fourn/class/fournisseur.commande.class.php index cb7d0ce6e6f..f8c57d7cc94 100644 --- a/htdocs/fourn/class/fournisseur.commande.class.php +++ b/htdocs/fourn/class/fournisseur.commande.class.php @@ -1126,7 +1126,7 @@ class CommandeFournisseur extends CommonOrder // insert products details into database for ($i=0;$i<$num;$i++) { - $result = $this->addline( + $result = $this->addline( // This include test on qty if option SUPPLIERORDER_WITH_NOPRICEDEFINED is not set $this->lines[$i]->desc, $this->lines[$i]->subprice, $this->lines[$i]->qty, @@ -1306,11 +1306,11 @@ class CommandeFournisseur extends CommonOrder */ function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1=0.0, $txlocaltax2=0.0, $fk_product=0, $fk_prod_fourn_price=0, $fourn_ref='', $remise_percent=0.0, $price_base_type='HT', $pu_ttc=0.0, $type=0, $info_bits=0, $notrigger=false, $date_start=null, $date_end=null, $array_options=0, $fk_unit=null) { - global $langs,$mysoc, $conf; + global $langs,$mysoc,$conf; $error = 0; - dol_syslog(get_class($this)."::addline $desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2. $fk_product, $fk_prod_fourn_price, $fourn_ref, $remise_percent, $price_base_type, $pu_ttc, $type, $fk_unit"); + dol_syslog(get_class($this)."::addline $desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2, $fk_product, $fk_prod_fourn_price, $fourn_ref, $remise_percent, $price_base_type, $pu_ttc, $type, $fk_unit"); include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php'; // Clean parameters @@ -1350,40 +1350,45 @@ class CommandeFournisseur extends CommonOrder { $this->db->begin(); - if ($fk_prod_fourn_price > 0) + if ($fk_product > 0) { - $prod = new Product($this->db, $fk_product); - if ($prod->fetch($fk_product) > 0) + if (empty($conf->global->SUPPLIERORDER_WITH_NOPRICEDEFINED)) { - $result=$prod->get_buyprice($fk_prod_fourn_price,$qty,$fk_product,$fourn_ref); - if ($result > 0) + // Check quantity is enough + dol_syslog(get_class($this)."::addline we check supplier prices fk_product=".$fk_product." fk_prod_fourn_price=".$fk_prod_fourn_price." qty=".$qty." fourn_ref=".$fourn_ref); + $prod = new Product($this->db, $fk_product); + if ($prod->fetch($fk_product) > 0) { - $label = $prod->libelle; - $pu = $prod->fourn_pu; - $ref = $prod->ref_fourn; - $product_type = $prod->type; + $result=$prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, $fourn_ref); // Search on couple $fk_prod_fourn_price/$qty first, then on triplet $qty/$fk_product/$fourn_ref + if ($result > 0) + { + $label = $prod->libelle; + $pu = $prod->fourn_pu; + $ref = $prod->ref_fourn; + $product_type = $prod->type; + } + if ($result == 0 || $result == -1) + { + $langs->load("errors"); + $this->error = "Ref " . $prod->ref . " " . $langs->trans("ErrorQtyTooLowForThisSupplier"); + $this->db->rollback(); + dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG); + return -1; + } + if ($result < -1) + { + $this->error=$prod->error; + $this->db->rollback(); + dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR); + return -1; + } } - if ($result == 0 || $result == -1) - { - $langs->load("errors"); - $this->error = "Ref " . $prod->ref . " " . $langs->trans("ErrorQtyTooLowForThisSupplier"); - $this->db->rollback(); - dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG); - return -1; - } - if ($result < -1) - { + else + { $this->error=$prod->error; - $this->db->rollback(); - dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR); return -1; } } - else - { - $this->error=$prod->error; - return -1; - } } else { @@ -1449,7 +1454,6 @@ class CommandeFournisseur extends CommonOrder $sql.= ", ".$multicurrency_total_ttc; $sql.= ")"; - dol_syslog(get_class($this)."::addline", LOG_DEBUG); $resql=$this->db->query($sql); //print $sql; if ($resql) diff --git a/htdocs/fourn/class/fournisseur.product.class.php b/htdocs/fourn/class/fournisseur.product.class.php index 48896689dce..05024f9991a 100644 --- a/htdocs/fourn/class/fournisseur.product.class.php +++ b/htdocs/fourn/class/fournisseur.product.class.php @@ -216,7 +216,7 @@ class ProductFournisseur extends Product $this->db->begin(); - if ($this->product_fourn_price_id) + if ($this->product_fourn_price_id > 0) { $sql = "UPDATE ".MAIN_DB_PREFIX."product_fournisseur_price"; $sql.= " SET fk_user = " . $user->id." ,"; @@ -237,7 +237,7 @@ class ProductFournisseur extends Product $sql.= " WHERE rowid = ".$this->product_fourn_price_id; // TODO Add price_base_type and price_ttc - dol_syslog(get_class($this).'::update_buyprice', LOG_DEBUG); + dol_syslog(get_class($this).'::update_buyprice update knowing id of line = product_fourn_price_id = '.$this->product_fourn_price_id, LOG_DEBUG); $resql = $this->db->query($sql); if ($resql) { @@ -249,7 +249,7 @@ class ProductFournisseur extends Product if (empty($error)) { $this->db->commit(); - return 0; + return $this->product_fourn_price_id; } else { @@ -267,94 +267,88 @@ class ProductFournisseur extends Product else { - // Delete price for this quantity - $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_fournisseur_price"; - $sql.= " WHERE fk_soc = ".$fourn->id." AND ref_fourn = '".$this->db->escape($ref_fourn)."' AND quantity = ".$qty." AND entity = ".$conf->entity; - dol_syslog(get_class($this).'::update_buyprice', LOG_DEBUG); - $resql=$this->db->query($sql); - if ($resql) - { - // Add price for this quantity to supplier - $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_fournisseur_price("; - $sql.= "datec, fk_product, fk_soc, ref_fourn, fk_user, price, quantity, remise_percent, remise, unitprice, tva_tx, charges, unitcharges, fk_availability, info_bits, entity, delivery_time_days,supplier_reputation)"; - $sql.= " values('".$this->db->idate($now)."',"; - $sql.= " ".$this->id.","; - $sql.= " ".$fourn->id.","; - $sql.= " '".$this->db->escape($ref_fourn)."',"; - $sql.= " ".$user->id.","; - $sql.= " ".$buyprice.","; - $sql.= " ".$qty.","; - $sql.= " ".$remise_percent.","; - $sql.= " ".$remise.","; - $sql.= " ".$unitBuyPrice.","; - $sql.= " ".$tva_tx.","; - $sql.= " ".$charges.","; - $sql.= " ".$unitCharges.","; - $sql.= " ".$availability.","; - $sql.= " ".$newnpr.","; - $sql.= $conf->entity.","; - $sql.= $delivery_time_days.","; - $sql.= (empty($supplier_reputation) ? 'NULL' : "'".$this->db->escape($supplier_reputation)."'"); - $sql.=")"; - - dol_syslog(get_class($this)."::update_buyprice", LOG_DEBUG); - if (! $this->db->query($sql)) - { - $error++; - } - - if (! $error && !empty($conf->global->PRODUCT_PRICE_SUPPLIER_NO_LOG)) - { - // Add record into log table - $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_fournisseur_price_log("; - $sql.= "datec, fk_product_fournisseur,fk_user,price,quantity)"; - $sql.= "values('".$this->db->idate($now)."',"; - $sql.= " ".$this->product_fourn_id.","; - $sql.= " ".$user->id.","; - $sql.= " ".price2num($buyprice).","; - $sql.= " ".$qty; - $sql.=")"; - - $resql=$this->db->query($sql); - if (! $resql) - { - $error++; - } - } - - - if (! $error) - { - // Call trigger - $result=$this->call_trigger('SUPPLIER_PRODUCT_BUYPRICE_CREATE',$user); - if ($result < 0) $error++; + dol_syslog(get_class($this) . '::update_buyprice without knowing id of line, so we delete from company, quantity and supplier_ref and insert again', LOG_DEBUG); + + // Delete price for this quantity + $sql = "DELETE FROM " . MAIN_DB_PREFIX . "product_fournisseur_price"; + $sql .= " WHERE fk_soc = " . $fourn->id . " AND ref_fourn = '" . $this->db->escape($ref_fourn) . "' AND quantity = " . $qty . " AND entity = " . $conf->entity; + $resql = $this->db->query($sql); + if ($resql) { + // Add price for this quantity to supplier + $sql = "INSERT INTO " . MAIN_DB_PREFIX . "product_fournisseur_price("; + $sql .= "datec, fk_product, fk_soc, ref_fourn, fk_user, price, quantity, remise_percent, remise, unitprice, tva_tx, charges, unitcharges, fk_availability, info_bits, entity, delivery_time_days,supplier_reputation)"; + $sql .= " values('" . $this->db->idate($now) . "',"; + $sql .= " " . $this->id . ","; + $sql .= " " . $fourn->id . ","; + $sql .= " '" . $this->db->escape($ref_fourn) . "',"; + $sql .= " " . $user->id . ","; + $sql .= " " . $buyprice . ","; + $sql .= " " . $qty . ","; + $sql .= " " . $remise_percent . ","; + $sql .= " " . $remise . ","; + $sql .= " " . $unitBuyPrice . ","; + $sql .= " " . $tva_tx . ","; + $sql .= " " . $charges . ","; + $sql .= " " . $unitCharges . ","; + $sql .= " " . $availability . ","; + $sql .= " " . $newnpr . ","; + $sql .= $conf->entity . ","; + $sql .= $delivery_time_days . ","; + $sql .= (empty($supplier_reputation) ? 'NULL' : "'" . $this->db->escape($supplier_reputation) . "'"); + $sql .= ")"; + + $idinserted = 0; + + $resql = $this->db->query($sql); + if ($resql) { + $idinserted = $this->db->last_insert_id(MAIN_DB_PREFIX . "product_fournisseur_price"); + } + else { + $error++; + } + + if (! $error && ! empty($conf->global->PRODUCT_PRICE_SUPPLIER_NO_LOG)) { + // Add record into log table + $sql = "INSERT INTO " . MAIN_DB_PREFIX . "product_fournisseur_price_log("; + $sql .= "datec, fk_product_fournisseur,fk_user,price,quantity)"; + $sql .= "values('" . $this->db->idate($now) . "',"; + $sql .= " " . $this->product_fourn_id . ","; + $sql .= " " . $user->id . ","; + $sql .= " " . price2num($buyprice) . ","; + $sql .= " " . $qty; + $sql .= ")"; + + $resql = $this->db->query($sql); + if (! $resql) { + $error++; + } + } + + if (! $error) { + // Call trigger + $result = $this->call_trigger('SUPPLIER_PRODUCT_BUYPRICE_CREATE', $user); + if ($result < 0) + $error++; // End call triggers - - if (empty($error)) - { - $this->db->commit(); - return 0; - } - else - { - $this->db->rollback(); - return -1; - } - } - else - { - $this->error=$this->db->error()." sql=".$sql; - $this->db->rollback(); - return -2; - } - } - else - { - $this->error=$this->db->error()." sql=".$sql; - $this->db->rollback(); - return -1; - } - } + + if (empty($error)) { + $this->db->commit(); + return $idinserted; + } else { + $this->db->rollback(); + return -1; + } + } else { + $this->error = $this->db->lasterror() . " sql=" . $sql; + $this->db->rollback(); + return -2; + } + } else { + $this->error = $this->db->lasterror() . " sql=" . $sql; + $this->db->rollback(); + return - 1; + } + } } /** diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 98684d5f980..d242a343ce9 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -1315,21 +1315,21 @@ class Product extends CommonObject * @param double $qty Quantity asked * @param int $product_id Filter on a particular product id * @param string $fourn_ref Filter on a supplier ref - * @return int <-1 if KO, -1 if qty not enough, 0 si ok mais rien trouve, id_product si ok et trouve. May also initialize some properties like (->ref_supplier, buyprice, fourn_pu, vatrate_supplier...) + * @return int <-1 if KO, -1 if qty not enough, 0 if OK but nothing found, id_product if OK and found. May also initialize some properties like (->ref_supplier, buyprice, fourn_pu, vatrate_supplier...) */ function get_buyprice($prodfournprice,$qty,$product_id=0,$fourn_ref=0) { global $conf; $result = 0; - // We do select by searching with qty and prodfournprice + // We do a first seach with a select by searching with couple prodfournprice and qty only (later we will search on triplet qty/product_id/fourn_ref) $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity,"; $sql.= " pfp.fk_product, pfp.ref_fourn, pfp.fk_soc, pfp.tva_tx, pfp.fk_supplier_price_expression"; $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp"; $sql.= " WHERE pfp.rowid = ".$prodfournprice; if ($qty) $sql.= " AND pfp.quantity <= ".$qty; - dol_syslog(get_class($this)."::get_buyprice", LOG_DEBUG); + dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG); $resql = $this->db->query($sql); if ($resql) { @@ -1358,9 +1358,9 @@ class Product extends CommonObject $result=$obj->fk_product; return $result; } - else + else // If not found { - // We do same select again but searching with qty, ref and id product + // We do a second search by doing a select again but searching with qty, ref and id product $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.fk_soc,"; $sql.= " pfp.fk_product, pfp.ref_fourn as ref_supplier, pfp.tva_tx, pfp.fk_supplier_price_expression"; $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp"; @@ -1370,7 +1370,7 @@ class Product extends CommonObject $sql.= " ORDER BY pfp.quantity DESC"; $sql.= " LIMIT 1"; - dol_syslog(get_class($this)."::get_buyprice", LOG_DEBUG); + dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG); $resql = $this->db->query($sql); if ($resql) { @@ -2656,6 +2656,8 @@ class Product extends CommonObject $now=dol_now(); + dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG); + if ($ref_fourn) { $sql = "SELECT rowid, fk_product"; @@ -2665,7 +2667,6 @@ class Product extends CommonObject $sql.= " AND fk_product != ".$this->id; $sql.= " AND entity = ".$conf->entity; - dol_syslog(get_class($this)."::add_fournisseur", LOG_DEBUG); $resql=$this->db->query($sql); if ($resql) { @@ -2689,7 +2690,6 @@ class Product extends CommonObject $sql.= " AND fk_product = ".$this->id; $sql.= " AND entity = ".$conf->entity; - dol_syslog(get_class($this)."::add_fournisseur", LOG_DEBUG); $resql=$this->db->query($sql); if ($resql) { @@ -2718,7 +2718,6 @@ class Product extends CommonObject $sql.= ", 0"; $sql.= ")"; - dol_syslog(get_class($this)."::add_fournisseur", LOG_DEBUG); if ($this->db->query($sql)) { $this->product_fourn_price_id = $this->db->last_insert_id(MAIN_DB_PREFIX."product_fournisseur_price"); diff --git a/test/phpunit/CommandeFournisseurTest.php b/test/phpunit/CommandeFournisseurTest.php index 67c120a2bb8..03880a56eca 100644 --- a/test/phpunit/CommandeFournisseurTest.php +++ b/test/phpunit/CommandeFournisseurTest.php @@ -145,7 +145,7 @@ class CommandeFournisseurTest extends PHPUnit_Framework_TestCase // Create supplier price $result=$product->add_fournisseur($user, $societe->id, $ref_fourn, $quantity); // This insert record with no value for price. Values are update later with update_buyprice $this->assertGreaterThanOrEqual(1, $result); - $result=$product->update_buyprice($quantity, 10, $user, 'HT', $societe, '', $ref_fourn, $tva_tx, 0, 0); + $result=$product->update_buyprice($quantity, 20, $user, 'HT', $societe, '', $ref_fourn, $tva_tx, 0, 0); $this->assertGreaterThanOrEqual(0, $result); // Create supplier order with a too low quantity From 753c5c7fc4fbb3b0e798e0265461d8ce217cc86f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 23:14:30 +0200 Subject: [PATCH 31/61] Fix minor error management --- htdocs/core/db/mssql.class.php | 23 ++++++----- htdocs/core/db/mysql.class.php | 8 +++- htdocs/core/db/mysqli.class.php | 8 +++- htdocs/core/db/pgsql.class.php | 67 +++++++++++++++++++-------------- 4 files changed, 65 insertions(+), 41 deletions(-) diff --git a/htdocs/core/db/mssql.class.php b/htdocs/core/db/mssql.class.php index 0be65dfe2e6..2df3e341452 100644 --- a/htdocs/core/db/mssql.class.php +++ b/htdocs/core/db/mssql.class.php @@ -192,8 +192,12 @@ class DoliDBMssql extends DoliDB function getVersion() { $resql=$this->query("SELECT @@VERSION"); - $version=$this->fetch_array($resql); - return $version['computed']; + if ($resql) + { + $version=$this->fetch_array($resql); + return $version['computed']; + } + else return ''; } /** @@ -203,10 +207,7 @@ class DoliDBMssql extends DoliDB */ function getDriverInfo() { - // FIXME: Dummy method - // TODO: Implement - - return ''; + return 'php mssql driver'; } /** @@ -648,7 +649,7 @@ class DoliDBMssql extends DoliDB function last_insert_id($tab,$fieldid='rowid') { $res = $this->query("SELECT @@IDENTITY as id"); - if ($data = $this->fetch_array($res)) + if ($res && $data = $this->fetch_array($res)) { return $data["id"]; } @@ -709,8 +710,12 @@ class DoliDBMssql extends DoliDB function DDLGetConnectId() { $resql=$this->query('SELECT CONNECTION_ID()'); - $row=$this->fetch_row($resql); - return $row[0]; + if ($resql) + { + $row=$this->fetch_row($resql); + return $row[0]; + } + else return '?'; } /** diff --git a/htdocs/core/db/mysql.class.php b/htdocs/core/db/mysql.class.php index 34a7ebdf9f5..a92bb09e0b3 100644 --- a/htdocs/core/db/mysql.class.php +++ b/htdocs/core/db/mysql.class.php @@ -549,8 +549,12 @@ class DoliDBMysql extends DoliDB function DDLGetConnectId() { $resql=$this->query('SELECT CONNECTION_ID()'); - $row=$this->fetch_row($resql); - return $row[0]; + if ($resql) + { + $row=$this->fetch_row($resql); + return $row[0]; + } + else return '?'; } diff --git a/htdocs/core/db/mysqli.class.php b/htdocs/core/db/mysqli.class.php index d21caecea36..b6df8420490 100644 --- a/htdocs/core/db/mysqli.class.php +++ b/htdocs/core/db/mysqli.class.php @@ -532,8 +532,12 @@ class DoliDBMysqli extends DoliDB function DDLGetConnectId() { $resql=$this->query('SELECT CONNECTION_ID()'); - $row=$this->fetch_row($resql); - return $row[0]; + if ($resql) + { + $row=$this->fetch_row($resql); + return $row[0]; + } + else return '?'; } /** diff --git a/htdocs/core/db/pgsql.class.php b/htdocs/core/db/pgsql.class.php index 048ef2d450d..c8b72ebcdd9 100644 --- a/htdocs/core/db/pgsql.class.php +++ b/htdocs/core/db/pgsql.class.php @@ -874,31 +874,34 @@ class DoliDBPgsql extends DoliDB */ function DDLInfoTable($table) { - $infotables=array(); + $infotables=array(); - $sql="SELECT "; - $sql.=" infcol.column_name as \"Column\","; - $sql.=" CASE WHEN infcol.character_maximum_length IS NOT NULL THEN infcol.udt_name || '('||infcol.character_maximum_length||')'"; - $sql.=" ELSE infcol.udt_name"; - $sql.=" END as \"Type\","; - $sql.=" infcol.collation_name as \"Collation\","; - $sql.=" infcol.is_nullable as \"Null\","; - $sql.=" '' as \"Key\","; - $sql.=" infcol.column_default as \"Default\","; - $sql.=" '' as \"Extra\","; - $sql.=" '' as \"Privileges\""; - $sql.=" FROM information_schema.columns infcol"; - $sql.=" WHERE table_schema='public' "; - $sql.=" AND table_name='".$table."'"; - $sql.=" ORDER BY ordinal_position;"; + $sql="SELECT "; + $sql.=" infcol.column_name as \"Column\","; + $sql.=" CASE WHEN infcol.character_maximum_length IS NOT NULL THEN infcol.udt_name || '('||infcol.character_maximum_length||')'"; + $sql.=" ELSE infcol.udt_name"; + $sql.=" END as \"Type\","; + $sql.=" infcol.collation_name as \"Collation\","; + $sql.=" infcol.is_nullable as \"Null\","; + $sql.=" '' as \"Key\","; + $sql.=" infcol.column_default as \"Default\","; + $sql.=" '' as \"Extra\","; + $sql.=" '' as \"Privileges\""; + $sql.=" FROM information_schema.columns infcol"; + $sql.=" WHERE table_schema='public' "; + $sql.=" AND table_name='".$table."'"; + $sql.=" ORDER BY ordinal_position;"; - dol_syslog($sql,LOG_DEBUG); - $result = $this->query($sql); - while($row = $this->fetch_row($result)) - { - $infotables[] = $row; - } - return $infotables; + dol_syslog($sql,LOG_DEBUG); + $result = $this->query($sql); + if ($result) + { + while($row = $this->fetch_row($result)) + { + $infotables[] = $row; + } + } + return $infotables; } @@ -1111,8 +1114,12 @@ class DoliDBPgsql extends DoliDB function getDefaultCharacterSetDatabase() { $resql=$this->query('SHOW SERVER_ENCODING'); - $liste=$this->fetch_array($resql); - return $liste['server_encoding']; + if ($resql) + { + $liste=$this->fetch_array($resql); + return $liste['server_encoding']; + } + else return ''; } /** @@ -1127,7 +1134,7 @@ class DoliDBPgsql extends DoliDB if ($resql) { $i = 0; - while ($obj = $this->fetch_object($resql) ) + while ($obj = $this->fetch_object($resql)) { $liste[$i]['charset'] = $obj->server_encoding; $liste[$i]['description'] = 'Default database charset'; @@ -1148,8 +1155,12 @@ class DoliDBPgsql extends DoliDB function getDefaultCollationDatabase() { $resql=$this->query('SHOW LC_COLLATE'); - $liste=$this->fetch_array($resql); - return $liste['lc_collate']; + if ($resql) + { + $liste=$this->fetch_array($resql); + return $liste['lc_collate']; + } + else return ''; } /** From c8a8474b69664c2481a416b4a25f55b5e6c9af18 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 23:19:57 +0200 Subject: [PATCH 32/61] Better practice in boolean comparison --- htdocs/commande/index.php | 4 ++-- htdocs/contrat/index.php | 4 ++-- htdocs/core/db/mssql.class.php | 2 +- htdocs/fichinter/index.php | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/htdocs/commande/index.php b/htdocs/commande/index.php index fa1b2064ed2..5731535697e 100644 --- a/htdocs/commande/index.php +++ b/htdocs/commande/index.php @@ -123,7 +123,7 @@ if ($resql) foreach ($listofstatus as $status) { $dataseries[]=array('label'=>$commandestatic->LibStatut($status,$bool,1),'data'=>(isset($vals[$status.$bool])?(int) $vals[$status.$bool]:0)); - if ($status==3 && $bool==false) $bool=true; + if ($status==3 && ! $bool) $bool=true; else $bool=false; } if ($conf->use_javascript_ajax) @@ -147,7 +147,7 @@ if ($resql) print ''; print ''; print "\n"; - if ($status==3 && $bool==false) $bool=true; + if ($status==3 && ! $bool) $bool=true; else $bool=false; } } diff --git a/htdocs/contrat/index.php b/htdocs/contrat/index.php index 18a60a3429d..250f1c98199 100644 --- a/htdocs/contrat/index.php +++ b/htdocs/contrat/index.php @@ -190,7 +190,7 @@ foreach($listofstatus as $status) print ''; print "\n"; } - if ($status==4 && $bool==false) $bool=true; + if ($status==4 && ! $bool) $bool=true; else $bool=false; } if (! empty($conf->use_javascript_ajax)) @@ -210,7 +210,7 @@ foreach($listofstatus as $status) print ''; print ''; print ''; - if ($status==4 && $bool==false) $bool=true; + if ($status==4 && ! $bool) $bool=true; else $bool=false; print "\n"; } diff --git a/htdocs/core/db/mssql.class.php b/htdocs/core/db/mssql.class.php index 2df3e341452..97c72160d76 100644 --- a/htdocs/core/db/mssql.class.php +++ b/htdocs/core/db/mssql.class.php @@ -360,7 +360,7 @@ class DoliDBMssql extends DoliDB $query_comp[]=$fld->COLUMN_NAME." IS NOT NULL"; } } - if ($query_comp) + if (! empty($query_comp)) $query.=" WHERE ".implode(" AND ",$query_comp); } } diff --git a/htdocs/fichinter/index.php b/htdocs/fichinter/index.php index 3e629e05789..fc24d8c8326 100644 --- a/htdocs/fichinter/index.php +++ b/htdocs/fichinter/index.php @@ -120,7 +120,7 @@ if ($resql) foreach ($listofstatus as $status) { $dataseries[]=array('label'=>$fichinterstatic->LibStatut($status,$bool,1),'data'=>(isset($vals[$status.$bool])?(int) $vals[$status.$bool]:0)); - if ($status==3 && $bool==false) $bool=true; + if ($status==3 && ! $bool) $bool=true; else $bool=false; } if ($conf->use_javascript_ajax) @@ -144,7 +144,7 @@ if ($resql) print ''; print ''; print "\n"; - if ($status==3 && $bool==false) $bool=true; + if ($status==3 && ! $bool) $bool=true; else $bool=false; } } From acae888b3ab27dc7577896db36af45a5c3d86c03 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 23:38:07 +0200 Subject: [PATCH 33/61] Fix phpunits --- .../facture/class/facture-rec.class.php | 114 +++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/htdocs/compta/facture/class/facture-rec.class.php b/htdocs/compta/facture/class/facture-rec.class.php index cddd511dbe4..13c10a5da0c 100644 --- a/htdocs/compta/facture/class/facture-rec.class.php +++ b/htdocs/compta/facture/class/facture-rec.class.php @@ -760,8 +760,120 @@ class FactureRec extends CommonInvoice $arraynow=dol_getdate($now); $nownotime=dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']); - parent::initAsSpecimen($option); + $prodids = array(); + $sql = "SELECT rowid"; + $sql.= " FROM ".MAIN_DB_PREFIX."product"; + $sql.= " WHERE entity IN (".getEntity('product', 1).")"; + $resql = $this->db->query($sql); + if ($resql) + { + $num_prods = $this->db->num_rows($resql); + $i = 0; + while ($i < $num_prods) + { + $i++; + $row = $this->db->fetch_row($resql); + $prodids[$i] = $row[0]; + } + } + // Initialize parameters + $this->id=0; + $this->ref = 'SPECIMEN'; + $this->specimen=1; + $this->socid = 1; + $this->date = $nownotime; + $this->date_lim_reglement = $nownotime + 3600 * 24 *30; + $this->cond_reglement_id = 1; + $this->cond_reglement_code = 'RECEP'; + $this->date_lim_reglement=$this->calculate_date_lim_reglement(); + $this->mode_reglement_id = 0; // Not forced to show payment mode CHQ + VIR + $this->mode_reglement_code = ''; // Not forced to show payment mode CHQ + VIR + $this->note_public='This is a comment (public)'; + $this->note_private='This is a comment (private)'; + $this->note='This is a comment (private)'; + $this->fk_incoterms=0; + $this->location_incoterms=''; + + if (empty($option) || $option != 'nolines') + { + // Lines + $nbp = 5; + $xnbp = 0; + while ($xnbp < $nbp) + { + $line=new FactureLigne($this->db); + $line->desc=$langs->trans("Description")." ".$xnbp; + $line->qty=1; + $line->subprice=100; + $line->tva_tx=19.6; + $line->localtax1_tx=0; + $line->localtax2_tx=0; + $line->remise_percent=0; + if ($xnbp == 1) // Qty is negative (product line) + { + $prodid = mt_rand(1, $num_prods); + $line->fk_product=$prodids[$prodid]; + $line->qty=-1; + $line->total_ht=-100; + $line->total_ttc=-119.6; + $line->total_tva=-19.6; + } + else if ($xnbp == 2) // UP is negative (free line) + { + $line->subprice=-100; + $line->total_ht=-100; + $line->total_ttc=-119.6; + $line->total_tva=-19.6; + $line->remise_percent=0; + } + else if ($xnbp == 3) // Discount is 50% (product line) + { + $prodid = mt_rand(1, $num_prods); + $line->fk_product=$prodids[$prodid]; + $line->total_ht=50; + $line->total_ttc=59.8; + $line->total_tva=9.8; + $line->remise_percent=50; + } + else // (product line) + { + $prodid = mt_rand(1, $num_prods); + $line->fk_product=$prodids[$prodid]; + $line->total_ht=100; + $line->total_ttc=119.6; + $line->total_tva=19.6; + $line->remise_percent=00; + } + + $this->lines[$xnbp]=$line; + $xnbp++; + + $this->total_ht += $line->total_ht; + $this->total_tva += $line->total_tva; + $this->total_ttc += $line->total_ttc; + } + $this->revenuestamp = 0; + + // Add a line "offered" + $line=new FactureLigne($this->db); + $line->desc=$langs->trans("Description")." (offered line)"; + $line->qty=1; + $line->subprice=100; + $line->tva_tx=19.6; + $line->localtax1_tx=0; + $line->localtax2_tx=0; + $line->remise_percent=100; + $line->total_ht=0; + $line->total_ttc=0; // 90 * 1.196 + $line->total_tva=0; + $prodid = mt_rand(1, $num_prods); + $line->fk_product=$prodids[$prodid]; + + $this->lines[$xnbp]=$line; + $xnbp++; + } + $this->usenewprice = 1; } From cd75b43d581000e29bbf04f382679d3f15b8e343 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 00:44:55 +0200 Subject: [PATCH 34/61] Can enable modules during migration --- htdocs/install/upgrade2.php | 41 ++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/htdocs/install/upgrade2.php b/htdocs/install/upgrade2.php index 9ee3d842e51..d3eb23e99b6 100644 --- a/htdocs/install/upgrade2.php +++ b/htdocs/install/upgrade2.php @@ -63,6 +63,7 @@ $setuplang=GETPOST("selectlang",'',3)?GETPOST("selectlang",'',3):'auto'; $langs->setDefaultLang($setuplang); $versionfrom=GETPOST("versionfrom",'',3)?GETPOST("versionfrom",'',3):(empty($argv[1])?'':$argv[1]); $versionto=GETPOST("versionto",'',3)?GETPOST("versionto",'',3):(empty($argv[2])?'':$argv[2]); +$enablemodules=GETPOST("enablemodules",'',3)?GETPOST("enablemodules",'',3):(empty($argv[3])?'':$argv[3]); $langs->load('admin'); $langs->load('install'); @@ -84,9 +85,9 @@ if (! is_object($conf)) dolibarr_install_syslog("upgrade2: conf file not initial * View */ -if (! $versionfrom && ! $versionto) +if ((! $versionfrom || preg_match('/version/', $versionfrom)) && (! $versionto || preg_match('/version/', $versionto))) { - print 'Error: Parameter versionfrom or versionto missing.'."\n"; + print 'Error: Parameter versionfrom or versionto missing or having a bad format.'."\n"; print 'Upgrade must be ran from cmmand line with parameters or called from page install/index.php (like a first install) instead of page install/upgrade.php'."\n"; // Test if batch mode $sapi_type = php_sapi_name(); @@ -416,6 +417,21 @@ if (! GETPOST("action") || preg_match('/upgrade/i',GETPOST('action'))) migrate_reload_menu($db,$langs,$conf,$versionto); } + + // Can force activation of some module during migration with third paramater = MAIN_MODULE_XXX,MAIN_MODULE_YYY,... + if ($enablemodules) + { + // Reload modules (this must be always and only into last targeted version) + $listofmodules=array(); + $tmplistofmodules=explode(',', $enablemodules); + foreach($tmplistofmodules as $value) + { + $listofmodules[$value]='newboxdefonly'; + } + migrate_reload_modules($db,$langs,$conf,$listofmodules,1); + } + + print ''; // On commit dans tous les cas. @@ -3776,11 +3792,12 @@ function migrate_delete_old_dir($db,$langs,$conf) * @param Translate $langs Object langs * @param Conf $conf Object conf * @param array $listofmodule List of modules + * @param int $force 1=Reload module even if not already loaded * @return void */ -function migrate_reload_modules($db,$langs,$conf,$listofmodule=array()) +function migrate_reload_modules($db,$langs,$conf,$listofmodule=array(),$force=0) { - dolibarr_install_syslog("upgrade2::migrate_reload_modules"); + dolibarr_install_syslog("upgrade2::migrate_reload_modules force=".$force); // If no info is provided, we reload all modules with mode newboxdefonly. if (count($listofmodule) == 0) @@ -3805,7 +3822,7 @@ function migrate_reload_modules($db,$langs,$conf,$listofmodule=array()) foreach($listofmodule as $moduletoreload => $reloadmode) { - if (empty($moduletoreload) || empty($conf->global->$moduletoreload)) continue; + if (empty($moduletoreload) || (empty($conf->global->$moduletoreload) && ! $force)) continue; // Discard reload if module not enabled $mod=null; @@ -3819,7 +3836,17 @@ function migrate_reload_modules($db,$langs,$conf,$listofmodule=array()) $mod->init($reloadmode); } } - if ($moduletoreload == 'MAIN_MODULE_BARCODE') + if ($moduletoreload == 'MAIN_MODULE_API') + { + dolibarr_install_syslog("upgrade2::migrate_reload_modules Reactivate Rest API module"); + $res=@include_once DOL_DOCUMENT_ROOT.'/core/modules/modApi.class.php'; + if ($res) { + $mod=new modApi($db); + //$mod->remove('noboxes'); + $mod->init($reloadmode); + } + } + if ($moduletoreload == 'MAIN_MODULE_BARCODE') { dolibarr_install_syslog("upgrade2::migrate_reload_modules Reactivate Barcode module"); $res=@include_once DOL_DOCUMENT_ROOT.'/core/modules/modBarcode.class.php'; @@ -3974,7 +4001,7 @@ function migrate_reload_modules($db,$langs,$conf,$listofmodule=array()) { print ''; From e6196f69cdabd6173da1938ad85f340ae3dd9a06 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 00:45:13 +0200 Subject: [PATCH 35/61] Fix Restler bug returning warnings --- dev/dolibarr_changes.txt | 13 ++++++++++--- .../framework/Luracast/Restler/AutoLoader.php | 3 +++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/dev/dolibarr_changes.txt b/dev/dolibarr_changes.txt index a7aed128151..82f364b0c1f 100644 --- a/dev/dolibarr_changes.txt +++ b/dev/dolibarr_changes.txt @@ -116,8 +116,15 @@ JQUERYFILETREE: RESTLER: -------- - if ($className == 'Luracast\Restler\string') return; - if ($className == 'Luracast\Restler\mixed') return; - +* Add 2 lines into function + private function alias($className, $currentClass) + { + ... +to get + private function alias($className, $currentClass) + { + if ($className == 'Luracast\Restler\string') return; + if ($className == 'Luracast\Restler\mixed') return; + ... \ No newline at end of file diff --git a/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php b/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php index 8f2ec3bf339..c2e454346ee 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php @@ -307,6 +307,9 @@ class AutoLoader */ private function alias($className, $currentClass) { + if ($className == 'Luracast\Restler\string') return; + if ($className == 'Luracast\Restler\mixed') return; + if ($className != $currentClass && false !== strpos($className, $currentClass)) if (!class_exists($currentClass, false) From 08549304f1ef9fdd84937661c9e26d0e5b52ed2c Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 00:51:15 +0200 Subject: [PATCH 36/61] Enhance travis to have phpunit test on modules not available on old version by enabling them at end of migration. --- .travis.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0f14e0a49ca..e5d0025d3ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -269,20 +269,20 @@ script: set +e cd htdocs/install php upgrade.php 3.5.0 3.6.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade350360.log - php upgrade2.php 3.5.0 3.6.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade350360-2.log - php step5.php 3.5.0 3.6.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade350360-3.log + php upgrade2.php 3.5.0 3.6.0 > $TRAVIS_BUILD_DIR/upgrade350360-2.log + php step5.php 3.5.0 3.6.0 > $TRAVIS_BUILD_DIR/upgrade350360-3.log php upgrade.php 3.6.0 3.7.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade360370.log - php upgrade2.php 3.6.0 3.7.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade360370-2.log - php step5.php 3.6.0 3.7.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade360370-3.log + php upgrade2.php 3.6.0 3.7.0 > $TRAVIS_BUILD_DIR/upgrade360370-2.log + php step5.php 3.6.0 3.7.0 > $TRAVIS_BUILD_DIR/upgrade360370-3.log php upgrade.php 3.7.0 3.8.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade370380.log - php upgrade2.php 3.7.0 3.8.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade370380-2.log - php step5.php 3.7.0 3.8.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade370380-3.log + php upgrade2.php 3.7.0 3.8.0 > $TRAVIS_BUILD_DIR/upgrade370380-2.log + php step5.php 3.7.0 3.8.0 > $TRAVIS_BUILD_DIR/upgrade370380-3.log php upgrade.php 3.8.0 3.9.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade380390.log - php upgrade2.php 3.8.0 3.9.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade380390-2.log - php step5.php 3.8.0 3.9.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade380390-3.log + php upgrade2.php 3.8.0 3.9.0 > $TRAVIS_BUILD_DIR/upgrade380390-2.log + php step5.php 3.8.0 3.9.0 > $TRAVIS_BUILD_DIR/upgrade380390-3.log php upgrade.php 3.9.0 4.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade390400.log - php upgrade2.php 3.9.0 4.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade390400-2.log - php step5.php 3.9.0 4.0.0 ignoredbversion > $TRAVIS_BUILD_DIR/upgrade390400-3.log + php upgrade2.php 3.9.0 4.0.0 MAIN_MODULE_API > $TRAVIS_BUILD_DIR/upgrade390400-2.log + php step5.php 3.9.0 4.0.0 > $TRAVIS_BUILD_DIR/upgrade390400-3.log cd - set +e echo From f00414e4f7eaf7e0f67a52295b4eb4a1103c8e16 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 10:50:42 +0200 Subject: [PATCH 37/61] Show travis log --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e5d0025d3ea..4810502852f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -286,7 +286,9 @@ script: cd - set +e echo - + echo Log for upgrade2.php 3.9.0 4.0.0 MAIN_MODULE_API + cat $TRAVIS_BUILD_DIR/upgrade390400-2.log + - | echo "Unit testing" # Ensure we catch errors. Set this to +e if you want to go to the end to see log file. From ac361080fa299452f52f7ed769e9062cf8663089 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 10:53:27 +0200 Subject: [PATCH 38/61] Debug travis error --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4810502852f..2c2e34eb4b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -288,6 +288,7 @@ script: echo echo Log for upgrade2.php 3.9.0 4.0.0 MAIN_MODULE_API cat $TRAVIS_BUILD_DIR/upgrade390400-2.log + cat /tmp/dolibarr_install.log - | echo "Unit testing" From 236ecf01b640560f184a44ec78c384d3b1091fe8 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 10:57:03 +0200 Subject: [PATCH 39/61] Help debug travis --- test/phpunit/RestAPIUserTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/phpunit/RestAPIUserTest.php b/test/phpunit/RestAPIUserTest.php index 466c3cab782..189c17a0bd3 100644 --- a/test/phpunit/RestAPIUserTest.php +++ b/test/phpunit/RestAPIUserTest.php @@ -114,7 +114,7 @@ class RestAPIUserTest extends PHPUnit_Framework_TestCase $url=$this->api_url.'/login?login='.$login.'&password='.$password; // Call the API login method to save api_key for this test class $result=getURLContent($url, 'GET', '', 1, array()); - //print __METHOD__." result = ".var_export($result, true)."\n"; + print __METHOD__." result = ".var_export($result, true)."\n"; print __METHOD__." curl_error_no: ".$result['curl_error_no']."\n"; $this->assertEquals($result['curl_error_no'],''); $object=json_decode($result['content'], true); From 9114513df25701bb314cc87ca84646e85c56a663 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 11:42:33 +0200 Subject: [PATCH 40/61] Revert "remove composer.lock" This reverts commit 7ec8557db9e83a3a0ea4c55d1aa0b327133bb9e1. --- .gitignore | 1 - composer.lock | 420 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index ca0334a63e9..1efe4f4bb82 100755 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ Thumbs.db # Vagrant generated files .vagrant # Composer installed repositories -composer.lock /htdocs/includes/**/.git # Composer autoloader and unwanted files htdocs/includes/autoload.php diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000000..43a9f1d2d7c --- /dev/null +++ b/composer.lock @@ -0,0 +1,420 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "c2b53c577364dbe3a56137043081b511", + "content-hash": "8f7a86cfbc13f45e13b73c49531818cb", + "packages": [ + { + "name": "ccampbell/chromephp", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/ccampbell/chromephp.git", + "reference": "c3c297615d48ae5b2a86a82311152d1ed095fcef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ccampbell/chromephp/zipball/c3c297615d48ae5b2a86a82311152d1ed095fcef", + "reference": "c3c297615d48ae5b2a86a82311152d1ed095fcef", + "shasum": "" + }, + "require": { + "php": ">=5.0.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "ChromePhp": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Craig Campbell", + "email": "iamcraigcampbell@gmail.com", + "homepage": "http://craig.is", + "role": "Developer" + } + ], + "description": "Log variables to the Chrome console (via Chrome Logger Google Chrome extension).", + "homepage": "http://github.com/ccampbell/chromephp", + "keywords": [ + "log", + "logging" + ], + "time": "2013-06-26 03:44:33" + }, + { + "name": "ckeditor/ckeditor", + "version": "dev-full/stable", + "source": { + "type": "git", + "url": "https://github.com/ckeditor/ckeditor-releases.git", + "reference": "e3eb254641c4c349ffc19e49bd4a1a6831b5b6d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ckeditor/ckeditor-releases/zipball/e3eb254641c4c349ffc19e49bd4a1a6831b5b6d0", + "reference": "e3eb254641c4c349ffc19e49bd4a1a6831b5b6d0", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+", + "LGPL-2.1+", + "MPL-1.1+" + ], + "authors": [ + { + "name": "CKSource", + "homepage": "http://cksource.com" + } + ], + "description": "JavaScript WYSIWYG web text editor.", + "homepage": "http://ckeditor.com", + "keywords": [ + "CKEditor", + "editor", + "fckeditor", + "html", + "javascript", + "richtext", + "text", + "wysiwyg" + ], + "time": "2016-03-31 16:19:25" + }, + { + "name": "mike42/escpos-php", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/mike42/escpos-php.git", + "reference": "96f05cbf460f5b67c2184ee4e91aedfbcedeb788" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mike42/escpos-php/zipball/96f05cbf460f5b67c2184ee4e91aedfbcedeb788", + "reference": "96f05cbf460f5b67c2184ee4e91aedfbcedeb788", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.5.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mike42\\": "src/Mike42" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roni Saha", + "email": "roni.cse@gmail.com" + }, + { + "name": "Michael Billington", + "email": "michael.billington@gmail.com" + }, + { + "name": "Gergely Radics", + "email": "gerifield@ustream.tv" + }, + { + "name": "Warren Doyle", + "email": "w.doyle@fuelled.co" + }, + { + "name": "vharo", + "email": "vharo@geepok.com" + } + ], + "description": "Thermal receipt printer library, for use with ESC/POS compatible printers", + "homepage": "https://github.com/mike42/escpos-php", + "keywords": [ + "ESC-POS", + "driver", + "escpos", + "print", + "receipt" + ], + "time": "2016-03-27 23:08:27" + }, + { + "name": "mobiledetect/mobiledetectlib", + "version": "2.8.17", + "source": { + "type": "git", + "url": "https://github.com/serbanghita/Mobile-Detect.git", + "reference": "b87da5f63a76e9615a0c74fcf168657b1ea7e41d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/b87da5f63a76e9615a0c74fcf168657b1ea7e41d", + "reference": "b87da5f63a76e9615a0c74fcf168657b1ea7e41d", + "shasum": "" + }, + "require": { + "php": ">=5.0.0" + }, + "require-dev": { + "codeclimate/php-test-reporter": "dev-master", + "johnkary/phpunit-speedtrap": "~1.0@dev", + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "classmap": [ + "Mobile_Detect.php" + ], + "psr-0": { + "Detection": "namespaced/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Serban Ghita", + "email": "serbanghita@gmail.com", + "homepage": "http://mobiledetect.net", + "role": "Developer" + } + ], + "description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.", + "homepage": "https://github.com/serbanghita/Mobile-Detect", + "keywords": [ + "detect mobile devices", + "mobile", + "mobile detect", + "mobile detector", + "php mobile detect" + ], + "time": "2015-09-17 14:45:21" + }, + { + "name": "phpoffice/phpexcel", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PHPExcel.git", + "reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PHPExcel/zipball/372c7cbb695a6f6f1e62649381aeaa37e7e70b32", + "reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "ext-xmlwriter": "*", + "php": ">=5.2.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "PHPExcel": "Classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "http://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker" + }, + { + "name": "Franck Lefevre", + "homepage": "http://blog.rootslabs.net" + }, + { + "name": "Erik Tilt" + } + ], + "description": "PHPExcel - OpenXML - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "http://phpexcel.codeplex.com", + "keywords": [ + "OpenXML", + "excel", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "time": "2015-05-01 07:00:55" + }, + { + "name": "restler/framework", + "version": "3.0.0", + "target-dir": "Luracast/Restler", + "source": { + "type": "git", + "url": "https://github.com/Luracast/Restler-Framework.git", + "reference": "6ee10b3e5dbc6376916fed55ec2340a37cce436b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Luracast/Restler-Framework/zipball/6ee10b3e5dbc6376916fed55ec2340a37cce436b", + "reference": "6ee10b3e5dbc6376916fed55ec2340a37cce436b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "replace": { + "luracast/restler": "3.*" + }, + "require-dev": { + "bshaffer/oauth2-server-php": "v1.0", + "luracast/explorer": "*", + "mustache/mustache": "dev-master", + "rodneyrehm/plist": "dev-master", + "symfony/yaml": "*", + "twig/twig": "v1.13.0", + "zendframework/zendamf": "dev-master" + }, + "suggest": { + "bshaffer/oauth2-server-php": "Restler can provide OAuth2 authentication using this library (see require-dev for details)", + "luracast/explorer": "Restler's very own api explorer (see require-dev for details)", + "mustache/mustache": "Restler can render HtmlView using mustache/handlebar templates (see require-dev for details)", + "rodneyrehm/plist": "Restler supports tho Apple plist xml format (see require-dev for details)", + "symfony/yaml": "Restler can produce content in yaml format as well (see require-dev for details)", + "twig/twig": "Restler can render HtmlView using twig templates (see require-dev for details)", + "zendframework/zendamf": "Support for the amf document format (see require-dev for details)" + }, + "type": "library", + "extra": { + "branch-alias": { + "master": "v3.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Luracast\\Restler": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "Luracast", + "email": "arul@luracast.com" + }, + { + "name": "Nick nickl- Lombard", + "email": "github@jigsoft.co.za" + } + ], + "description": "Just the Restler Framework without the tests and examples", + "homepage": "http://luracast.com/products/restler/", + "keywords": [ + "api", + "framework", + "rest", + "server" + ], + "time": "2015-08-04 07:52:49" + }, + { + "name": "tecnickcom/tcpdf", + "version": "6.2.12", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/TCPDF.git", + "reference": "2f732eaa91b5665274689b1d40b285a7bacdc37f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/2f732eaa91b5665274689b1d40b285a7bacdc37f", + "reference": "2f732eaa91b5665274689b1d40b285a7bacdc37f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "fonts", + "config", + "include", + "tcpdf.php", + "tcpdf_parser.php", + "tcpdf_import.php", + "tcpdf_barcodes_1d.php", + "tcpdf_barcodes_2d.php", + "include/tcpdf_colors.php", + "include/tcpdf_filters.php", + "include/tcpdf_font_data.php", + "include/tcpdf_fonts.php", + "include/tcpdf_images.php", + "include/tcpdf_static.php", + "include/barcodes/datamatrix.php", + "include/barcodes/pdf417.php", + "include/barcodes/qrcode.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPLv3" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "homepage": "http://nicolaasuni.tecnick.com" + } + ], + "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", + "homepage": "http://www.tcpdf.org/", + "keywords": [ + "PDFD32000-2008", + "TCPDF", + "barcodes", + "datamatrix", + "pdf", + "pdf417", + "qrcode" + ], + "time": "2015-09-12 10:08:34" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "ckeditor/ckeditor": 20, + "mike42/escpos-php": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.0", + "ext-curl": "*" + }, + "platform-dev": [] +} From e7385c9e4f943663edbf84749c704eed25732da2 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 11:57:13 +0200 Subject: [PATCH 41/61] Try to solve side effect of this fuck... autoload creating a big mess in a lot of features. --- .travis.yml | 10 ++-------- htdocs/.gitignore | 2 +- .../restler/framework/Luracast/Restler/AutoLoader.php | 1 + 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c2e34eb4b8..3052d5ad84c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -103,11 +103,6 @@ install: composer require squizlabs/php_codesniffer ^2 echo -- | - echo "Adding Composer binaries to the path" - export PATH="$TRAVIS_BUILD_DIR/htdocs/includes/bin:$PATH" - echo - before_script: - | echo Start travis @@ -286,9 +281,8 @@ script: cd - set +e echo - echo Log for upgrade2.php 3.9.0 4.0.0 MAIN_MODULE_API - cat $TRAVIS_BUILD_DIR/upgrade390400-2.log - cat /tmp/dolibarr_install.log + #cat $TRAVIS_BUILD_DIR/upgrade390400-2.log + #cat /tmp/dolibarr_install.log - | echo "Unit testing" diff --git a/htdocs/.gitignore b/htdocs/.gitignore index 050707daf91..608ef55d106 100644 --- a/htdocs/.gitignore +++ b/htdocs/.gitignore @@ -20,5 +20,5 @@ /allscreens* /ecommerce/ /cabinetmed* -/conf/conf.php /webmail* +/conf/conf.php diff --git a/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php b/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php index c2e454346ee..9639ab44fb7 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php @@ -307,6 +307,7 @@ class AutoLoader */ private function alias($className, $currentClass) { + // DOL_LDR if ($className == 'Luracast\Restler\string') return; if ($className == 'Luracast\Restler\mixed') return; From 83f3b7305ac6bf0472e1c24725097cc79dc2431e Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 12:26:46 +0200 Subject: [PATCH 42/61] Fix travis file --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3052d5ad84c..ef9a7f51d32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -103,6 +103,11 @@ install: composer require squizlabs/php_codesniffer ^2 echo +- | + echo "Adding path of binaries tools installed by composer to the PATH" + export PATH="$TRAVIS_BUILD_DIR/htdocs/includes/bin:$PATH" + echo + before_script: - | echo Start travis From ea8bb1f0221b1e197eeeb160f6c5491530666fc5 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 12:27:45 +0200 Subject: [PATCH 43/61] Fix composer.lock --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 43a9f1d2d7c..df333f5571c 100644 --- a/composer.lock +++ b/composer.lock @@ -267,7 +267,7 @@ }, { "name": "restler/framework", - "version": "3.0.0", + "version": "3.0.0-RC6", "target-dir": "Luracast/Restler", "source": { "type": "git", From e571741ef8964c8f4a19ebac4287d31afbcc4c75 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 12:58:50 +0200 Subject: [PATCH 44/61] Add doc on composer --- build/composer/README | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 build/composer/README diff --git a/build/composer/README b/build/composer/README new file mode 100644 index 00000000000..2464b0d246e --- /dev/null +++ b/build/composer/README @@ -0,0 +1,5 @@ + +To upgrade a lib with composer: + +composer update --no-dev --no-autoloader --dry-run ccampbell/chromephp + From 9f20779136cb12bb1d5fe08cbd871d682022d44e Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 13:18:55 +0200 Subject: [PATCH 45/61] Revert "Update escpos-php" This reverts commit 2f42a226d0db3ce0ca6b543668d525a3581fcc70. --- htdocs/includes/mike42/escpos-php/.gitignore | 3 - htdocs/includes/mike42/escpos-php/.travis.yml | 20 - htdocs/includes/mike42/escpos-php/Escpos.php | 851 ++++++++++++++++++ htdocs/includes/mike42/escpos-php/README.md | 285 ++---- .../includes/mike42/escpos-php/autoload.php | 26 - .../includes/mike42/escpos-php/composer.json | 5 - .../mike42/escpos-php/example/README.md | 26 + .../mike42/escpos-php/example/barcode.php | 181 ++++ .../mike42/escpos-php/example/bit-image.php | 32 + .../character-encodings-with-images.php | 59 ++ .../example/character-encodings.php | 58 ++ .../escpos-php/example/character-tables.php | 71 ++ .../mike42/escpos-php/example/demo.php | 167 ++++ .../mike42/escpos-php/example/graphics.php | 32 + .../escpos-php/example/interface/README.md | 8 + .../escpos-php/example/interface/ethernet.php | 22 + .../example/interface/linux-usb.php | 33 + .../escpos-php/example/interface/smb.php | 51 ++ .../example/interface/windows-lpt.php | 30 + .../example/interface/windows-usb.php | 32 + .../escpos-php/example/print-from-html.php | 53 ++ .../escpos-php/example/print-from-pdf.php | 71 ++ .../mike42/escpos-php/example/qr-code.php | 81 ++ .../escpos-php/example/receipt-with-logo.php | 96 ++ .../character-encoding-test-strings.inc | 35 + .../escpos-php/example/resources/document.odt | Bin 0 -> 39751 bytes .../escpos-php/example/resources/document.pdf | Bin 0 -> 24389 bytes .../escpos-php/example/resources/document.z | Bin 0 -> 20320 bytes .../example/resources/escpos-php-small.png | Bin 0 -> 1941 bytes .../example/resources/escpos-php.png | Bin 0 -> 7871 bytes .../escpos-php/example/resources/tux.png | Bin 0 -> 5198 bytes .../specific/29-latvian-star-tup592.php | 21 + .../32-german-tm-t20-ii-custom-command.php | 36 + .../specific/33-spanish-seypos-prp-300.php | 16 + .../example/specific/39-currency-symbols.php | 69 ++ .../specific/44-pound-symbol-star-tsp650.php | 31 + .../example/specific/50-P-822D-greek.php | 16 + .../example/specific/54-gfx-sidebyside.php | 58 ++ .../specific/6-arabic-epos-tep-220m.php | 47 + .../escpos-php/example/specific/README.md | 7 + .../mike42/escpos-php/example/text-size.php | 62 ++ .../src/AbstractCapabilityProfile.php | 61 ++ .../mike42/escpos-php/src/CodePage.php | 181 ++++ .../src/DefaultCapabilityProfile.php | 103 +++ .../escpos-php/src/DummyPrintConnector.php | 78 ++ .../src/EposTepCapabilityProfile.php | 4 + .../mike42/escpos-php/src/EscposImage.php | 405 +++++++++ .../escpos-php/src/EscposPrintBuffer.php | 304 +++++++ .../escpos-php/src/FilePrintConnector.php | 80 ++ .../escpos-php/src/ImagePrintBuffer.php | 99 ++ .../escpos-php/src/NetworkPrintConnector.php | 39 + .../escpos-php/src/P822DCapabilityProfile.php | 90 ++ .../mike42/escpos-php/src/PrintBuffer.php | 75 ++ .../mike42/escpos-php/src/PrintConnector.php | 56 ++ .../src/SimpleCapabilityProfile.php | 17 + .../escpos-php/src/StarCapabilityProfile.php | 82 ++ .../escpos-php/src/WindowsPrintConnector.php | 356 ++++++++ ...Characters-DefaultCapabilityProfile.ser.gz | Bin 0 -> 12928 bytes .../Characters-SimpleCapabilityProfile.ser.gz | Bin 0 -> 1116 bytes .../Characters-StarCapabilityProfile.ser.gz | Bin 0 -> 9416 bytes .../mike42/escpos-php/test/bootstrap.php | 27 + .../test/integration/ExampleTest.php | 126 +++ .../integration/resources/output/barcode.bin | Bin 0 -> 2342 bytes .../resources/output/bit-image.bin | Bin 0 -> 9789 bytes .../resources/output/character-encodings.bin | Bin 0 -> 1780 bytes .../resources/output/character-tables.bin | Bin 0 -> 8174 bytes .../integration/resources/output/demo.bin | Bin 0 -> 73643 bytes .../integration/resources/output/graphics.bin | Bin 0 -> 9635 bytes .../resources/output/interface.bin | 2 + .../resources/output/print-from-pdf.bin.z | Bin 0 -> 17081 bytes .../integration/resources/output/qr-code.bin | Bin 0 -> 1551 bytes .../resources/output/receipt-with-logo.bin | Bin 0 -> 9579 bytes .../resources/output/text-size.bin | Bin 0 -> 368 bytes .../mike42/escpos-php/test/phpunit.xml | 16 + .../unit/AbstractCapabilityProfileTest.php | 69 ++ .../escpos-php/test/unit/EscposImageTest.php | 235 +++++ .../test/unit/EscposPrintBufferTest.php | 150 +++ .../escpos-php/test/unit/EscposTest.php | 765 ++++++++++++++++ .../test/unit/WindowsPrintConnectorTest.php | 278 ++++++ .../test/unit/resources/black_transparent.gif | Bin 0 -> 65 bytes .../test/unit/resources/black_transparent.png | Bin 0 -> 167 bytes .../test/unit/resources/black_white.bmp | Bin 0 -> 138 bytes .../test/unit/resources/black_white.gif | Bin 0 -> 65 bytes .../test/unit/resources/black_white.jpg | Bin 0 -> 175 bytes .../test/unit/resources/black_white.png | Bin 0 -> 156 bytes .../test/unit/resources/canvas_black.bmp | Bin 0 -> 142 bytes .../test/unit/resources/canvas_black.gif | Bin 0 -> 72 bytes .../test/unit/resources/canvas_black.jpg | Bin 0 -> 160 bytes .../test/unit/resources/canvas_black.png | Bin 0 -> 239 bytes .../test/unit/resources/canvas_white.bmp | Bin 0 -> 142 bytes .../test/unit/resources/canvas_white.gif | Bin 0 -> 72 bytes .../test/unit/resources/canvas_white.jpg | Bin 0 -> 160 bytes .../test/unit/resources/canvas_white.png | Bin 0 -> 239 bytes .../escpos-php/test/unit/resources/demo.php | 19 + .../escpos-php/test/unit/resources/doc.pdf | Bin 0 -> 6681 bytes 95 files changed, 6156 insertions(+), 252 deletions(-) delete mode 100644 htdocs/includes/mike42/escpos-php/.travis.yml create mode 100644 htdocs/includes/mike42/escpos-php/Escpos.php delete mode 100644 htdocs/includes/mike42/escpos-php/autoload.php create mode 100644 htdocs/includes/mike42/escpos-php/example/README.md create mode 100644 htdocs/includes/mike42/escpos-php/example/barcode.php create mode 100644 htdocs/includes/mike42/escpos-php/example/bit-image.php create mode 100644 htdocs/includes/mike42/escpos-php/example/character-encodings-with-images.php create mode 100644 htdocs/includes/mike42/escpos-php/example/character-encodings.php create mode 100644 htdocs/includes/mike42/escpos-php/example/character-tables.php create mode 100644 htdocs/includes/mike42/escpos-php/example/demo.php create mode 100644 htdocs/includes/mike42/escpos-php/example/graphics.php create mode 100644 htdocs/includes/mike42/escpos-php/example/interface/README.md create mode 100644 htdocs/includes/mike42/escpos-php/example/interface/ethernet.php create mode 100644 htdocs/includes/mike42/escpos-php/example/interface/linux-usb.php create mode 100644 htdocs/includes/mike42/escpos-php/example/interface/smb.php create mode 100644 htdocs/includes/mike42/escpos-php/example/interface/windows-lpt.php create mode 100644 htdocs/includes/mike42/escpos-php/example/interface/windows-usb.php create mode 100644 htdocs/includes/mike42/escpos-php/example/print-from-html.php create mode 100644 htdocs/includes/mike42/escpos-php/example/print-from-pdf.php create mode 100644 htdocs/includes/mike42/escpos-php/example/qr-code.php create mode 100644 htdocs/includes/mike42/escpos-php/example/receipt-with-logo.php create mode 100644 htdocs/includes/mike42/escpos-php/example/resources/character-encoding-test-strings.inc create mode 100644 htdocs/includes/mike42/escpos-php/example/resources/document.odt create mode 100644 htdocs/includes/mike42/escpos-php/example/resources/document.pdf create mode 100644 htdocs/includes/mike42/escpos-php/example/resources/document.z create mode 100644 htdocs/includes/mike42/escpos-php/example/resources/escpos-php-small.png create mode 100644 htdocs/includes/mike42/escpos-php/example/resources/escpos-php.png create mode 100644 htdocs/includes/mike42/escpos-php/example/resources/tux.png create mode 100644 htdocs/includes/mike42/escpos-php/example/specific/29-latvian-star-tup592.php create mode 100644 htdocs/includes/mike42/escpos-php/example/specific/32-german-tm-t20-ii-custom-command.php create mode 100644 htdocs/includes/mike42/escpos-php/example/specific/33-spanish-seypos-prp-300.php create mode 100644 htdocs/includes/mike42/escpos-php/example/specific/39-currency-symbols.php create mode 100644 htdocs/includes/mike42/escpos-php/example/specific/44-pound-symbol-star-tsp650.php create mode 100644 htdocs/includes/mike42/escpos-php/example/specific/50-P-822D-greek.php create mode 100644 htdocs/includes/mike42/escpos-php/example/specific/54-gfx-sidebyside.php create mode 100644 htdocs/includes/mike42/escpos-php/example/specific/6-arabic-epos-tep-220m.php create mode 100644 htdocs/includes/mike42/escpos-php/example/specific/README.md create mode 100644 htdocs/includes/mike42/escpos-php/example/text-size.php create mode 100644 htdocs/includes/mike42/escpos-php/src/AbstractCapabilityProfile.php create mode 100644 htdocs/includes/mike42/escpos-php/src/CodePage.php create mode 100644 htdocs/includes/mike42/escpos-php/src/DefaultCapabilityProfile.php create mode 100644 htdocs/includes/mike42/escpos-php/src/DummyPrintConnector.php create mode 100644 htdocs/includes/mike42/escpos-php/src/EposTepCapabilityProfile.php create mode 100644 htdocs/includes/mike42/escpos-php/src/EscposImage.php create mode 100644 htdocs/includes/mike42/escpos-php/src/EscposPrintBuffer.php create mode 100644 htdocs/includes/mike42/escpos-php/src/FilePrintConnector.php create mode 100644 htdocs/includes/mike42/escpos-php/src/ImagePrintBuffer.php create mode 100644 htdocs/includes/mike42/escpos-php/src/NetworkPrintConnector.php create mode 100644 htdocs/includes/mike42/escpos-php/src/P822DCapabilityProfile.php create mode 100644 htdocs/includes/mike42/escpos-php/src/PrintBuffer.php create mode 100644 htdocs/includes/mike42/escpos-php/src/PrintConnector.php create mode 100644 htdocs/includes/mike42/escpos-php/src/SimpleCapabilityProfile.php create mode 100644 htdocs/includes/mike42/escpos-php/src/StarCapabilityProfile.php create mode 100644 htdocs/includes/mike42/escpos-php/src/WindowsPrintConnector.php create mode 100644 htdocs/includes/mike42/escpos-php/src/cache/Characters-DefaultCapabilityProfile.ser.gz create mode 100644 htdocs/includes/mike42/escpos-php/src/cache/Characters-SimpleCapabilityProfile.ser.gz create mode 100644 htdocs/includes/mike42/escpos-php/src/cache/Characters-StarCapabilityProfile.ser.gz create mode 100644 htdocs/includes/mike42/escpos-php/test/bootstrap.php create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/ExampleTest.php create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/barcode.bin create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/bit-image.bin create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/character-encodings.bin create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/character-tables.bin create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/demo.bin create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/graphics.bin create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/interface.bin create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/print-from-pdf.bin.z create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/qr-code.bin create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/receipt-with-logo.bin create mode 100644 htdocs/includes/mike42/escpos-php/test/integration/resources/output/text-size.bin create mode 100644 htdocs/includes/mike42/escpos-php/test/phpunit.xml create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/AbstractCapabilityProfileTest.php create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/EscposImageTest.php create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/EscposPrintBufferTest.php create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/EscposTest.php create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/WindowsPrintConnectorTest.php create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/black_transparent.gif create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/black_transparent.png create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.bmp create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.gif create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.jpg create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.png create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_black.bmp create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_black.gif create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_black.jpg create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_black.png create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_white.bmp create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_white.gif create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_white.jpg create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_white.png create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/demo.php create mode 100644 htdocs/includes/mike42/escpos-php/test/unit/resources/doc.pdf diff --git a/htdocs/includes/mike42/escpos-php/.gitignore b/htdocs/includes/mike42/escpos-php/.gitignore index 3fe7ed0ea16..0e8fc4a31ee 100644 --- a/htdocs/includes/mike42/escpos-php/.gitignore +++ b/htdocs/includes/mike42/escpos-php/.gitignore @@ -10,6 +10,3 @@ doc/doxygen_sqlite3.db # composer files vendor/ - -# cache -src/Mike42/Escpos/PrintBuffers/cache/ \ No newline at end of file diff --git a/htdocs/includes/mike42/escpos-php/.travis.yml b/htdocs/includes/mike42/escpos-php/.travis.yml deleted file mode 100644 index 969cd6aad15..00000000000 --- a/htdocs/includes/mike42/escpos-php/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: php - -php: - - 5.3 - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - hhvm - - nightly - -matrix: - allow_failures: - - php: nightly - -script: phpunit --configuration test/phpunit.xml --coverage-text - -install: - - composer install - diff --git a/htdocs/includes/mike42/escpos-php/Escpos.php b/htdocs/includes/mike42/escpos-php/Escpos.php new file mode 100644 index 00000000000..57e7eb2c8ac --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/Escpos.php @@ -0,0 +1,851 @@ +, + * incorporating modifications by: + * - Roni Saha + * - Gergely Radics + * - Warren Doyle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This class generates ESC/POS printer control commands for compatible printers. + * See README.md for a summary of compatible printers and supported commands, and + * basic usage. + * + * See example/demo.php for a detailed print-out demonstrating the range of commands + * implemented in this project. + * + * Note that some functions have not been implemented: + * - Set paper sensors + * - Select print colour + * + * Please direct feature requests, bug reports and contributions to escpos-php + * on Github: + * - https://github.com/mike42/escpos-php + */ +require_once(dirname(__FILE__) . "/src/EscposImage.php"); +require_once(dirname(__FILE__) . "/src/PrintBuffer.php"); +require_once(dirname(__FILE__) . "/src/EscposPrintBuffer.php"); +require_once(dirname(__FILE__) . "/src/PrintConnector.php"); +require_once(dirname(__FILE__) . "/src/WindowsPrintConnector.php"); +require_once(dirname(__FILE__) . "/src/FilePrintConnector.php"); +require_once(dirname(__FILE__) . "/src/NetworkPrintConnector.php"); +require_once(dirname(__FILE__) . "/src/AbstractCapabilityProfile.php"); +require_once(dirname(__FILE__) . "/src/DefaultCapabilityProfile.php"); +require_once(dirname(__FILE__) . "/src/SimpleCapabilityProfile.php"); +require_once(dirname(__FILE__) . "/src/EposTepCapabilityProfile.php"); +require_once(dirname(__FILE__) . "/src/StarCapabilityProfile.php"); +require_once(dirname(__FILE__) . "/src/P822DCapabilityProfile.php"); +require_once(dirname(__FILE__) . "/src/CodePage.php"); +require_once(dirname(__FILE__) . "/src/ImagePrintBuffer.php"); + +class Escpos { + /* ASCII codes */ + const NUL = "\x00"; + const LF = "\x0a"; + const ESC = "\x1b"; + const FS = "\x1c"; + const FF = "\x0c"; + const GS = "\x1d"; + const DLE = "\x10"; + const EOT = "\x04"; + + /* Barcode types */ + const BARCODE_UPCA = 65; + const BARCODE_UPCE = 66; + const BARCODE_JAN13 = 67; + const BARCODE_JAN8 = 68; + const BARCODE_CODE39 = 69; + const BARCODE_ITF = 70; + const BARCODE_CODABAR = 71; + const BARCODE_CODE93 = 72; + const BARCODE_CODE128 = 73; + + /* Barcode HRI (human-readable interpretation) text position */ + const BARCODE_TEXT_NONE = 0; + const BARCODE_TEXT_ABOVE = 1; + const BARCODE_TEXT_BELOW = 2; + + /* Cut types */ + const CUT_FULL = 65; + const CUT_PARTIAL = 66; + + /* Fonts */ + const FONT_A = 0; + const FONT_B = 1; + const FONT_C = 2; + + /* Image sizing options */ + const IMG_DEFAULT = 0; + const IMG_DOUBLE_WIDTH = 1; + const IMG_DOUBLE_HEIGHT = 2; + + /* Justifications */ + const JUSTIFY_LEFT = 0; + const JUSTIFY_CENTER = 1; + const JUSTIFY_RIGHT = 2; + + /* Print mode constants */ + const MODE_FONT_A = 0; + const MODE_FONT_B = 1; + const MODE_EMPHASIZED = 8; + const MODE_DOUBLE_HEIGHT = 16; + const MODE_DOUBLE_WIDTH = 32; + const MODE_UNDERLINE = 128; + + /* QR code error correction levels */ + const QR_ECLEVEL_L = 0; + const QR_ECLEVEL_M = 1; + const QR_ECLEVEL_Q = 2; + const QR_ECLEVEL_H = 3; + + /* QR code models */ + const QR_MODEL_1 = 1; + const QR_MODEL_2 = 2; + const QR_MICRO = 3; + + /* Printer statuses */ + const STATUS_PRINTER = 1; + const STATUS_OFFLINE_CAUSE = 2; + const STATUS_ERROR_CAUSE = 3; + const STATUS_PAPER_ROLL = 4; + const STATUS_INK_A = 7; + const STATUS_INK_B = 6; + const STATUS_PEELER = 8; + + /* Underline */ + const UNDERLINE_NONE = 0; + const UNDERLINE_SINGLE = 1; + const UNDERLINE_DOUBLE = 2; + + /** + * @var PrintBuffer The printer's output buffer. + */ + private $buffer; + + /** + * @var PrintConnector + */ + private $connector; + + /** + * @var AbstractCapabilityProfile + */ + private $profile; + + /** + * @var int Current character code table + */ + private $characterTable; + + /** + * Construct a new print object + * + * @param PrintConnector $connector The PrintConnector to send data to. If not set, output is sent to standard output. + * @param AbstractCapabilityProfile $profile Supported features of this printer. If not set, the DefaultCapabilityProfile will be used, which is suitable for Epson printers. + * @throws InvalidArgumentException + */ + function __construct(PrintConnector $connector = null, AbstractCapabilityProfile $profile = null) { + if(is_null($connector)) { + if(php_sapi_name() == 'cli') { + $connector = new FilePrintConnector("php://stdout"); + } else { + throw new InvalidArgumentException("Argument passed to Escpos::__construct() must implement interface PrintConnector, null given."); + } + } + /* Set connector */ + $this -> connector = $connector; + + /* Set capability profile */ + if($profile === null) { + $profile = DefaultCapabilityProfile::getInstance(); + } + $this -> profile = $profile; + /* Set buffer */ + $buffer = new EscposPrintBuffer(); + $this -> buffer = null; + $this -> setPrintBuffer($buffer); + $this -> initialize(); + } + + /** + * Print a barcode. + * + * @param string $content The information to encode. + * @param int $type The barcode standard to output. If not specified, `Escpos::BARCODE_CODE39` will be used. Note that some barcode formats only support specific lengths or sets of characters. + * @throws InvalidArgumentException Where the length or characters used in $content is invalid for the requested barcode format. + */ + function barcode($content, $type = self::BARCODE_CODE39) { + /* Validate input */ + self::validateInteger($type, 65, 73, __FUNCTION__, "Barcode type"); + $len = strlen($content); + switch($type) { + case self::BARCODE_UPCA: + self::validateInteger($len, 11, 12, __FUNCTION__, "UPCA barcode content length"); + self::validateStringRegex($content, __FUNCTION__, "/^[0-9]{11,12}$/", "UPCA barcode content"); + break; + case self::BARCODE_UPCE: + self::validateIntegerMulti($len, array(array(6, 8), array(11, 12)), __FUNCTION__, "UPCE barcode content length"); + self::validateStringRegex($content, __FUNCTION__, "/^([0-9]{6,8}|[0-9]{11,12})$/", "UPCE barcode content"); + break; + case self::BARCODE_JAN13: + self::validateInteger($len, 12, 13, __FUNCTION__, "JAN13 barcode content length"); + self::validateStringRegex($content, __FUNCTION__, "/^[0-9]{12,13}$/", "JAN13 barcode content"); + break; + case self::BARCODE_JAN8: + self::validateInteger($len, 7, 8, __FUNCTION__, "JAN8 barcode content length"); + self::validateStringRegex($content, __FUNCTION__, "/^[0-9]{7,8}$/", "JAN8 barcode content"); + break; + case self::BARCODE_CODE39: + self::validateInteger($len, 1, 255, __FUNCTION__, "CODE39 barcode content length"); // 255 is a limitation of the "function b" command, not the barcode format. + self::validateStringRegex($content, __FUNCTION__, "/^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$/", "CODE39 barcode content"); + break; + case self::BARCODE_ITF: + self::validateInteger($len, 2, 255, __FUNCTION__, "ITF barcode content length"); // 255 is a limitation of the "function b" command, not the barcode format. + self::validateStringRegex($content, __FUNCTION__, "/^([0-9]{2})+$/", "ITF barcode content"); + break; + case self::BARCODE_CODABAR: + self::validateInteger($len, 1, 255, __FUNCTION__, "Codabar barcode content length"); // 255 is a limitation of the "function b" command, not the barcode format. + self::validateStringRegex($content, __FUNCTION__, "/^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$/", "Codabar barcode content"); + break; + case self::BARCODE_CODE93: + self::validateInteger($len, 1, 255, __FUNCTION__, "Code93 barcode content length"); // 255 is a limitation of the "function b" command, not the barcode format. + self::validateStringRegex($content, __FUNCTION__, "/^[\\x00-\\x7F]+$/", "Code93 barcode content"); + break; + case self::BARCODE_CODE128: + self::validateInteger($len, 1, 255, __FUNCTION__, "Code128 barcode content length"); // 255 is a limitation of the "function b" command, not the barcode format. + // The CODE128 encoder is quite complex, so only a very basic header-check is applied here. + self::validateStringRegex($content, __FUNCTION__, "/^\{[A-C][\\x00-\\x7F]+$/", "Code128 barcode content"); + break; + } + if(!$this -> profile -> getSupportsBarcodeB()) { + // A simpler barcode command which supports fewer codes + self::validateInteger($type, 65, 71, __FUNCTION__); + $this -> connector -> write(self::GS . "k" . chr($type - 65) . $content . self::NUL); + return; + } + // More advanced function B, used in preference + $this -> connector -> write(self::GS . "k" . chr($type) . chr(strlen($content)) . $content); + } + + /** + * Print an image, using the older "bit image" command. This creates padding on the right of the image, + * if its width is not divisible by 8. + * + * Should only be used if your printer does not support the graphics() command. + * + * @param EscposImage $img The image to print + * @param EscposImage $size Size modifier for the image. + */ + function bitImage(EscposImage $img, $size = self::IMG_DEFAULT) { + self::validateInteger($size, 0, 3, __FUNCTION__); + $header = self::dataHeader(array($img -> getWidthBytes(), $img -> getHeight()), true); + $this -> connector -> write(self::GS . "v0" . chr($size) . $header); + $this -> connector -> write($img -> toRasterFormat()); + } + + /** + * Close the underlying buffer. With some connectors, the + * job will not actually be sent to the printer until this is called. + */ + function close() { + $this -> connector -> finalize(); + } + + /** + * Cut the paper. + * + * @param int $mode Cut mode, either Escpos::CUT_FULL or Escpos::CUT_PARTIAL. If not specified, `Escpos::CUT_FULL` will be used. + * @param int $lines Number of lines to feed + */ + function cut($mode = self::CUT_FULL, $lines = 3) { + // TODO validation on cut() inputs + $this -> connector -> write(self::GS . "V" . chr($mode) . chr($lines)); + } + + /** + * Print and feed line / Print and feed n lines. + * + * @param int $lines Number of lines to feed + */ + function feed($lines = 1) { + self::validateInteger($lines, 1, 255, __FUNCTION__); + if($lines <= 1) { + $this -> connector -> write(self::LF); + } else { + $this -> connector -> write(self::ESC . "d" . chr($lines)); + } + } + + /** + * Some printers require a form feed to release the paper. On most printers, this + * command is only useful in page mode, which is not implemented in this driver. + */ + function feedForm() { + $this -> connector -> write(self::FF); + } + + /** + * Print and reverse feed n lines. + * + * @param int $lines number of lines to feed. If not specified, 1 line will be fed. + */ + function feedReverse($lines = 1) { + self::validateInteger($lines, 1, 255, __FUNCTION__); + $this -> connector -> write(self::ESC . "e" . chr($lines)); + } + + /** + * @return number + */ + function getCharacterTable() { + return $this -> characterTable; + } + + /** + * @return PrintBuffer + */ + function getPrintBuffer() { + return $this -> buffer; + } + + /** + * @return PrintConnector + */ + function getPrintConnector() { + return $this -> connector; + } + + /** + * @return AbstractCapabilityProfile + */ + function getPrinterCapabilityProfile() { + return $this -> profile; + } + + /** + * @param int $type The type of status to request + * @return stdClass Class containing requested status, or null if either no status was received, or your print connector is unable to read from the printer. + */ + function getPrinterStatus($type = self::STATUS_PRINTER) { + self::validateIntegerMulti($type, array(array(1, 4), array(6, 8)), __FUNCTION__); + // Determine which flags we are looking for + $statusFlags = array( + self::STATUS_PRINTER => array( + 4 => "pulseHigh", // connector pin 3, see pulse(). + 8 => "offline", + 32 => "waitingForOnlineRecovery", + 64 => "feedButtonPressed" + ), + self::STATUS_OFFLINE_CAUSE => array( + 4 => "coverOpen", + 8 => "paperManualFeed", + 32 => "paperEnd", + 64 => "errorOccurred" + ), + self::STATUS_ERROR_CAUSE => array( + 4 => "recoverableError", + 8 => "autocutterError", + 32 => "unrecoverableError", + 64 => "autorecoverableError" + ), + self::STATUS_PAPER_ROLL => array( + 4 => "paperNearEnd", + 32 => "paperNotPresent" + ), + self::STATUS_INK_A => array( + 4 => "inkNearEnd", + 8 => "inkEnd", + 32 => "inkNotPresent", + 64 => "cleaning" + ), + self::STATUS_INK_B => array( + 4 => "inkNearEnd", + 8 => "inkEnd", + 32 => "inkNotPresent" + ), + self::STATUS_PEELER => array( + 4 => "labelWaitingForRemoval", + 32 => "labelPaperNotDetected" + ) + ); + $flags = $statusFlags[$type]; + // Clear any previous statuses which haven't been read yet + $f = $this -> connector -> read(1); + // Make request + $reqC = chr($type); + switch($type) { + // Special cases: These are two-character requests + case self::STATUS_INK_A: + $reqC = chr(7) . chr(1); + break; + case self::STATUS_INK_B: + $reqC = chr(7) . chr(2); + break; + case self::STATUS_PEELER: + $reqC = chr(8) . chr(3); + break; + } + $this -> connector -> write(self::DLE . self::EOT . $reqC); + // Wait for single-character response + $f = $this -> connector -> read(1); + $i = 0; + while($f === false && $i < 50000) { + usleep(100); + $f = $this -> connector -> read(1); + $i++; + } + if($f === false) { + // Timeout + return null; + } + $ret = new stdClass(); + foreach($flags as $num => $name) { + $ret -> $name = (ord($f) & $num) != 0; + } + return $ret; + } + + /** + * Print an image to the printer. + * + * Size modifiers are: + * - IMG_DEFAULT (leave image at original size) + * - IMG_DOUBLE_WIDTH + * - IMG_DOUBLE_HEIGHT + * + * See the example/ folder for detailed examples. + * + * The function bitImage() takes the same parameters, and can be used if + * your printer doesn't support the newer graphics commands. + * + * @param EscposImage $img The image to print. + * @param int $size Output size modifier for the image. + */ + function graphics(EscposImage $img, $size = self::IMG_DEFAULT) { + self::validateInteger($size, 0, 3, __FUNCTION__); + $imgHeader = self::dataHeader(array($img -> getWidth(), $img -> getHeight()), true); + $tone = '0'; + $colors = '1'; + $xm = (($size & self::IMG_DOUBLE_WIDTH) == self::IMG_DOUBLE_WIDTH) ? chr(2) : chr(1); + $ym = (($size & self::IMG_DOUBLE_HEIGHT) == self::IMG_DOUBLE_HEIGHT) ? chr(2) : chr(1); + $header = $tone . $xm . $ym . $colors . $imgHeader; + $this -> wrapperSendGraphicsData('0', 'p', $header . $img -> toRasterFormat()); + $this -> wrapperSendGraphicsData('0', '2'); + } + + /** + * Initialize printer. This resets formatting back to the defaults. + */ + function initialize() { + $this -> connector -> write(self::ESC . "@"); + $this -> characterTable = 0; + } + + /** + * Generate a pulse, for opening a cash drawer if one is connected. + * The default settings should open an Epson drawer. + * + * @param int $pin 0 or 1, for pin 2 or pin 5 kick-out connector respectively. + * @param int $on_ms pulse ON time, in milliseconds. + * @param int $off_ms pulse OFF time, in milliseconds. + */ + function pulse($pin = 0, $on_ms = 120, $off_ms = 240) { + self::validateInteger($pin, 0, 1, __FUNCTION__); + self::validateInteger($on_ms, 1, 511, __FUNCTION__); + self::validateInteger($off_ms, 1, 511, __FUNCTION__); + $this -> connector -> write(self::ESC . "p" . chr($pin + 48) . chr($on_ms / 2) . chr($off_ms / 2)); + } + + /** + * Print the given data as a QR code on the printer. + * + * @param string $content The content of the code. Numeric data will be more efficiently compacted. + * @param int $ec Error-correction level to use. One of Escpos::QR_ECLEVEL_L (default), Escpos::QR_ECLEVEL_M, Escpos::QR_ECLEVEL_Q or Escpos::QR_ECLEVEL_H. Higher error correction results in a less compact code. + * @param int $size Pixel size to use. Must be 1-16 (default 3) + * @param int $model QR code model to use. Must be one of Escpos::QR_MODEL_1, Escpos::QR_MODEL_2 (default) or Escpos::QR_MICRO (not supported by all printers). + */ + function qrCode($content, $ec = self::QR_ECLEVEL_L, $size = 3, $model = self::QR_MODEL_2) { + self::validateString($content, __FUNCTION__); + self::validateInteger($ec, 0, 3, __FUNCTION__); + self::validateInteger($size, 1, 16, __FUNCTION__); + self::validateInteger($model, 1, 3, __FUNCTION__); + if($content == "") { + return; + } + if(!$this -> profile -> getSupportsQrCode()) { + // TODO use software rendering via phpqrcode instead + throw new Exception("QR codes are not supported on your printer."); + } + $cn = '1'; // Code type for QR code + // Select model: 1, 2 or micro. + $this -> wrapperSend2dCodeData(chr(65), $cn, chr(48 + $model) . chr(0)); + // Set dot size. + $this -> wrapperSend2dCodeData(chr(67), $cn, chr($size)); + // Set error correction level: L, M, Q, or H + $this -> wrapperSend2dCodeData(chr(69), $cn, chr(48 + $ec)); + // Send content & print + $this -> wrapperSend2dCodeData(chr(80), $cn, $content, '0'); + $this -> wrapperSend2dCodeData(chr(81), $cn, '', '0'); + } + + /** + * Switch character table (code page) manually. Used in conjunction with textRaw() to + * print special characters which can't be encoded automatically. + * + * @param int $table The table to select. Available code tables are model-specific. + */ + function selectCharacterTable($table = 0) { + self::validateInteger($table, 0, 255, __FUNCTION__); + $supported = $this -> profile -> getSupportedCodePages(); + if(!isset($supported[$table])) { + throw new InvalidArgumentException("There is no code table $table allowed by this printer's capability profile."); + } + $this -> characterTable = $table; + if($this -> profile -> getSupportsStarCommands()) { + /* Not an ESC/POS command: STAR printers stash all the extra code pages under a different command. */ + $this -> connector -> write(self::ESC . self::GS . "t" . chr($table)); + return; + } + $this -> connector -> write(self::ESC . "t" . chr($table)); + } + + /** + * Select print mode(s). + * + * Several MODE_* constants can be OR'd together passed to this function's `$mode` argument. The valid modes are: + * - MODE_FONT_A + * - MODE_FONT_B + * - MODE_EMPHASIZED + * - MODE_DOUBLE_HEIGHT + * - MODE_DOUBLE_WIDTH + * - MODE_UNDERLINE + * + * @param int $mode The mode to use. Default is Escpos::MODE_FONT_A, with no special formatting. This has a similar effect to running initialize(). + */ + function selectPrintMode($mode = self::MODE_FONT_A) { + $allModes = self::MODE_FONT_B | self::MODE_EMPHASIZED | self::MODE_DOUBLE_HEIGHT | self::MODE_DOUBLE_WIDTH | self::MODE_UNDERLINE; + if(!is_integer($mode) || $mode < 0 || ($mode & $allModes) != $mode) { + throw new InvalidArgumentException("Invalid mode"); + } + + $this -> connector -> write(self::ESC . "!" . chr($mode)); + } + + /** + * Set barcode height. + * + * @param int $height Height in dots. If not specified, 8 will be used. + */ + function setBarcodeHeight($height = 8) { + self::validateInteger($height, 1, 255, __FUNCTION__); + $this -> connector -> write(self::GS . "h" . chr($height)); + } + + + /** + * Set the position for the Human Readable Interpretation (HRI) of barcode characters. + * + * @param position $position. Use Escpos::BARCODE_TEXT_NONE to hide the text (default), or any combination of Escpos::BARCODE_TEXT_TOP and Escpos::BARCODE_TEXT_BOTTOM flags to display the text. + */ + function setBarcodeTextPosition($position = self::BARCODE_TEXT_NONE) { + self::validateInteger($position, 0, 3, __FUNCTION__, "Barcode text position"); + $this -> connector -> write(self::GS . "H" . chr($position)); + } + + /** + * Turn double-strike mode on/off. + * + * @param boolean $on true for double strike, false for no double strike + */ + function setDoubleStrike($on = true) { + self::validateBoolean($on, __FUNCTION__); + $this -> connector -> write(self::ESC . "G". ($on ? chr(1) : chr(0))); + } + + /** + * Turn emphasized mode on/off. + * + * @param boolean $on true for emphasis, false for no emphasis + */ + function setEmphasis($on = true) { + self::validateBoolean($on, __FUNCTION__); + $this -> connector -> write(self::ESC . "E". ($on ? chr(1) : chr(0))); + } + + /** + * Select font. Most printers have two fonts (Fonts A and B), and some have a third (Font C). + * + * @param int $font The font to use. Must be either Escpos::FONT_A, Escpos::FONT_B, or Escpos::FONT_C. + */ + function setFont($font = self::FONT_A) { + self::validateInteger($font, 0, 2, __FUNCTION__); + $this -> connector -> write(self::ESC . "M" . chr($font)); + } + + /** + * Select justification. + * + * @param int $justification One of Escpos::JUSTIFY_LEFT, Escpos::JUSTIFY_CENTER, or Escpos::JUSTIFY_RIGHT. + */ + function setJustification($justification = self::JUSTIFY_LEFT) { + self::validateInteger($justification, 0, 2, __FUNCTION__); + $this -> connector -> write(self::ESC . "a" . chr($justification)); + } + + /** + * Attach a different print buffer to the printer. Buffers are responsible for handling text output to the printer. + * + * @param PrintBuffer $buffer The buffer to use. + * @throws InvalidArgumentException Where the buffer is already attached to a different printer. + */ + function setPrintBuffer(PrintBuffer $buffer) { + if($buffer === $this -> buffer) { + return; + } + if($buffer -> getPrinter() != null) { + throw new InvalidArgumentException("This buffer is already attached to a printer."); + } + if($this -> buffer !== null) { + $this -> buffer -> setPrinter(null); + } + $this -> buffer = $buffer; + $this -> buffer -> setPrinter($this); + } + + /** + * Set black/white reverse mode on or off. In this mode, text is printed white on a black background. + * + * @param boolean $on True to enable, false to disable. + */ + function setReverseColors($on = true) { + self::validateBoolean($on, __FUNCTION__); + $this -> connector -> write(self::GS . "B" . ($on ? chr(1) : chr(0))); + } + + /** + * Set the size of text, as a multiple of the normal size. + * + * @param int $widthMultiplier Multiple of the regular height to use (range 1 - 8) + * @param int $heightMultiplier Multiple of the regular height to use (range 1 - 8) + */ + function setTextSize($widthMultiplier, $heightMultiplier) { + self::validateInteger($widthMultiplier, 1, 8, __FUNCTION__); + self::validateInteger($heightMultiplier, 1, 8, __FUNCTION__); + $c = pow(2,4) * ($widthMultiplier - 1) + ($heightMultiplier - 1); + $this -> connector -> write(self::GS . "!" . chr($c)); + } + + /** + * Set underline for printed text. + * + * Argument can be true/false, or one of UNDERLINE_NONE, + * UNDERLINE_SINGLE or UNDERLINE_DOUBLE. + * + * @param int $underline Either true/false, or one of Escpos::UNDERLINE_NONE, Escpos::UNDERLINE_SINGLE or Escpos::UNDERLINE_DOUBLE. Defaults to Escpos::UNDERLINE_SINGLE. + */ + function setUnderline($underline = self::UNDERLINE_SINGLE) { + /* Map true/false to underline constants */ + if($underline === true) { + $underline = self::UNDERLINE_SINGLE; + } else if($underline === false) { + $underline = self::UNDERLINE_NONE; + } + /* Set the underline */ + self::validateInteger($underline, 0, 2, __FUNCTION__); + $this -> connector -> write(self::ESC . "-". chr($underline)); + } + + /** + * Add text to the buffer. + * + * Text should either be followed by a line-break, or feed() should be called + * after this to clear the print buffer. + * + * @param string $str Text to print + */ + function text($str = "") { + self::validateString($str, __FUNCTION__); + $this -> buffer -> writeText((string)$str); + } + + /** + * Add text to the buffer without attempting to interpret chararacter codes. + * + * Text should either be followed by a line-break, or feed() should be called + * after this to clear the print buffer. + * + * @param string $str Text to print + */ + function textRaw($str = "") { + self::validateString($str, __FUNCTION__); + $this -> buffer -> writeTextRaw((string)$str); + } + + /** + * Wrapper for GS ( k, to calculate and send correct data length. + * + * @param string $fn Function to use + * @param string $cn Output code type. Affects available data + * @param string $data Data to send. + * @param string $m Modifier/variant for function. Often '0' where used. + * @throws InvalidArgumentException Where the input lengths are bad. + */ + private function wrapperSend2dCodeData($fn, $cn, $data = '', $m = '') { + if(strlen($m) > 1 || strlen($cn) != 1 || strlen($fn) != 1) { + throw new InvalidArgumentException("wrapperSend2dCodeData: cn and fn must be one character each."); + } + $header = $this -> intLowHigh(strlen($data) + strlen($m) + 2, 2); + $this -> connector -> write(self::GS . "(k" . $header . $cn . $fn . $m . $data); + } + + /** + * Wrapper for GS ( L, to calculate and send correct data length. + * + * @param string $m Modifier/variant for function. Usually '0'. + * @param string $fn Function number to use, as character. + * @param string $data Data to send. + * @throws InvalidArgumentException Where the input lengths are bad. + */ + private function wrapperSendGraphicsData($m, $fn, $data = '') { + if(strlen($m) != 1 || strlen($fn) != 1) { + throw new InvalidArgumentException("wrapperSendGraphicsData: m and fn must be one character each."); + } + $header = $this -> intLowHigh(strlen($data) + 2, 2); + $this -> connector -> write(self::GS . "(L" . $header . $m . $fn . $data); + } + + /** + * Convert widths and heights to characters. Used before sending graphics to set the size. + * + * @param array $inputs + * @param boolean $long True to use 4 bytes, false to use 2 + * @return string + */ + private static function dataHeader(array $inputs, $long = true) { + $outp = array(); + foreach($inputs as $input) { + if($long) { + $outp[] = Escpos::intLowHigh($input, 2); + } else { + self::validateInteger($input, 0 , 255, __FUNCTION__); + $outp[] = chr($input); + } + } + return implode("", $outp); + } + + /** + * Generate two characters for a number: In lower and higher parts, or more parts as needed. + * @param int $int Input number + * @param int $length The number of bytes to output (1 - 4). + */ + private static function intLowHigh($input, $length) { + $maxInput = (256 << ($length * 8) - 1); + self::validateInteger($length, 1, 4, __FUNCTION__); + self::validateInteger($input, 0, $maxInput, __FUNCTION__); + $outp = ""; + for($i = 0; $i < $length; $i++) { + $outp .= chr($input % 256); + $input = (int)($input / 256); + } + return $outp; + } + + /** + * Throw an exception if the argument given is not a boolean + * + * @param boolean $test the input to test + * @param string $source the name of the function calling this + */ + protected static function validateBoolean($test, $source) { + if(!($test === true || $test === false)) { + throw new InvalidArgumentException("Argument to $source must be a boolean"); + } + } + + /** + * Throw an exception if the argument given is not an integer within the specified range + * + * @param int $test the input to test + * @param int $min the minimum allowable value (inclusive) + * @param int $max the maximum allowable value (inclusive) + * @param string $source the name of the function calling this + * @param string $argument the name of the invalid parameter + */ + protected static function validateInteger($test, $min, $max, $source, $argument = "Argument") { + self::validateIntegerMulti($test, array(array($min, $max)), $source, $argument); + } + + /** + * Throw an exception if the argument given is not an integer within one of the specified ranges + * + * @param int $test the input to test + * @param arrray $ranges array of two-item min/max ranges. + * @param string $source the name of the function calling this + * @param string $source the name of the function calling this + * @param string $argument the name of the invalid parameter + */ + protected static function validateIntegerMulti($test, array $ranges, $source, $argument = "Argument") { + if(!is_integer($test)) { + throw new InvalidArgumentException("$argument given to $source must be a number, but '$test' was given."); + } + $match = false; + foreach($ranges as $range) { + $match |= $test >= $range[0] && $test <= $range[1]; + } + if(!$match) { + // Put together a good error "range 1-2 or 4-6" + $rangeStr = "range "; + for($i = 0; $i < count($ranges); $i++) { + $rangeStr .= $ranges[$i][0] . "-" . $ranges[$i][1]; + if($i == count($ranges) - 1) { + continue; + } else if($i == count($ranges) - 2) { + $rangeStr .= " or "; + } else { + $rangeStr .= ", "; + } + } + throw new InvalidArgumentException("$argument given to $source must be in $rangeStr, but $test was given."); + } + } + + /** + * Throw an exception if the argument given can't be cast to a string + * + * @param string $test the input to test + * @param string $source the name of the function calling this + * @param string $argument the name of the invalid parameter + */ + protected static function validateString($test, $source, $argument = "Argument") { + if (is_object($test) && !method_exists($test, '__toString')) { + throw new InvalidArgumentException("$argument to $source must be a string"); + } + } + + protected static function validateStringRegex($test, $source, $regex, $argument = "Argument") { + if(preg_match($regex, $test) === 0) { + throw new InvalidArgumentException("$argument given to $source is invalid. It should match regex '$regex', but '$test' was given."); + } + } +} diff --git a/htdocs/includes/mike42/escpos-php/README.md b/htdocs/includes/mike42/escpos-php/README.md index 065fdea3768..43798bb0852 100644 --- a/htdocs/includes/mike42/escpos-php/README.md +++ b/htdocs/includes/mike42/escpos-php/README.md @@ -1,10 +1,73 @@ -# ESC/POS Print Driver for PHP - +ESC/POS Print Driver for PHP +============================ This project implements a subset of Epson's ESC/POS protocol for thermal receipt printers. It allows you to generate and print receipts with basic formatting, cutting, and barcodes on a compatible printer. The library was developed to add drop-in support for receipt printing to any PHP app, including web-based point-of-sale (POS) applications. -## Compatibility +Basic usage +----------- +A "hello world" receipt can be generated easily (Call this `hello-world.php`): +```php + text("Hello World!\n"); +$printer -> cut(); +$printer -> close(); +``` +This would be printed as: +``` +# Networked printer +php hello-world.php | nc 10.x.x.x. 9100 +# Local printer +php hello-world.php > /dev/... +# Windows local printer +php hello-world.php > foo.txt +net use LPT1 \\server\printer +copy foo.txt LPT1 +del foo.txt +``` + +From your web app, you could pass the output directly to a socket if your printer is networked: +```php + text("Hello World!\n"); +$printer -> cut(); +$printer -> close(); +``` + +Or to a local printer: +```php + text("Hello World!\n"); +$printer -> cut(); +$printer -> close(); +``` + +### Basic workflow +The library should be initialised with a PrintConnector, which will pass on the data to your printer. +Use the table under "Compatibility", or the examples below to choose the appropriate connector for your +platform & interface. If no connector is specified, then standard output is used. + +When you have finished using the print object, call `close()` to finalize any data transfers. + +### Tips & examples +On Linux, your printer device file will be somewhere like `/dev/lp0` (parallel), `/dev/usb/lp1` (USB), `/dev/ttyUSB0` (USB-Serial), `/dev/ttyS0` (serial). + +On Windows, the device files will be along the lines of `LPT1` (parallel) or `COM1` (serial). Use the `WindowsPrintConnector` to tap into system printing on Windows (eg. [Windows USB](https://github.com/mike42/escpos-php/tree/master/example/interface/windows-usb.php), [SMB](https://github.com/mike42/escpos-php/tree/master/example/interface/smb.php) or [Windows LPT](https://github.com/mike42/escpos-php/tree/master/example/interface/windows-lpt.php)) - this submits print jobs via a queue rather than communicating directly with the printer. + +A complete real-world receipt can be found in the code of [Auth](https://github.com/mike42/Auth) in [ReceiptPrinter.php](https://github.com/mike42/Auth/blob/master/lib/misc/ReceiptPrinter.php). It includes justification, boldness, and a barcode. + +Other examples are located in the [example/](https://github.com/mike42/escpos-php/blob/master/example/) directory. + +Compatibility +------------- ### Interfaces and operating systems This driver is known to work with the following OS/interface combinations: @@ -52,19 +115,11 @@ This driver is known to work with the following OS/interface combinations: - - - - - -
'.$langs->trans("Warehouse").''.$langs->trans("NumberOfUnit").''.$langs->trans("AverageUnitPricePMPShort").'
'; print '
'; diff --git a/htdocs/product/stock/productlot_card.php b/htdocs/product/stock/productlot_card.php index 2e8620797c7..4af5b8a4d59 100644 --- a/htdocs/product/stock/productlot_card.php +++ b/htdocs/product/stock/productlot_card.php @@ -20,7 +20,7 @@ * \file stock/productlot_card.php * \ingroup stock * \brief This file is an example of a php page - * Initialy built by build_class_from_table on 2016-05-17 10:33 + * Initialy built by build_class_from_table on 2016-05-17 12:22 */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); @@ -57,12 +57,12 @@ $backtopage = GETPOST('backtopage'); $myparam = GETPOST('myparam','alpha'); +$search_entity=GETPOST('search_entity','int'); $search_fk_product=GETPOST('search_fk_product','int'); $search_batch=GETPOST('search_batch','alpha'); -$search_note_public=GETPOST('search_note_public','alpha'); -$search_note_private=GETPOST('search_note_private','alpha'); -$search_qty=GETPOST('search_qty','alpha'); -$search_import_key=GETPOST('search_import_key','alpha'); +$search_fk_user_creat=GETPOST('search_fk_user_creat','int'); +$search_fk_user_modif=GETPOST('search_fk_user_modif','int'); +$search_import_key=GETPOST('search_import_key','int'); @@ -114,12 +114,12 @@ if (empty($reshook)) /* object_prop_getpost_prop */ + $object->entity=GETPOST('entity','int'); $object->fk_product=GETPOST('fk_product','int'); $object->batch=GETPOST('batch','alpha'); - $object->note_public=GETPOST('note_public','alpha'); - $object->note_private=GETPOST('note_private','alpha'); - $object->qty=GETPOST('qty','alpha'); - $object->import_key=GETPOST('import_key','alpha'); + $object->fk_user_creat=GETPOST('fk_user_creat','int'); + $object->fk_user_modif=GETPOST('fk_user_modif','int'); + $object->import_key=GETPOST('import_key','int'); @@ -161,12 +161,12 @@ if (empty($reshook)) $error=0; + $object->entity=GETPOST('entity','int'); $object->fk_product=GETPOST('fk_product','int'); $object->batch=GETPOST('batch','alpha'); - $object->note_public=GETPOST('note_public','alpha'); - $object->note_private=GETPOST('note_private','alpha'); - $object->qty=GETPOST('qty','alpha'); - $object->import_key=GETPOST('import_key','alpha'); + $object->fk_user_creat=GETPOST('fk_user_creat','int'); + $object->fk_user_modif=GETPOST('fk_user_modif','int'); + $object->import_key=GETPOST('import_key','int'); @@ -262,11 +262,11 @@ if ($action == 'create') print '
'."\n"; // print ''; // +print ''; print ''; print ''; -print ''; -print ''; -print ''; +print ''; +print ''; print ''; print '
'.$langs->trans("Label").'
'.$langs->trans("Fieldentity").'
'.$langs->trans("Fieldfk_product").'
'.$langs->trans("Fieldbatch").'
'.$langs->trans("Fieldnote_public").'
'.$langs->trans("Fieldnote_private").'
'.$langs->trans("Fieldqty").'
'.$langs->trans("Fieldfk_user_creat").'
'.$langs->trans("Fieldfk_user_modif").'
'.$langs->trans("Fieldimport_key").'
'."\n"; @@ -295,11 +295,11 @@ if (($id || $ref) && $action == 'edit') print ''."\n"; // print ''; // +print ''; print ''; print ''; -print ''; -print ''; -print ''; +print ''; +print ''; print ''; print '
'.$langs->trans("Label").'
'.$langs->trans("Fieldentity").'
'.$langs->trans("Fieldfk_product").'
'.$langs->trans("Fieldbatch").'
'.$langs->trans("Fieldnote_public").'
'.$langs->trans("Fieldnote_private").'
'.$langs->trans("Fieldqty").'
'.$langs->trans("Fieldfk_user_creat").'
'.$langs->trans("Fieldfk_user_modif").'
'.$langs->trans("Fieldimport_key").'
'; @@ -330,11 +330,11 @@ if ($id && (empty($action) || $action == 'view' || $action == 'delete')) print ''."\n"; // print ''; // +print ''; print ''; print ''; -print ''; -print ''; -print ''; +print ''; +print ''; print ''; print '
'.$langs->trans("Label").'
'.$langs->trans("Fieldentity").'$object->entity
'.$langs->trans("Fieldfk_product").'$object->fk_product
'.$langs->trans("Fieldbatch").'$object->batch
'.$langs->trans("Fieldnote_public").'$object->note_public
'.$langs->trans("Fieldnote_private").'$object->note_private
'.$langs->trans("Fieldqty").'$object->qty
'.$langs->trans("Fieldfk_user_creat").'$object->fk_user_creat
'.$langs->trans("Fieldfk_user_modif").'$object->fk_user_modif
'.$langs->trans("Fieldimport_key").'$object->import_key
'; diff --git a/htdocs/product/stock/productlot_list.php b/htdocs/product/stock/productlot_list.php index ded98827e31..a8fd68f6a34 100644 --- a/htdocs/product/stock/productlot_list.php +++ b/htdocs/product/stock/productlot_list.php @@ -20,7 +20,7 @@ * \file stock/productlot_list.php * \ingroup stock * \brief This file is an example of a php page - * Initialy built by build_class_from_table on 2016-05-17 10:33 + * Initialy built by build_class_from_table on 2016-05-17 12:22 */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); @@ -59,12 +59,12 @@ $backtopage = GETPOST('backtopage'); $myparam = GETPOST('myparam','alpha'); +$search_entity=GETPOST('search_entity','int'); $search_fk_product=GETPOST('search_fk_product','int'); $search_batch=GETPOST('search_batch','alpha'); -$search_note_public=GETPOST('search_note_public','alpha'); -$search_note_private=GETPOST('search_note_private','alpha'); -$search_qty=GETPOST('search_qty','alpha'); -$search_import_key=GETPOST('search_import_key','alpha'); +$search_fk_user_creat=GETPOST('search_fk_user_creat','int'); +$search_fk_user_modif=GETPOST('search_fk_user_modif','int'); +$search_import_key=GETPOST('search_import_key','int'); $search_myfield=GETPOST('search_myfield'); @@ -109,11 +109,11 @@ if (($id > 0 || ! empty($ref)) && $action != 'add') // Definition of fields for list $arrayfields=array( +'t.entity'=>array('label'=>$langs->trans("Fieldentity"), 'checked'=>1), 't.fk_product'=>array('label'=>$langs->trans("Fieldfk_product"), 'checked'=>1), 't.batch'=>array('label'=>$langs->trans("Fieldbatch"), 'checked'=>1), -'t.note_public'=>array('label'=>$langs->trans("Fieldnote_public"), 'checked'=>1), -'t.note_private'=>array('label'=>$langs->trans("Fieldnote_private"), 'checked'=>1), -'t.qty'=>array('label'=>$langs->trans("Fieldqty"), 'checked'=>1), +'t.fk_user_creat'=>array('label'=>$langs->trans("Fieldfk_user_creat"), 'checked'=>1), +'t.fk_user_modif'=>array('label'=>$langs->trans("Fieldfk_user_modif"), 'checked'=>1), 't.import_key'=>array('label'=>$langs->trans("Fieldimport_key"), 'checked'=>1), @@ -152,11 +152,11 @@ include DOL_DOCUMENT_ROOT.'/core/actions_changeselectedfields.inc.php'; if (GETPOST("button_removefilter_x") || GETPOST("button_removefilter.x") ||GETPOST("button_removefilter")) // All test are required to be compatible with all browsers { +$search_entity=''; $search_fk_product=''; $search_batch=''; -$search_note_public=''; -$search_note_private=''; -$search_qty=''; +$search_fk_user_creat=''; +$search_fk_user_modif=''; $search_import_key=''; @@ -235,14 +235,15 @@ jQuery(document).ready(function() { $sql = "SELECT"; $sql.= " t.rowid,"; - $sql .= " t.tms,"; + $sql .= " t.entity,"; $sql .= " t.fk_product,"; $sql .= " t.batch,"; $sql .= " t.eatby,"; $sql .= " t.sellby,"; - $sql .= " t.note_public,"; - $sql .= " t.note_private,"; - $sql .= " t.qty,"; + $sql .= " t.datec,"; + $sql .= " t.tms,"; + $sql .= " t.fk_user_creat,"; + $sql .= " t.fk_user_modif,"; $sql .= " t.import_key"; @@ -257,11 +258,11 @@ if (is_array($extrafields->attribute_label) && count($extrafields->attribute_lab $sql.= " WHERE 1 = 1"; //$sql.= " WHERE u.entity IN (".getEntity('mytable',1).")"; +if ($search_entity) $sql.= natural_search("entity",$search_entity); if ($search_fk_product) $sql.= natural_search("fk_product",$search_fk_product); if ($search_batch) $sql.= natural_search("batch",$search_batch); -if ($search_note_public) $sql.= natural_search("note_public",$search_note_public); -if ($search_note_private) $sql.= natural_search("note_private",$search_note_private); -if ($search_qty) $sql.= natural_search("qty",$search_qty); +if ($search_fk_user_creat) $sql.= natural_search("fk_user_creat",$search_fk_user_creat); +if ($search_fk_user_modif) $sql.= natural_search("fk_user_modif",$search_fk_user_modif); if ($search_import_key) $sql.= natural_search("import_key",$search_import_key); @@ -306,11 +307,11 @@ if ($resql) $params=''; if ($limit > 0 && $limit != $conf->liste_limit) $param.='&limit='.$limit; +if ($search_entity != '') $params.= '&search_entity='.urlencode($search_entity); if ($search_fk_product != '') $params.= '&search_fk_product='.urlencode($search_fk_product); if ($search_batch != '') $params.= '&search_batch='.urlencode($search_batch); -if ($search_note_public != '') $params.= '&search_note_public='.urlencode($search_note_public); -if ($search_note_private != '') $params.= '&search_note_private='.urlencode($search_note_private); -if ($search_qty != '') $params.= '&search_qty='.urlencode($search_qty); +if ($search_fk_user_creat != '') $params.= '&search_fk_user_creat='.urlencode($search_fk_user_creat); +if ($search_fk_user_modif != '') $params.= '&search_fk_user_modif='.urlencode($search_fk_user_modif); if ($search_import_key != '') $params.= '&search_import_key='.urlencode($search_import_key); @@ -389,11 +390,11 @@ if ($search_import_key != '') $params.= '&search_import_key='.urlencode($sea // Fields title search print '
'; - print ' 0 ? ' disabled':'').' value="'.(GETPOST('batch_number')?GETPOST('batch_number'):$pdluo->batch).'">'; // If form was opened for a specific pdluoid, field is disabled + if ($pdluoid > 0) + { + // If form was opened for a specific pdluoid, field is disabled + print ''; + print ''; + } + else + { + print ''; + } print '
'.$langs->trans("l_eatby").''; From be251cd2b0f39299cd6a6b5dbba0171c56c05037 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 18:22:48 +0200 Subject: [PATCH 22/61] Add high risk sql at end --- htdocs/install/mysql/migration/3.9.0-4.0.0.sql | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql index f34e52dafe6..136a7114eb0 100644 --- a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql +++ b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql @@ -430,8 +430,6 @@ ALTER TABLE llx_categorie_account ADD CONSTRAINT fk_categorie_account_fk_account -- Delete old deprecated field ALTER TABLE llx_product_stock DROP COLUMN pmp; --- VMYSQL4.1 ALTER TABLE llx_c_type_resource CHANGE COLUMN rowid rowid integer NOT NULL AUTO_INCREMENT; - ALTER TABLE llx_resource ADD COLUMN asset_number varchar(255) after ref; ALTER TABLE llx_resource ADD COLUMN datec datetime DEFAULT NULL; ALTER TABLE llx_resource ADD COLUMN date_valid datetime DEFAULT NULL; @@ -468,6 +466,9 @@ update llx_product_batch set batch = '000000' where batch = 'Undefined'; update llx_product_lot set batch = '000000' where batch = 'Undefined'; update llx_stock_mouvement set batch = '000000' where batch = 'Undefined'; --- At end +-- At end (higher risk of error) + +-- VMYSQL4.1 ALTER TABLE llx_c_type_resource CHANGE COLUMN rowid rowid integer NOT NULL AUTO_INCREMENT; ALTER TABLE llx_product_batch ADD UNIQUE INDEX uk_product_batch (fk_product_stock, batch); + From e2d3f4e27d02d84c9824a97620f421b6a3becd1b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 19:15:44 +0200 Subject: [PATCH 23/61] Supplier proposal was missing --- htdocs/langs/en_US/other.lang | 2 ++ htdocs/product/class/product.class.php | 37 ++++++++++++++++++++++++++ htdocs/product/stats/card.php | 23 ++++++++++------ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/htdocs/langs/en_US/other.lang b/htdocs/langs/en_US/other.lang index 6187293aefd..af0aea4c274 100644 --- a/htdocs/langs/en_US/other.lang +++ b/htdocs/langs/en_US/other.lang @@ -159,11 +159,13 @@ StatsByNumberOfEntities=Statistics in number of referring entities NumberOfProposals=Number of proposals on last 12 month NumberOfCustomerOrders=Number of customer orders on last 12 month NumberOfCustomerInvoices=Number of customer invoices on last 12 month +NumberOfSupplierProposals=Number of supplier proposals on last 12 month NumberOfSupplierOrders=Number of supplier orders on last 12 month NumberOfSupplierInvoices=Number of supplier invoices on last 12 month NumberOfUnitsProposals=Number of units on proposals on last 12 month NumberOfUnitsCustomerOrders=Number of units on customer orders on last 12 month NumberOfUnitsCustomerInvoices=Number of units on customer invoices on last 12 month +NumberOfUnitsSupplierProposals=Number of units on supplier proposals on last 12 month NumberOfUnitsSupplierOrders=Number of units on supplier orders on last 12 month NumberOfUnitsSupplierInvoices=Number of units on supplier invoices on last 12 month EMailTextInterventionAddedContact=A newintervention %s has been assigned to you. diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index e125d538fac..32a474d924b 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -2311,6 +2311,7 @@ class Product extends CommonObject if (!$user->rights->societe->client->voir && !$socid) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; $sql.= " WHERE f.rowid = d.fk_facture"; if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id; + else $sql.=" AND d.fk_product > 0"; if ($filteronproducttype >= 0) $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype; $sql.= " AND f.fk_soc = s.rowid"; $sql.= " AND f.entity IN (".getEntity('facture', 1).")"; @@ -2343,6 +2344,7 @@ class Product extends CommonObject if (!$user->rights->societe->client->voir && !$socid) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; $sql.= " WHERE f.rowid = d.fk_facture_fourn"; if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id; + else $sql.=" AND d.fk_product > 0"; if ($filteronproducttype >= 0) $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype; $sql.= " AND f.fk_soc = s.rowid"; $sql.= " AND f.entity IN (".getEntity('facture_fourn', 1).")"; @@ -2375,6 +2377,7 @@ class Product extends CommonObject if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; $sql.= " WHERE p.rowid = d.fk_propal"; if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id; + else $sql.=" AND d.fk_product > 0"; if ($filteronproducttype >= 0) $sql.= " AND prod.rowid = d.fk_product AND prod.fk_product_type =".$filteronproducttype; $sql.= " AND p.fk_soc = s.rowid"; $sql.= " AND p.entity IN (".getEntity('propal', 1).")"; @@ -2386,6 +2389,38 @@ class Product extends CommonObject return $this->_get_stats($sql,$mode); } + /** + * Return nb of units or proposals in which product is included + * + * @param int $socid Limit count on a particular third party id + * @param string $mode 'byunit'=number of unit, 'bynumber'=nb of entities + * @param int $filteronproducttype 0=To filter on product only, 1=To filter on services only + * @return array <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11 + */ + function get_nb_propalsupplier($socid, $mode, $filteronproducttype=-1) + { + global $conf; + global $user; + + $sql = "SELECT sum(d.qty), date_format(p.date_valid, '%Y%m')"; + if ($mode == 'bynumber') $sql.= ", count(DISTINCT p.rowid)"; + $sql.= " FROM ".MAIN_DB_PREFIX."supplier_proposaldet as d, ".MAIN_DB_PREFIX."supplier_proposal as p, ".MAIN_DB_PREFIX."societe as s"; + if ($filteronproducttype >= 0) $sql.=", ".MAIN_DB_PREFIX."product as prod"; + if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; + $sql.= " WHERE p.rowid = d.fk_supplier_proposal"; + if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id; + else $sql.=" AND d.fk_product > 0"; + if ($filteronproducttype >= 0) $sql.= " AND prod.rowid = d.fk_product AND prod.fk_product_type =".$filteronproducttype; + $sql.= " AND p.fk_soc = s.rowid"; + $sql.= " AND p.entity IN (".getEntity('propal', 1).")"; + if (!$user->rights->societe->client->voir && !$socid) $sql.= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id; + if ($socid > 0) $sql.= " AND p.fk_soc = ".$socid; + $sql.= " GROUP BY date_format(p.date_valid,'%Y%m')"; + $sql.= " ORDER BY date_format(p.date_valid,'%Y%m') DESC"; + + return $this->_get_stats($sql,$mode); + } + /** * Return nb of units or orders in which product is included * @@ -2405,6 +2440,7 @@ class Product extends CommonObject if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; $sql.= " WHERE c.rowid = d.fk_commande"; if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id; + else $sql.=" AND d.fk_product > 0"; if ($filteronproducttype >= 0) $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype; $sql.= " AND c.fk_soc = s.rowid"; $sql.= " AND c.entity IN (".getEntity('commande', 1).")"; @@ -2435,6 +2471,7 @@ class Product extends CommonObject if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; $sql.= " WHERE c.rowid = d.fk_commande"; if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id; + else $sql.=" AND d.fk_product > 0"; if ($filteronproducttype >= 0) $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype; $sql.= " AND c.fk_soc = s.rowid"; $sql.= " AND c.entity IN (".getEntity('commande_fournisseur', 1).")"; diff --git a/htdocs/product/stats/card.php b/htdocs/product/stats/card.php index 7003690e3c8..3a01d952acd 100644 --- a/htdocs/product/stats/card.php +++ b/htdocs/product/stats/card.php @@ -212,15 +212,20 @@ if (! empty($id) || ! empty($ref) || GETPOST('id') == 'all') 'propal' =>array('modulepart'=>'productstats_proposals', 'file' => $object->id.'/propal12m'.((string) $type != '' ? '_type'.$type : '').'_'.$mode.'.png', 'label' => ($mode=='byunit'?$langs->transnoentitiesnoconv("NumberOfUnitsProposals"):$langs->transnoentitiesnoconv("NumberOfProposals"))), + 'proposalssuppliers'=>array('modulepart'=>'productstats_proposalssuppliers', + 'file' => $object->id.'/proposalssuppliers12m'.((string) $type != '' ? '_type'.$type : '').'_'.$mode.'.png', + 'label' => ($mode=='byunit'?$langs->transnoentitiesnoconv("NumberOfUnitsSupplierProposals"):$langs->transnoentitiesnoconv("NumberOfSupplierProposals"))), + 'orders' =>array('modulepart'=>'productstats_orders', 'file' => $object->id.'/orders12m'.((string) $type != '' ? '_type'.$type : '').'_'.$mode.'.png', 'label' => ($mode=='byunit'?$langs->transnoentitiesnoconv("NumberOfUnitsCustomerOrders"):$langs->transnoentitiesnoconv("NumberOfCustomerOrders"))), - 'invoices' =>array('modulepart'=>'productstats_invoices', - 'file' => $object->id.'/invoices12m'.((string) $type != '' ? '_type'.$type : '').'_'.$mode.'.png', - 'label' => ($mode=='byunit'?$langs->transnoentitiesnoconv("NumberOfUnitsCustomerInvoices"):$langs->transnoentitiesnoconv("NumberOfCustomerInvoices"))), 'orderssuppliers'=>array('modulepart'=>'productstats_orderssuppliers', 'file' => $object->id.'/orderssuppliers12m'.((string) $type != '' ? '_type'.$type : '').'_'.$mode.'.png', 'label' => ($mode=='byunit'?$langs->transnoentitiesnoconv("NumberOfUnitsSupplierOrders"):$langs->transnoentitiesnoconv("NumberOfSupplierOrders"))), + + 'invoices' =>array('modulepart'=>'productstats_invoices', + 'file' => $object->id.'/invoices12m'.((string) $type != '' ? '_type'.$type : '').'_'.$mode.'.png', + 'label' => ($mode=='byunit'?$langs->transnoentitiesnoconv("NumberOfUnitsCustomerInvoices"):$langs->transnoentitiesnoconv("NumberOfCustomerInvoices"))), 'invoicessuppliers'=>array('modulepart'=>'productstats_invoicessuppliers', 'file' => $object->id.'/invoicessuppliers12m'.((string) $type != '' ? '_type'.$type : '').'_'.$mode.'.png', 'label' => ($mode=='byunit'?$langs->transnoentitiesnoconv("NumberOfUnitsSupplierInvoices"):$langs->transnoentitiesnoconv("NumberOfSupplierInvoices"))), @@ -245,11 +250,12 @@ if (! empty($id) || ! empty($ref) || GETPOST('id') == 'all') } else { - if ($key == 'propal') $graph_data = $object->get_nb_propal($socid,$mode,((string) $type != '' ? $type : -1)); - if ($key == 'orders') $graph_data = $object->get_nb_order($socid,$mode,((string) $type != '' ? $type : -1)); - if ($key == 'invoices') $graph_data = $object->get_nb_vente($socid,$mode,((string) $type != '' ? $type : -1)); - if ($key == 'invoicessuppliers') $graph_data = $object->get_nb_achat($socid,$mode,((string) $type != '' ? $type : -1)); - if ($key == 'orderssuppliers') $graph_data = $object->get_nb_ordersupplier($socid,$mode,((string) $type != '' ? $type : -1)); + if ($key == 'propal') $graph_data = $object->get_nb_propal($socid,$mode,((string) $type != '' ? $type : -1)); + if ($key == 'orders') $graph_data = $object->get_nb_order($socid,$mode,((string) $type != '' ? $type : -1)); + if ($key == 'invoices') $graph_data = $object->get_nb_vente($socid,$mode,((string) $type != '' ? $type : -1)); + if ($key == 'proposalssuppliers') $graph_data = $object->get_nb_propalsupplier($socid,$mode,((string) $type != '' ? $type : -1)); + if ($key == 'invoicessuppliers') $graph_data = $object->get_nb_achat($socid,$mode,((string) $type != '' ? $type : -1)); + if ($key == 'orderssuppliers') $graph_data = $object->get_nb_ordersupplier($socid,$mode,((string) $type != '' ? $type : -1)); // TODO Save cachefile $graphfiles[$key]['file'] } @@ -291,6 +297,7 @@ if (! empty($id) || ! empty($ref) || GETPOST('id') == 'all') if ($graphfiles == 'propal' && ! $user->rights->propale->lire) continue; if ($graphfiles == 'order' && ! $user->rights->commande->lire) continue; if ($graphfiles == 'invoices' && ! $user->rights->facture->lire) continue; + if ($graphfiles == 'proposals_suppliers' && ! $user->rights->supplier_proposal->lire) continue; if ($graphfiles == 'invoices_suppliers' && ! $user->rights->fournisseur->facture->lire) continue; if ($graphfiles == 'orders_suppliers' && ! $user->rights->fournisseur->commande->lire) continue; From 7063b26018c5f87b9a8362eb8ecb9e4fb66017fd Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 17 May 2016 19:32:19 +0200 Subject: [PATCH 24/61] Debug 4.0 --- htdocs/product/stock/massstockmove.php | 4 ++-- htdocs/product/stock/replenish.php | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/htdocs/product/stock/massstockmove.php b/htdocs/product/stock/massstockmove.php index c7b7ed41034..b8ec52efcab 100644 --- a/htdocs/product/stock/massstockmove.php +++ b/htdocs/product/stock/massstockmove.php @@ -378,7 +378,7 @@ print ''; - print ''; + print ''; print ''; print $formproduct->selectWarehouses($id_tw,'id_tw','',1); print '
'.($nb[$status.$bool]?$nb[$status.$bool]:0).' '.$staticcontratligne->LibStatut($status,3,($bool?1:0)).'
'.$staticcontratligne->LibStatut($status,0,($bool?1:0)).''.($nb[$status.$bool]?$nb[$status.$bool]:0).' '.$staticcontratligne->LibStatut($status,3,($bool?1:0)).'

'.$langs->trans("MigrationFinished").'
'; print ''.$langs->trans('Upgrade').': '; - print $langs->trans('MigrationReloadModule')." ".$mod->getName(); + print $langs->trans('MigrationReloadModule').' '.$mod->getName(); // We keep getName outside of trans because getName is already encoded/translated print ""; print "
\n"; print '
No Yes
CUPS hostedYesYesNo
### Printers Many thermal receipt printers support ESC/POS to some degree. This driver has been known to work with: -- Bixolon SRP-350III -- Citizen CBM1000-II - EPOS TEP 220M - Epson TM-T88III - Epson TM-T88IV @@ -74,12 +129,9 @@ Many thermal receipt printers support ESC/POS to some degree. This driver has be - Epson TM-T70II - Epson TM-U220 - Epson FX-890 (requires `feedForm()` to release paper). -- Excelvan HOP-E58 (connect through powered hub) - Okipos 80 Plus III - P-822D -- P85A-401 (make unknown) - SEYPOS PRP-300 (Also marketed as TYSSO PRP-300) -- Silicon SP-201 / RP80USE - Star TSP-650 - Star TUP-592 - Xprinter XP-Q800 @@ -87,152 +139,16 @@ Many thermal receipt printers support ESC/POS to some degree. This driver has be - Zijang ZJ-5870 - Zijang ZJ-5890T (Marketed as POS 5890T) -If you use any other printer with this code, please [let us know](https://github.com/mike42/escpos-php/issues/new) so that it can be added to the list. +If you use any other printer with this code, please let me know so I can add it to the list. -## Basic usage - -### Include the library - -#### Composer -If you are using composer, then add `mike42/escpos-php` as a dependency: - -```` -composer require mike42/escpos-php -```` - -In this case, you would include composer's auto-loader at the top of your source files: - -```` - text("Hello World!\n"); -$printer -> cut(); -$printer -> close(); -``` - -Some examples are below for common interfaces. - -Communicate with a printer with an Ethernet interface using `netcat`: -```` -php hello-world.php | nc 10.x.x.x. 9100 -```` - -A USB local printer connected with `usblp` on Linux has a device file (Includes USB-parallel interfaces): -```` -php hello-world.php > /dev/usb/lp0 -```` - -A computer installed into the local `cups` server is accessed through `lp` or `lpr`: -```` -php hello-world.php > foo.txt -lpr -o raw -H localhost -P printer foo.txt -```` - -A local or networked printer on a Windows computer is mapped in to a file, and generally requires you to share the printer first: - -```` -php hello-world.php > foo.txt -net use LPT1 \\server\printer -copy foo.txt LPT1 -del foo.txt -``` - -If you have troubles at this point, then you should consult your OS and printer system documentation to try to find a working print command. - -### Using a PrintConnector - -To print receipts from PHP, use the most applicable [PrintConnector](https://github.com/mike42/escpos-php/tree/master/src/Mike42/Escpos/PrintConnectors) for your setup. The connector simply provides the plumbing to get data to the printer. - -For example, a `NetworkPrintConnector` accepts an IP address and port: - -````php -use Mike42\Escpos\PrintConnectors\NetworkPrintConnector; -use Mike42\Escpos\Printer; -$connector = new NetworkPrintConnector("10.x.x.x", 9100); -$printer = new Printer($connector); -try { - // ... Print stuff -} finally { - $printer -> close(); -} -```` - -While a serial printer might use: -```php -use Mike42\Escpos\PrintConnectors\FilePrintConnector; -use Mike42\Escpos\Printer; -$connector = new FilePrintConnector("/dev/ttyS0"); -$printer = new Printer($connector); -``` - -For each OS/interface combination that's supported, there are examples in the compatibility section of how a `PrintConnector` would be constructed. If you can't get a `PrintConnector` to work, then be sure to include the working print command in bug. - -### Using a CapabilityProfile - -Support for commands and code pages varies between printer vendors and models. By default, the driver will accept UTF-8, and output commands that are suitable for Epson TM-series printers. - -When trying out a new brand of printer, it's a good idea to use the `SimpleCapabilityProfile`, which instructs the driver to avoid the use of advanced features (generally simpler image handling, ASCII-only text). - -```php -use Mike42\Escpos\PrintConnectors\FilePrintConnector; -use Mike42\Escpos\CapabilityProfiles\SimpleCapabilityProfile; -$connector = new WindowsPrintConnector("smb://computer/printer"); -$printer = new Printer($connector, $profile); -``` - -As another example, Star-branded printers use different commands: - -```php -use Mike42\Escpos\PrintConnectors\FilePrintConnector; -use Mike42\Escpos\CapabilityProfiles\StarCapabilityProfile; -$connector = new WindowsPrintConnector("smb://computer/printer"); -$printer = new Printer($connector, $profile); -``` - -Further developing this mechanism is a priority for future releases. - -### Tips & examples -On Linux, your printer device file will be somewhere like `/dev/lp0` (parallel), `/dev/usb/lp1` (USB), `/dev/ttyUSB0` (USB-Serial), `/dev/ttyS0` (serial). - -On Windows, the device files will be along the lines of `LPT1` (parallel) or `COM1` (serial). Use the `WindowsPrintConnector` to tap into system printing on Windows (eg. [Windows USB](https://github.com/mike42/escpos-php/tree/master/example/interface/windows-usb.php), [SMB](https://github.com/mike42/escpos-php/tree/master/example/interface/smb.php) or [Windows LPT](https://github.com/mike42/escpos-php/tree/master/example/interface/windows-lpt.php)) - this submits print jobs via a queue rather than communicating directly with the printer. - -A complete real-world receipt can be found in the code of [Auth](https://github.com/mike42/Auth) in [ReceiptPrinter.php](https://github.com/mike42/Auth/blob/master/lib/misc/ReceiptPrinter.php). It includes justification, boldness, and a barcode. - -Other examples are located in the [example/](https://github.com/mike42/escpos-php/blob/master/example/) directory. - -## Available methods +Available methods +----------------- ### __construct(PrintConnector $connector, AbstractCapabilityProfile $profile) Construct new print object. Parameters: -- `PrintConnector $connector`: The PrintConnector to send data to. +- `PrintConnector $connector`: The PrintConnector to send data to. If not set, output is sent to standard output. - `AbstractCapabilityProfile $profile` Supported features of this printer. If not set, the DefaultCapabilityProfile will be used, which is suitable for Epson printers. See [example/interface/]("https://github.com/mike42/escpos-php/tree/master/example/interface/) for ways to open connections for different platforms and interfaces. @@ -243,7 +159,7 @@ Print a barcode. Parameters: - `string $content`: The information to encode. -- `int $type`: The barcode standard to output. If not specified, `Printer::BARCODE_CODE39` will be used. +- `int $type`: The barcode standard to output. If not specified, `Escpos::BARCODE_CODE39` will be used. Currently supported barcode standards are (depending on your printer): @@ -265,7 +181,7 @@ Cut the paper. Parameters: -- `int $mode`: Cut mode, either `Printer::CUT_FULL` or `Printer::CUT_PARTIAL`. If not specified, `Printer::CUT_FULL` will be used. +- `int $mode`: Cut mode, either `Escpos::CUT_FULL` or `Escpos::CUT_PARTIAL`. If not specified, `Escpos::CUT_FULL` will be used. - `int $lines`: Number of lines to feed before cutting. If not specified, 3 will be used. ### feed($lines) @@ -309,7 +225,7 @@ $printer -> graphics($img); See the [example/](https://github.com/mike42/escpos-php/blob/master/example/) folder for detailed examples. -The function [bitImage()](#bitimageescposimage-image-size) takes the same parameters, and can be used if your printer doesn't support the newer graphics commands. As an additional fallback, the `bitImageColumnFormat()` function is also provided. +The function [bitImage()](#bitimageescposimage-image-size) takes the same parameters, and can be used if your printer doesn't support the newer graphics commands. ### initialize() Initialize printer. This resets formatting back to the defaults. @@ -327,16 +243,16 @@ Parameters: Print the given data as a QR code on the printer. - `string $content`: The content of the code. Numeric data will be more efficiently compacted. -- `int $ec` Error-correction level to use. One of `Printer::QR_ECLEVEL_L` (default), `Printer::QR_ECLEVEL_M`, `Printer::QR_ECLEVEL_Q` or `Printer::QR_ECLEVEL_H`. Higher error correction results in a less compact code. +- `int $ec` Error-correction level to use. One of `Escpos::QR_ECLEVEL_L` (default), `Escpos::QR_ECLEVEL_M`, `Escpos::QR_ECLEVEL_Q` or `Escpos::QR_ECLEVEL_H`. Higher error correction results in a less compact code. - `int $size`: Pixel size to use. Must be 1-16 (default 3) -- `int $model`: QR code model to use. Must be one of `Printer::QR_MODEL_1`, `Printer::QR_MODEL_2` (default) or `Printer::QR_MICRO` (not supported by all printers). +- `int $model`: QR code model to use. Must be one of `Escpos::QR_MODEL_1`, `Escpos::QR_MODEL_2` (default) or `Escpos::QR_MICRO` (not supported by all printers). ### selectPrintMode($mode) Select print mode(s). Parameters: -- `int $mode`: The mode to use. Default is `Printer::MODE_FONT_A`, with no special formatting. This has a similar effect to running `initialize()`. +- `int $mode`: The mode to use. Default is `Escpos::MODE_FONT_A`, with no special formatting. This has a similar effect to running `initialize()`. Several MODE_* constants can be OR'd together passed to this function's `$mode` argument. The valid modes are: @@ -354,13 +270,6 @@ Parameters: - `int $height`: Height in dots. If not specified, 8 will be used. -### setColor($color) -Select print color - on printers that support multiple colors. - -Parameters: - -- `int $color`: Color to use. Must be either `Printer::COLOR_1` (default), or `Printer::COLOR_2` - ### setDoubleStrike($on) Turn double-strike mode on/off. @@ -380,14 +289,14 @@ Select font. Most printers have two fonts (Fonts A and B), and some have a third Parameters: -- `int $font`: The font to use. Must be either `Printer::FONT_A`, `Printer::FONT_B`, or `Printer::FONT_C`. +- `int $font`: The font to use. Must be either `Escpos::FONT_A`, `Escpos::FONT_B`, or `Escpos::FONT_C`. ### setJustification($justification) Select justification. Parameters: -- `int $justification`: One of `Printer::JUSTIFY_LEFT`, `Printer::JUSTIFY_CENTER`, or `Printer::JUSTIFY_RIGHT`. +- `int $justification`: One of `Escpos::JUSTIFY_LEFT`, `Escpos::JUSTIFY_CENTER`, or `Escpos::JUSTIFY_RIGHT`. ### setReverseColors($on) Set black/white reverse mode on or off. In this mode, text is printed white on a black background. @@ -409,7 +318,7 @@ Set underline for printed text. Parameters: -- `int $underline`: Either `true`/`false`, or one of `Printer::UNDERLINE_NONE`, `Printer::UNDERLINE_SINGLE` or `Printer::UNDERLINE_DOUBLE`. Defaults to `Printer::UNDERLINE_SINGLE`. +- `int $underline`: Either `true`/`false`, or one of `Escpos::UNDERLINE_NONE`, `Escpos::UNDERLINE_SINGLE` or `Escpos::UNDERLINE_DOUBLE`. Defaults to `Escpos::UNDERLINE_SINGLE`. ### text($str) Add text to the buffer. Text should either be followed by a line-break, or `feed()` should be called after this. @@ -418,46 +327,26 @@ Parameters: - `string $str`: The string to print. -# Further notes +Further notes +------------- Posts I've written up for people who are learning how to use receipt printers: * [What is ESC/POS, and how do I use it?](http://mike.bitrevision.com/blog/what-is-escpos-and-how-do-i-use-it), which documents the output of test.php. * [Setting up an Epson receipt printer](http://mike.bitrevision.com/blog/2014-20-26-setting-up-an-epson-receipt-printer) * [Getting a USB receipt printer working on Linux](http://mike.bitrevision.com/blog/2015-03-getting-a-usb-receipt-printer-working-on-linux) -# Development - -This code is MIT licensed, and you are encouraged to contribute any modifications back to the project. - -For development, it's suggested that you load `imagick` and `gd` `Xdebug` PHP modules, and install `composer` and `phpunit`. - -The tests are executed on [Travis CI](https://travis-ci.org/mike42/escpos-php) over versions of PHP from 5.3 up to 5.6, 7, and HHVM. Earlier versions of PHP are not supported. - -Fetch a copy of this code and load idependencies with composer: - - git clone https://github.com/mike42/escpos-php - cd escpos-php/ - composer install - -Execute unit tests via `phpunit`: - - phpunit --configuration test/phpunit.xml --coverage-text - -Pull requests and bug reports welcome. - - +- [ronisaha/php-esc-pos](https://github.com/ronisaha/php-esc-pos) - + diff --git a/htdocs/includes/mike42/escpos-php/autoload.php b/htdocs/includes/mike42/escpos-php/autoload.php deleted file mode 100644 index 7779486b824..00000000000 --- a/htdocs/includes/mike42/escpos-php/autoload.php +++ /dev/null @@ -1,26 +0,0 @@ -setBarcodeHeight ( 40 ); + +/* Text position */ +$printer->selectPrintMode ( Escpos::MODE_DOUBLE_HEIGHT | Escpos::MODE_DOUBLE_WIDTH ); +$printer->text ( "Text position\n" ); +$printer->selectPrintMode (); +$hri = array ( + Escpos::BARCODE_TEXT_NONE => "No text", + Escpos::BARCODE_TEXT_ABOVE => "Above", + Escpos::BARCODE_TEXT_BELOW => "Below", + Escpos::BARCODE_TEXT_ABOVE | Escpos::BARCODE_TEXT_BELOW => "Both" +); +foreach ( $hri as $position => $caption ) { + $printer->text ( $caption . "\n" ); + $printer->setBarcodeTextPosition ( $position ); + $printer->barcode ( "012345678901", Escpos::BARCODE_JAN13 ); + $printer->feed (); +} + +/* Barcode types */ +$standards = array ( + Escpos::BARCODE_UPCA => array ( + "title" => "UPC-A", + "caption" => "Fixed-length numeric product barcodes.", + "example" => array ( + array ( + "caption" => "12 char numeric including (wrong) check digit.", + "content" => "012345678901" + ), + array ( + "caption" => "Send 11 chars to add check digit automatically.", + "content" => "01234567890" + ) + ) + ), + Escpos::BARCODE_UPCE => array ( + "title" => "UPC-E", + "caption" => "Fixed-length numeric compact product barcodes.", + "example" => array ( + array ( + "caption" => "6 char numeric - auto check digit & NSC", + "content" => "123456" + ), + array ( + "caption" => "7 char numeric - auto check digit", + "content" => "0123456" + ), + array ( + "caption" => "8 char numeric", + "content" => "01234567" + ), + array ( + "caption" => "11 char numeric - auto check digit", + "content" => "01234567890" + ), + array ( + "caption" => "12 char numeric including (wrong) check digit", + "content" => "012345678901" + ) + ) + ), + Escpos::BARCODE_JAN13 => array ( + "title" => "JAN13/EAN13", + "caption" => "Fixed-length numeric barcodes.", + "example" => array ( + array ( + "caption" => "12 char numeric - auto check digit", + "content" => "012345678901" + ), + array ( + "caption" => "13 char numeric including (wrong) check digit", + "content" => "0123456789012" + ) + ) + ), + Escpos::BARCODE_JAN8 => array ( + "title" => "JAN8/EAN8", + "caption" => "Fixed-length numeric barcodes.", + "example" => array ( + array ( + "caption" => "7 char numeric - auto check digit", + "content" => "0123456" + ), + array ( + "caption" => "8 char numeric including (wrong) check digit", + "content" => "01234567" + ) + ) + ), + Escpos::BARCODE_CODE39 => array ( + "title" => "Code39", + "caption" => "Variable length alphanumeric w/ some special chars.", + "example" => array ( + array ( + "caption" => "Text, numbers, spaces", + "content" => "ABC 012" + ), + array ( + "caption" => "Special characters", + "content" => "$%+-./" + ), + array ( + "caption" => "Extra char (*) Used as start/stop", + "content" => "*TEXT*" + ) + ) + ), + Escpos::BARCODE_ITF => array ( + "title" => "ITF", + "caption" => "Variable length numeric w/even number of digits,\nas they are encoded in pairs.", + "example" => array ( + array ( + "caption" => "Numeric- even number of digits", + "content" => "0123456789" + ) + ) + ), + Escpos::BARCODE_CODABAR => array ( + "title" => "Codabar", + "caption" => "Varaible length numeric with some allowable\nextra characters. ABCD/abcd must be used as\nstart/stop characters (one at the start, one\nat the end) to distinguish between barcode\napplications.", + "example" => array ( + array ( + "caption" => "Numeric w/ A A start/stop. ", + "content" => "A012345A" + ), + array ( + "caption" => "Extra allowable characters", + "content" => "A012$+-./:A" + ) + ) + ), + Escpos::BARCODE_CODE93 => array ( + "title" => "Code93", + "caption" => "Variable length- any ASCII is available", + "example" => array ( + array ( + "caption" => "Text", + "content" => "012abcd" + ) + ) + ), + Escpos::BARCODE_CODE128 => array ( + "title" => "Code128", + "caption" => "Variable length- any ASCII is available", + "example" => array ( + array ( + "caption" => "Code set A uppercase & symbols", + "content" => "{A" . "012ABCD" + ), + array ( + "caption" => "Code set B general text", + "content" => "{B" . "012ABCDabcd" + ), + array ( + "caption" => "Code set C compact numbers\n Sending chr(21) chr(32) chr(43)", + "content" => "{C" . chr ( 21 ) . chr ( 32 ) . chr ( 43 ) + ) + ) + ) +); +$printer->setBarcodeTextPosition ( Escpos::BARCODE_TEXT_BELOW ); +foreach ( $standards as $type => $standard ) { + $printer->selectPrintMode ( Escpos::MODE_DOUBLE_HEIGHT | Escpos::MODE_DOUBLE_WIDTH ); + $printer->text ( $standard ["title"] . "\n" ); + $printer->selectPrintMode (); + $printer->text ( $standard ["caption"] . "\n\n" ); + foreach ( $standard ["example"] as $id => $barcode ) { + $printer->setEmphasis ( true ); + $printer->text ( $barcode ["caption"] . "\n" ); + $printer->setEmphasis ( false ); + $printer->text ( "Content: " . $barcode ["content"] . "\n" ); + $printer->barcode ( $barcode ["content"], $type ); + $printer->feed (); + } +} +$printer->cut (); +$printer->close (); + diff --git a/htdocs/includes/mike42/escpos-php/example/bit-image.php b/htdocs/includes/mike42/escpos-php/example/bit-image.php new file mode 100644 index 00000000000..f3a4054d98f --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/bit-image.php @@ -0,0 +1,32 @@ + text("These example images are printed with the older\nbit image print command. You should only use\n\$p -> bitImage() if \$p -> graphics() does not\nwork on your printer.\n\n"); + + $printer -> bitImage($tux); + $printer -> text("Regular Tux (bit image).\n"); + $printer -> feed(); + + $printer -> bitImage($tux, Escpos::IMG_DOUBLE_WIDTH); + $printer -> text("Wide Tux (bit image).\n"); + $printer -> feed(); + + $printer -> bitImage($tux, Escpos::IMG_DOUBLE_HEIGHT); + $printer -> text("Tall Tux (bit image).\n"); + $printer -> feed(); + + $printer -> bitImage($tux, Escpos::IMG_DOUBLE_WIDTH | Escpos::IMG_DOUBLE_HEIGHT); + $printer -> text("Large Tux in correct proportion (bit image).\n"); +} catch(Exception $e) { + /* Images not supported on your PHP, or image file not found */ + $printer -> text($e -> getMessage() . "\n"); +} + +$printer -> cut(); +$printer -> close(); +?> diff --git a/htdocs/includes/mike42/escpos-php/example/character-encodings-with-images.php b/htdocs/includes/mike42/escpos-php/example/character-encodings-with-images.php new file mode 100644 index 00000000000..a98bdbc96ae --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/character-encodings-with-images.php @@ -0,0 +1,59 @@ + selectPrintMode(Escpos::MODE_DOUBLE_HEIGHT | Escpos::MODE_EMPHASIZED | Escpos::MODE_DOUBLE_WIDTH); + $printer -> text("Implemented languages\n"); + $printer -> selectPrintMode(); + foreach($inputsOk as $label => $str) { + $printer -> setEmphasis(true); + $printer -> text($label . ":\n"); + $printer -> setEmphasis(false); + foreach($buffers as $buffer) { + $printer -> setPrintBuffer($buffer); + $printer -> text($str); + } + $printer -> setPrintBuffer($buffers[0]); + } + $printer -> feed(); + + $printer -> selectPrintMode(Escpos::MODE_DOUBLE_HEIGHT | Escpos::MODE_EMPHASIZED | Escpos::MODE_DOUBLE_WIDTH); + $printer -> text("Works in progress\n"); + $printer -> selectPrintMode(); + foreach($inputsNotOk as $label => $str) { + $printer -> setEmphasis(true); + $printer -> text($label . ":\n"); + $printer -> setEmphasis(false); + foreach($buffers as $buffer) { + $printer -> setPrintBuffer($buffer); + $printer -> text($str); + } + $printer -> setPrintBuffer($buffers[0]); + } + $printer -> cut(); + + /* Close printer */ + $printer -> close(); +} catch(Exception $e) { + echo "Couldn't print to this printer: " . $e -> getMessage() . "\n"; +} + diff --git a/htdocs/includes/mike42/escpos-php/example/character-encodings.php b/htdocs/includes/mike42/escpos-php/example/character-encodings.php new file mode 100644 index 00000000000..8a42ecd0847 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/character-encodings.php @@ -0,0 +1,58 @@ + selectPrintMode(Escpos::MODE_DOUBLE_HEIGHT | Escpos::MODE_EMPHASIZED | Escpos::MODE_DOUBLE_WIDTH); + $printer -> text("Implemented languages\n"); + $printer -> selectPrintMode(); + foreach($inputsOk as $label => $str) { + $printer -> setEmphasis(true); + $printer -> text($label . ":\n"); + $printer -> setEmphasis(false); + $printer -> text($str); + } + $printer -> feed(); + + $printer -> selectPrintMode(Escpos::MODE_DOUBLE_HEIGHT | Escpos::MODE_EMPHASIZED | Escpos::MODE_DOUBLE_WIDTH); + $printer -> text("Works in progress\n"); + $printer -> selectPrintMode(); + foreach($inputsNotOk as $label => $str) { + $printer -> setEmphasis(true); + $printer -> text($label . ":\n"); + $printer -> setEmphasis(false); + $printer -> text($str); + } + $printer -> cut(); + + /* Close printer */ + $printer -> close(); +} catch(Exception $e) { + echo "Couldn't print to this printer: " . $e -> getMessage() . "\n"; +} + diff --git a/htdocs/includes/mike42/escpos-php/example/character-tables.php b/htdocs/includes/mike42/escpos-php/example/character-tables.php new file mode 100644 index 00000000000..1ce2c5c9f1e --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/character-tables.php @@ -0,0 +1,71 @@ + getSupportedCodePages(); +$first = true; // Print larger table for first code-page. +foreach($codePages as $table => $name) { + /* Change printer code page */ + $printer -> selectCharacterTable(255); + $printer -> selectCharacterTable($table); + /* Select & print a label for it */ + $label = $name; + if($name === false) { + $label= " (not matched to iconv table)"; + } + $printer -> setEmphasis(true); + $printer -> textRaw("Table $table: $label\n"); + $printer -> setEmphasis(false); + if($name === false && !$verbose) { + continue; // Skip non-recognised + } + /* Print a table of available characters (first table is larger than subsequent ones */ + if($first) { + $first = false; + compactCharTable($printer, 1, true); + } else { + compactCharTable($printer); + } +} +$printer -> cut(); +$printer -> close(); + +function compactCharTable($printer, $start = 4, $header = false) { + /* Output a compact character table for the current encoding */ + $chars = str_repeat(' ', 256); + for($i = 0; $i < 255; $i++) { + $chars[$i] = ($i > 32 && $i != 127) ? chr($i) : ' '; + } + if($header) { + $printer -> setEmphasis(true); + $printer -> textRaw(" 0123456789ABCDEF0123456789ABCDEF\n"); + $printer -> setEmphasis(false); + } + for($y = $start; $y < 8; $y++) { + $printer -> setEmphasis(true); + $printer -> textRaw(strtoupper(dechex($y * 2)) . " "); + $printer -> setEmphasis(false); + $printer -> textRaw(substr($chars, $y * 32, 32) . "\n"); + } +} diff --git a/htdocs/includes/mike42/escpos-php/example/demo.php b/htdocs/includes/mike42/escpos-php/example/demo.php new file mode 100644 index 00000000000..1c99e5cc880 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/demo.php @@ -0,0 +1,167 @@ + + */ +require_once(dirname(__FILE__) . "/../Escpos.php"); +$printer = new Escpos(); + +/* Initialize */ +$printer -> initialize(); + +/* Text */ +$printer -> text("Hello world\n"); +$printer -> cut(); + +/* Line feeds */ +$printer -> text("ABC"); +$printer -> feed(7); +$printer -> text("DEF"); +$printer -> feedReverse(3); +$printer -> text("GHI"); +$printer -> feed(); +$printer -> cut(); + +/* Font modes */ +$modes = array( + Escpos::MODE_FONT_B, + Escpos::MODE_EMPHASIZED, + Escpos::MODE_DOUBLE_HEIGHT, + Escpos::MODE_DOUBLE_WIDTH, + Escpos::MODE_UNDERLINE); +for($i = 0; $i < pow(2, count($modes)); $i++) { + $bits = str_pad(decbin($i), count($modes), "0", STR_PAD_LEFT); + $mode = 0; + for($j = 0; $j < strlen($bits); $j++) { + if(substr($bits, $j, 1) == "1") { + $mode |= $modes[$j]; + } + } + $printer -> selectPrintMode($mode); + $printer -> text("ABCDEFGHIJabcdefghijk\n"); +} +$printer -> selectPrintMode(); // Reset +$printer -> cut(); + +/* Underline */ +for($i = 0; $i < 3; $i++) { + $printer -> setUnderline($i); + $printer -> text("The quick brown fox jumps over the lazy dog\n"); +} +$printer -> setUnderline(0); // Reset +$printer -> cut(); + +/* Cuts */ +$printer -> text("Partial cut\n(not available on all printers)\n"); +$printer -> cut(Escpos::CUT_PARTIAL); +$printer -> text("Full cut\n"); +$printer -> cut(Escpos::CUT_FULL); + +/* Emphasis */ +for($i = 0; $i < 2; $i++) { + $printer -> setEmphasis($i == 1); + $printer -> text("The quick brown fox jumps over the lazy dog\n"); +} +$printer -> setEmphasis(false); // Reset +$printer -> cut(); + +/* Double-strike (looks basically the same as emphasis) */ +for($i = 0; $i < 2; $i++) { + $printer -> setDoubleStrike($i == 1); + $printer -> text("The quick brown fox jumps over the lazy dog\n"); +} +$printer -> setDoubleStrike(false); +$printer -> cut(); + +/* Fonts (many printers do not have a 'Font C') */ +$fonts = array( + Escpos::FONT_A, + Escpos::FONT_B, + Escpos::FONT_C); +for($i = 0; $i < count($fonts); $i++) { + $printer -> setFont($fonts[$i]); + $printer -> text("The quick brown fox jumps over the lazy dog\n"); +} +$printer -> setFont(); // Reset +$printer -> cut(); + +/* Justification */ +$justification = array( + Escpos::JUSTIFY_LEFT, + Escpos::JUSTIFY_CENTER, + Escpos::JUSTIFY_RIGHT); +for($i = 0; $i < count($justification); $i++) { + $printer -> setJustification($justification[$i]); + $printer -> text("A man a plan a canal panama\n"); +} +$printer -> setJustification(); // Reset +$printer -> cut(); + +/* Barcodes - see barcode.php for more detail */ +$printer -> setBarcodeHeight(80); +$printer->setBarcodeTextPosition ( Escpos::BARCODE_TEXT_BELOW ); +$printer -> barcode("9876"); +$printer -> feed(); +$printer -> cut(); + +/* Graphics - this demo will not work on some non-Epson printers */ +try { + $logo = new EscposImage("resources/escpos-php.png"); + $imgModes = array( + Escpos::IMG_DEFAULT, + Escpos::IMG_DOUBLE_WIDTH, + Escpos::IMG_DOUBLE_HEIGHT, + Escpos::IMG_DOUBLE_WIDTH | Escpos::IMG_DOUBLE_HEIGHT + ); + foreach($imgModes as $mode) { + $printer -> graphics($logo, $mode); + } +} catch(Exception $e) { + /* Images not supported on your PHP, or image file not found */ + $printer -> text($e -> getMessage() . "\n"); +} +$printer -> cut(); + +/* Bit image */ +try { + $logo = new EscposImage("resources/escpos-php.png"); + $imgModes = array( + Escpos::IMG_DEFAULT, + Escpos::IMG_DOUBLE_WIDTH, + Escpos::IMG_DOUBLE_HEIGHT, + Escpos::IMG_DOUBLE_WIDTH | Escpos::IMG_DOUBLE_HEIGHT + ); + foreach($imgModes as $mode) { + $printer -> bitImage($logo, $mode); + } +} catch(Exception $e) { + /* Images not supported on your PHP, or image file not found */ + $printer -> text($e -> getMessage() . "\n"); +} +$printer -> cut(); + +/* QR Code - see also the more in-depth demo at qr-code.php */ +$testStr = "Testing 123"; +$models = array( + Escpos::QR_MODEL_1 => "QR Model 1", + Escpos::QR_MODEL_2 => "QR Model 2 (default)", + Escpos::QR_MICRO => "Micro QR code\n(not supported on all printers)"); +foreach($models as $model => $name) { + $printer -> qrCode($testStr, Escpos::QR_ECLEVEL_L, 3, $model); + $printer -> text("$name\n"); + $printer -> feed(); +} +$printer -> cut(); + +/* Pulse */ +$printer -> pulse(); + +/* Always close the printer! On some PrintConnectors, no actual + * data is sent until the printer is closed. */ +$printer -> close(); +?> diff --git a/htdocs/includes/mike42/escpos-php/example/graphics.php b/htdocs/includes/mike42/escpos-php/example/graphics.php new file mode 100644 index 00000000000..7650b6b9a08 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/graphics.php @@ -0,0 +1,32 @@ + graphics($tux); + $printer -> text("Regular Tux.\n"); + $printer -> feed(); + + $printer -> graphics($tux, Escpos::IMG_DOUBLE_WIDTH); + $printer -> text("Wide Tux.\n"); + $printer -> feed(); + + $printer -> graphics($tux, Escpos::IMG_DOUBLE_HEIGHT); + $printer -> text("Tall Tux.\n"); + $printer -> feed(); + + $printer -> graphics($tux, Escpos::IMG_DOUBLE_WIDTH | Escpos::IMG_DOUBLE_HEIGHT); + $printer -> text("Large Tux in correct proportion.\n"); + + $printer -> cut(); +} catch(Exception $e) { + /* Images not supported on your PHP, or image file not found */ + $printer -> text($e -> getMessage() . "\n"); +} + +$printer -> close(); +?> diff --git a/htdocs/includes/mike42/escpos-php/example/interface/README.md b/htdocs/includes/mike42/escpos-php/example/interface/README.md new file mode 100644 index 00000000000..96fe2eed2fb --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/interface/README.md @@ -0,0 +1,8 @@ +Interfaces +---------- +This directory contains boilerpalte code to show you how to open a print connector +to printers which are connected in different ways. + +To get a list of supported interfaces and operating systems, see the main README.md file for the project. + +If you have a printer interface with no example, and you want to help put one together, then please lodge a request on the bug tracker: https://github.com/mike42/escpos-php/issues diff --git a/htdocs/includes/mike42/escpos-php/example/interface/ethernet.php b/htdocs/includes/mike42/escpos-php/example/interface/ethernet.php new file mode 100644 index 00000000000..bfea443903c --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/interface/ethernet.php @@ -0,0 +1,22 @@ + text("Hello World!\n"); + $printer -> cut(); + + /* Close printer */ + $printer -> close(); +} catch(Exception $e) { + echo "Couldn't print to this printer: " . $e -> getMessage() . "\n"; +} + diff --git a/htdocs/includes/mike42/escpos-php/example/interface/linux-usb.php b/htdocs/includes/mike42/escpos-php/example/interface/linux-usb.php new file mode 100644 index 00000000000..9e1d96b91c0 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/interface/linux-usb.php @@ -0,0 +1,33 @@ + text("Hello World!\n"); + $printer -> cut(); + + /* Close printer */ + $printer -> close(); +} catch(Exception $e) { + echo "Couldn't print to this printer: " . $e -> getMessage() . "\n"; +} + diff --git a/htdocs/includes/mike42/escpos-php/example/interface/smb.php b/htdocs/includes/mike42/escpos-php/example/interface/smb.php new file mode 100644 index 00000000000..40988f553ee --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/interface/smb.php @@ -0,0 +1,51 @@ + testfile + * ## If you need authentication, use "net use" to hook up the printer: + * # net use "\\computername\Receipt Printer" /user:Guest + * # net use "\\computername\Receipt Printer" /user:Bob secret + * # net use "\\computername\Receipt Printer" /user:workgroup\Bob secret + * copy testfile "\\computername\Receipt Printer" + * del testfile + * + * GNU/Linux: + * # No authentication + * echo "Hello World" | smbclient "//computername/Receipt Printer" -c "print -" -N + * # Guest login + * echo "Hello World" | smbclient "//computername/Receipt Printer" -U Guest -c "print -" -N + * # Basic username/password + * echo "Hello World" | smbclient "//computername/Receipt Printer" secret -U "Bob" -c "print -" + * # Including domain name + * echo "Hello World" | smbclient "//computername/Receipt Printer" secret -U "workgroup\\Bob" -c "print -" + */ +try { + // Enter the share name for your printer here, as a smb:// url format + $connector = null; + //$connector = new WindowsPrintConnector("smb://computername/Receipt Printer"); + //$connector = new WindowsPrintConnector("smb://Guest@computername/Receipt Printer"); + //$connector = new WindowsPrintConnector("smb://FooUser:secret@computername/workgroup/Receipt Printer"); + //$connector = new WindowsPrintConnector("smb://User:secret@computername/Receipt Printer"); + + /* Print a "Hello world" receipt" */ + $printer = new Escpos($connector); + $printer -> text("Hello World!\n"); + $printer -> cut(); + + /* Close printer */ + $printer -> close(); +} catch(Exception $e) { + echo "Couldn't print to this printer: " . $e -> getMessage() . "\n"; +} diff --git a/htdocs/includes/mike42/escpos-php/example/interface/windows-lpt.php b/htdocs/includes/mike42/escpos-php/example/interface/windows-lpt.php new file mode 100644 index 00000000000..76294425cdc --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/interface/windows-lpt.php @@ -0,0 +1,30 @@ + LPT1 + */ +try { + $connector = null; + //$connector = new WindowsPrintConnector("LPT1"); + + // A FilePrintConnector will also work, but on non-Windows systems, writes + // to an actual file called 'LPT1' rather than giving a useful error. + // $connector = new FilePrintConnector("LPT1"); + + /* Print a "Hello world" receipt" */ + $printer = new Escpos($connector); + $printer -> text("Hello World!\n"); + $printer -> cut(); + + /* Close printer */ + $printer -> close(); +} catch(Exception $e) { + echo "Couldn't print to this printer: " . $e -> getMessage() . "\n"; +} diff --git a/htdocs/includes/mike42/escpos-php/example/interface/windows-usb.php b/htdocs/includes/mike42/escpos-php/example/interface/windows-usb.php new file mode 100644 index 00000000000..d4b550e2f93 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/interface/windows-usb.php @@ -0,0 +1,32 @@ + testfile + * copy testfile "\\%COMPUTERNAME%\Receipt Printer" + * del testfile + */ +try { + // Enter the share name for your USB printer here + $connector = null; + //$connector = new WindowsPrintConnector("Receipt Printer"); + + /* Print a "Hello world" receipt" */ + $printer = new Escpos($connector); + $printer -> text("Hello World!\n"); + $printer -> cut(); + + /* Close printer */ + $printer -> close(); +} catch(Exception $e) { + echo "Couldn't print to this printer: " . $e -> getMessage() . "\n"; +} diff --git a/htdocs/includes/mike42/escpos-php/example/print-from-html.php b/htdocs/includes/mike42/escpos-php/example/print-from-html.php new file mode 100644 index 00000000000..f0751d7a4ae --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/print-from-html.php @@ -0,0 +1,53 @@ + 225 below) and printing w/ Escpos::IMG_DOUBLE_WIDTH | Escpos::IMG_DOUBLE_HEIGHT + */ +try { + /* Set up command */ + $source = "http://en.m.wikipedia.org/wiki/ESC/P"; + $width = 550; + $dest = tempnam(sys_get_temp_dir(), 'escpos') . ".png"; + $cmd = sprintf("wkhtmltoimage -n -q --width %s %s %s", + escapeshellarg($width), + escapeshellarg($source), + escapeshellarg($dest)); + + /* Run wkhtmltoimage */ + ob_start(); + system($cmd); // Can also use popen() for better control of process + $outp = ob_get_contents(); + ob_end_clean(); + if(!file_exists($dest)) { + throw new Exception("Command $cmd failed: $outp"); + } + + /* Load up the image */ + try { + $img = new EscposImage($dest); + } catch(Exception $e) { + unlink($dest); + throw $e; + } + unlink($dest); + + /* Print it */ + $printer = new Escpos(); // Add connector for your printer here. + $printer -> bitImage($img); // bitImage() seems to allow larger images than graphics() on the TM-T20. + $printer -> cut(); + $printer -> close(); +} catch(Exception $e) { + echo $e -> getMessage(); +} + diff --git a/htdocs/includes/mike42/escpos-php/example/print-from-pdf.php b/htdocs/includes/mike42/escpos-php/example/print-from-pdf.php new file mode 100644 index 00000000000..ba6a4c67a96 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/print-from-pdf.php @@ -0,0 +1,71 @@ + graphics($page); + } + $printer -> cut(); + $printer -> close(); +} catch(Exception $e) { + /* + * loadPdf() throws exceptions if files or not found, or you don't have the + * imagick extension to read PDF's + */ + echo $e -> getMessage() . "\n"; + exit(0); +} + + +/* + * 2: Speed up printing by roughly halving the resolution, and printing double-size. + * This gives a 75% speed increase at the expense of some quality. + * + * Reduce the page width further if necessary: if it extends past the printing area, your prints will be very slow. + */ +$printer = new Escpos(); +$pdf = 'resources/document.pdf'; +$pages = EscposImage::loadPdf($pdf, 260); +foreach($pages as $page) { + $printer -> graphics($page, Escpos::IMG_DOUBLE_HEIGHT | Escpos::IMG_DOUBLE_WIDTH); +} +$printer -> cut(); +$printer -> close(); + +/* + * 3: PDF printing still too slow? If you regularly print the same files, serialize & compress your + * EscposImage objects (after printing[1]), instead of throwing them away. + * + * (You can also do this to print logos on computers which don't have an + * image processing library, by preparing a serialized version of your logo on your PC) + * + * [1]After printing, the pixels are loaded and formatted for the print command you used, so even a raspberry pi can print complex PDF's quickly. + */ +$printer = new Escpos(); +$pdf = 'resources/document.pdf'; +$ser = 'resources/document.z'; +if(!file_exists($ser)) { + $pages = EscposImage::loadPdf($pdf); +} else { + $pages = unserialize(gzuncompress(file_get_contents($ser))); +} + +foreach($pages as $page) { + $printer -> graphics($page); +} +$printer -> cut(); +$printer -> close(); + +if(!file_exists($ser)) { + file_put_contents($ser, gzcompress(serialize($pages))); +} diff --git a/htdocs/includes/mike42/escpos-php/example/qr-code.php b/htdocs/includes/mike42/escpos-php/example/qr-code.php new file mode 100644 index 00000000000..dbf21aaedf4 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/qr-code.php @@ -0,0 +1,81 @@ + qrCode($testStr); +$printer -> text("Most simple example\n"); +$printer -> feed(); + +// Demo that alignment is the same as text +$printer -> setJustification(Escpos::JUSTIFY_CENTER); +$printer -> qrCode($testStr); +$printer -> text("Same example, centred\n"); +$printer -> setJustification(); +$printer -> feed(); + +// Demo of numeric data being packed more densly +title($printer, "Data encoding\n"); +$test = array( + "Numeric" => "0123456789012345678901234567890123456789", + "Alphanumeric" => "abcdefghijklmnopqrstuvwxyzabcdefghijklmn", + "Binary" => str_repeat("\0", 40)); +foreach($test as $type => $data) { + $printer -> qrCode($data); + $printer -> text("$type\n"); + $printer -> feed(); +} + +// Demo of error correction +title($printer, "Error correction\n"); +$ec = array( + Escpos::QR_ECLEVEL_L => "L", + Escpos::QR_ECLEVEL_M => "M", + Escpos::QR_ECLEVEL_Q => "Q", + Escpos::QR_ECLEVEL_H => "H"); +foreach($ec as $level => $name) { + $printer -> qrCode($testStr, $level); + $printer -> text("Error correction $name\n"); + $printer -> feed(); +} + +// Change size +title($printer, "Pixel size\n"); +$sizes = array( + 1 => "(minimum)", + 2 => "", + 3 => "(default)", + 4 => "", + 5 => "", + 10 => "", + 16 => "(maximum)"); +foreach($sizes as $size => $label) { + $printer -> qrCode($testStr, Escpos::QR_ECLEVEL_L, $size); + $printer -> text("Pixel size $size $label\n"); + $printer -> feed(); +} + +// Change model +title($printer, "QR model\n"); +$models = array( + Escpos::QR_MODEL_1 => "QR Model 1", + Escpos::QR_MODEL_2 => "QR Model 2 (default)", + Escpos::QR_MICRO => "Micro QR code\n(not supported on all printers)"); +foreach($models as $model => $name) { + $printer -> qrCode($testStr, Escpos::QR_ECLEVEL_L, 3, $model); + $printer -> text("$name\n"); + $printer -> feed(); +} + +// Cut & close +$printer -> cut(); +$printer -> close(); + +function title(Escpos $printer, $str) { + $printer -> selectPrintMode(Escpos::MODE_DOUBLE_HEIGHT | Escpos::MODE_DOUBLE_WIDTH); + $printer -> text($str); + $printer -> selectPrintMode(); +} diff --git a/htdocs/includes/mike42/escpos-php/example/receipt-with-logo.php b/htdocs/includes/mike42/escpos-php/example/receipt-with-logo.php new file mode 100644 index 00000000000..8322c09c750 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/receipt-with-logo.php @@ -0,0 +1,96 @@ + setJustification(Escpos::JUSTIFY_CENTER); +$printer -> graphics($logo); + +/* Name of shop */ +$printer -> selectPrintMode(Escpos::MODE_DOUBLE_WIDTH); +$printer -> text("ExampleMart Ltd.\n"); +$printer -> selectPrintMode(); +$printer -> text("Shop No. 42.\n"); +$printer -> feed(); + +/* Title of receipt */ +$printer -> setEmphasis(true); +$printer -> text("SALES INVOICE\n"); +$printer -> setEmphasis(false); + +/* Items */ +$printer -> setJustification(Escpos::JUSTIFY_LEFT); +$printer -> setEmphasis(true); +$printer -> text(new item('', '$')); +$printer -> setEmphasis(false); +foreach($items as $item) { + $printer -> text($item); +} +$printer -> setEmphasis(true); +$printer -> text($subtotal); +$printer -> setEmphasis(false); +$printer -> feed(); + +/* Tax and total */ +$printer -> text($tax); +$printer -> selectPrintMode(Escpos::MODE_DOUBLE_WIDTH); +$printer -> text($total); +$printer -> selectPrintMode(); + +/* Footer */ +$printer -> feed(2); +$printer -> setJustification(Escpos::JUSTIFY_CENTER); +$printer -> text("Thank you for shopping at ExampleMart\n"); +$printer -> text("For trading hours, please visit example.com\n"); +$printer -> feed(2); +$printer -> text($date . "\n"); + +/* Cut the receipt and open the cash drawer */ +$printer -> cut(); +$printer -> pulse(); + +$printer -> close(); + +/* A wrapper to do organise item names & prices into columns */ +class item { + private $name; + private $price; + private $dollarSign; + + public function __construct($name = '', $price = '', $dollarSign = false) { + $this -> name = $name; + $this -> price = $price; + $this -> dollarSign = $dollarSign; + } + + public function __toString() { + $rightCols = 10; + $leftCols = 38; + if($this -> dollarSign) { + $leftCols = $leftCols / 2 - $rightCols / 2; + } + $left = str_pad($this -> name, $leftCols) ; + + $sign = ($this -> dollarSign ? '$ ' : ''); + $right = str_pad($sign . $this -> price, $rightCols, ' ', STR_PAD_LEFT); + return "$left$right\n"; + } +} +?> diff --git a/htdocs/includes/mike42/escpos-php/example/resources/character-encoding-test-strings.inc b/htdocs/includes/mike42/escpos-php/example/resources/character-encoding-test-strings.inc new file mode 100644 index 00000000000..caca76c4479 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/resources/character-encoding-test-strings.inc @@ -0,0 +1,35 @@ + "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther spillede på xylofon.\n", + "German" => "Falsches Üben von Xylophonmusik quält jeden größeren Zwerg.\n", + "Greek" => "Ξεσκεπάζω την ψυχοφθόρα βδελυγμία\n", + "English" => "The quick brown fox jumps over the lazy dog.\n", + "Spanish" => "El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro.\n", + "French" => "Le cœur déçu mais l'âme plutôt naïve, Louÿs rêva de crapaüter en canoë au delà des îles, près du mälström où brûlent les novæ.\n", + "Irish Gaelic" => "D'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh.\n", + "Hungarian" => "Árvíztűrő tükörfúrógép.\n", + "Icelandic" => "Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa.\n", + "Latvian" => "Glāžšķūņa rūķīši dzērumā čiepj Baha koncertflīģeļu vākus.\n", + "Polish" => "Pchnąć w tę łódź jeża lub ośm skrzyń fig.\n", + "Russian" => "В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!\n", + "Turkish" => "Pijamalı hasta, yağız şoföre çabucak güvendi.\n", + "Japanese (Katakana half-width)" => implode("\n", array("イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム", "ウイノオクヤマ ケフコエテ アサキユメミシ エヒモセスン")) . "\n" + ); + +/* + * These strings are not expected to print correctly, if at all, even on an Epson printer. This is due to a mix of + * escpos driver, printer, and PHP language support issues. + * + * They are included here as a collection of things not yet implemented. + */ +$inputsNotOk = array( + "Thai (No character encoder available)" => "นายสังฆภัณฑ์ เฮงพิทักษ์ฝั่ง ผู้เฒ่าซึ่งมีอาชีพเป็นฅนขายฃวด ถูกตำรวจปฏิบัติการจับฟ้องศาล ฐานลักนาฬิกาคุณหญิงฉัตรชฎา ฌานสมาธิ\n", + "Japanese (Hiragana)" => implode("\n", array("いろはにほへとちりぬるを", " わかよたれそつねならむ", "うゐのおくやまけふこえて", "あさきゆめみしゑひもせす")) . "\n", + "Japanese (Katakana full-width)" => implode("\n", array("イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム", "ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン")) . "\n", + "Arabic (RTL not supported, encoding issues)" => "صِف خَلقَ خَودِ كَمِثلِ الشَمسِ إِذ بَزَغَت — يَحظى الضَجيعُ بِها نَجلاءَ مِعطارِ" . "\n", + "Hebrew (RTL not supported, line break issues)" => "דג סקרן שט בים מאוכזב ולפתע מצא לו חברה איך הקליטה" . "\n" + ); + diff --git a/htdocs/includes/mike42/escpos-php/example/resources/document.odt b/htdocs/includes/mike42/escpos-php/example/resources/document.odt new file mode 100644 index 0000000000000000000000000000000000000000..4bbd7f98b654dd1f8086589f22bfb9ed7ecb4c9a GIT binary patch literal 39751 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%0DXC4u*ZB5o$LxKAV8c6QUjWTfwW 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 literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/example/resources/document.pdf b/htdocs/includes/mike42/escpos-php/example/resources/document.pdf new file mode 100644 index 0000000000000000000000000000000000000000..93a702337f1b09cfcb96ddce75a0c6421b69251e GIT binary patch literal 24389 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 RuDa7m%-5OL40^FJe*+_L<7)r_ literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/example/resources/escpos-php-small.png b/htdocs/includes/mike42/escpos-php/example/resources/escpos-php-small.png new file mode 100644 index 0000000000000000000000000000000000000000..3c74d2d57a29e0398f706efd0e2f4531c036584a GIT binary patch literal 1941 zcmV;G2Wt3004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0063uBQgL0010qNS#tmY3ljhU3ljkVnw%H_00wqRL_t(o!|j=SP?Xmh#(&>l zSaz3-7a$0t#I+JdEt*SdVzto;>J%@jY1LGl#3|#nbtcX8MGaPGe0QlGv(= zv6I%pF&Y)r*2Y2{gP101yfEOUTx>vVKtW`e{ht282Q2JjVw)++4A0D-J?Gu$yyw~X zob#Tu-@*XC05Gg~FH?!>Hx;$KgV`-6+t`~hD7s&iG;#GPxm*Q@WpPDCueVP{{UD}* z4bj-3TMP!N`1&O>33SH5)@|&MsPr7y_CI*}%rPKlK+NY@7_45h{Pl*Tx1p6kP(mEb8BZyH>(g5GH_9Bw zu%^NU+EK6J!^BLd7?TQuOkph<WCW9Kb?&t8UeAG6p+8i(*Pj$abX zbl#(xS$qTd7&wTJ*U2EA06}Zv3meCIkz)*E!p+6xa)T0LSjaFIvxjrcV=0MjpcoI& zk&ekks!8SvMzWd_lrn-R$zTyXC?uOD*ezcM6RBhj%NWK(qyawi7>dbQt`Nm<853zU zGcCHgSe-D{jbJZpi|*AEQbb12`oA?n>lBWF=4ffiC~L(#42l?;j&BrcyBSs}XZO1I z*!rlun8Wx8-fi5Er`ITbOw%VJid7NZM1q$!5t%kt_R-^(#oWpX(ihXXMSj0vKulkV zux6U5U@}*!AcJyTjOATiWKu;2XBkZ;6R~lWcJ8DKA17($F3vHE(_G`L#BdxRHKcK# z%iK*Z!%5=rB=axoxRXm%lSLEpr1c>xB+s1Cd|lJq%2A0@HAzKEQl^SDU4PSDl_*y= zs@Hg>sZ{fIM0Y7!NAyj-r70Ssay97z73pCeP_Dcx(sy-0(xWQW5=Ci;@)V_QTB%pU z@=Szj>bR?PByK+eO#JvU30Ss{dq|+G9l(!h$Fyo#ER%Npn6z6bQD-}7m1fw;pRi^O ztNe7CC4AF%_xv;-K35BPK+J%c&qR#6N`;g?84CG<(#=qhPs3TE%06hFBbUaNs2tI|g2>?I8{aZXAFcCxc1GO+2^ZCJs;7$er7N zTJ*j*yfXo4riL+Gra59ogoTO972|5@ZZ@q;j4%?%*QBsOCDExH&@# zmq_9wP29m{5*b7#L%2Z`!)d0GR9cDdNV}}#r)A8fv0GG+Yv=zLH$4ve4+CPpXku(! zU^guQnEaJ8%YKDj)LZrUIBXsLZS3c)Wv%8-S^*FxUHzKa-PP|T2dxJx0g5?eSue7O zmI(2f=4sxj8FDLMEi$!DiMm}yk}m5pxwJral1epBiF!?@+VreqG)-qDozqNt^^~ql zDpZO_>(7!J^+UO}NFPZ$u1OlKO$sWYjf&S-^ns*W&6ivG`bpPr>JsBx>n zo0J%&kb}4DbuE*XPg|3`~WAjZRg`f z8CiH(@A10>Y#@eA-j0`1!#tw-5iOkPDP|O#nF>G@kMkUf0HpIWOR)h4^V!Te0ODB7 zYAc&hW-B=WI9Sea7zRKp1>{*r&1_yF3ji-aV-2wYjAtuz0C2K`U(oA6l%&87Zp#T5?8FWQhbVF}#ZDnqB07G(R zVRU6=Aa`kWXdp*PO;A^X4i^9b03~!qSaf7zbY(hYa%Ew3WdJfTF*PkPGc7SRR5CI; zH8DChGb=GMIxsLILPgX7001R)MObuXVRU6WZEs|0W_bWIFflbPFf%PNG*mJ$Ix;mn bG%zbMF*-0X?$PJj00000NkvXXu0mjfgfCmX literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/example/resources/escpos-php.png b/htdocs/includes/mike42/escpos-php/example/resources/escpos-php.png new file mode 100644 index 0000000000000000000000000000000000000000..feee2d71cc280655b1765f2689bbed8299ba394e GIT binary patch literal 7871 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 literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/example/specific/29-latvian-star-tup592.php b/htdocs/includes/mike42/escpos-php/example/specific/29-latvian-star-tup592.php new file mode 100644 index 00000000000..72dc7c8f2ed --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/specific/29-latvian-star-tup592.php @@ -0,0 +1,21 @@ + text("Glāžšķūņa rūķīši dzērumā čiepj Baha koncertflīģeļu vākus\n"); +$printer -> cut(); +$printer -> close(); + +/* Option 2: Image-based output (formatting not available using this output) */ +$buffer = new ImagePrintBuffer(); +$connector = new FilePrintConnector("php://stdout"); +$printer = new Escpos($connector, $profile); +$printer -> setPrintBuffer($buffer); +$printer -> text("Glāžšķūņa rūķīši dzērumā čiepj Baha koncertflīģeļu vākus\n"); +$printer -> cut(); +$printer -> close(); +?> \ No newline at end of file diff --git a/htdocs/includes/mike42/escpos-php/example/specific/32-german-tm-t20-ii-custom-command.php b/htdocs/includes/mike42/escpos-php/example/specific/32-german-tm-t20-ii-custom-command.php new file mode 100644 index 00000000000..49eca235091 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/specific/32-german-tm-t20-ii-custom-command.php @@ -0,0 +1,36 @@ + getPrintConnector() -> write($cmd); +$printer -> text("Beispieltext in Deutsch\n"); +$printer -> cut(); +$printer -> close(); + +/* + * Hex-dump of output confirms that ESC V 1 being sent: + * + * 0000000 033 @ 033 V 001 B e i s p i e l t e x + * 0000010 t i n D e u t s c h \n 035 V A + * 0000020 003 + */ \ No newline at end of file diff --git a/htdocs/includes/mike42/escpos-php/example/specific/33-spanish-seypos-prp-300.php b/htdocs/includes/mike42/escpos-php/example/specific/33-spanish-seypos-prp-300.php new file mode 100644 index 00000000000..7314ad21bf0 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/specific/33-spanish-seypos-prp-300.php @@ -0,0 +1,16 @@ + text("El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro.\n"); +$printer -> cut(); +$printer -> close(); + diff --git a/htdocs/includes/mike42/escpos-php/example/specific/39-currency-symbols.php b/htdocs/includes/mike42/escpos-php/example/specific/39-currency-symbols.php new file mode 100644 index 00000000000..b3d4b29edd1 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/specific/39-currency-symbols.php @@ -0,0 +1,69 @@ + text("€ 9,95\n"); +$printer -> text("£ 9.95\n"); +$printer -> text("$ 9.95\n"); +$printer -> text("¥ 9.95\n"); +$printer -> cut(); +$printer -> close(); + +/* Option 2: Image-based output (formatting not available using this output). */ +$buffer = new ImagePrintBuffer(); +$connector = new FilePrintConnector("php://stdout"); +$printer = new Escpos($connector, $profile); +$printer -> setPrintBuffer($buffer); +$printer -> text("€ 9,95\n"); +$printer -> text("£ 9.95\n"); +$printer -> text("$ 9.95\n"); +$printer -> text("¥ 9.95\n"); +$printer -> cut(); +$printer -> close(); + +/* + Option 3: If the printer is configured to print in a specific code + page, you can set up a CapabilityProfile which either references its + iconv encoding name, or includes all of the available characters. + + Here, we make use of CP858 for its inclusion of currency symbols which + are not available in CP437. CP858 has good printer support, but is not + included in all iconv builds. +*/ +class CustomCapabilityProfile extends SimpleCapabilityProfile { + function getCustomCodePages() { + /* + * Example to print in a specific, user-defined character set + * on a printer which has been configured to use i + */ + return array( + 'CP858' => "ÇüéâäàåçêëèïîìÄÅ" . + "ÉæÆôöòûùÿÖÜø£Ø×ƒ" . + "áíóúñѪº¿®¬½¼¡«»" . + "░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐" . + "└┴┬├─┼ãÃ╚╔╩╦╠═╬¤" . + "ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀" . + "ÓßÔÒõÕµþÞÚÛÙýݯ´" . + " ±‗¾¶§÷¸°¨·¹³²■ "); + } + + function getSupportedCodePages() { + return array( + 0 => 'custom:CP858'); + } +} + +$connector = new FilePrintConnector("php://stdout"); +$profile = CustomCapabilityProfile::getInstance(); +$printer = new Escpos($connector, $profile); +$printer -> text("€ 9,95\n"); +$printer -> text("£ 9.95\n"); +$printer -> text("$ 9.95\n"); +$printer -> text("¥ 9.95\n"); + +$printer -> cut(); +$printer -> close(); diff --git a/htdocs/includes/mike42/escpos-php/example/specific/44-pound-symbol-star-tsp650.php b/htdocs/includes/mike42/escpos-php/example/specific/44-pound-symbol-star-tsp650.php new file mode 100644 index 00000000000..4ef1a9575c2 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/specific/44-pound-symbol-star-tsp650.php @@ -0,0 +1,31 @@ + textRaw("\x9C"); // based on position in CP437 +$printer -> text(" 1.95\n"); + +// B) Manually encoded UTF8 pound symbol. Tests that the driver correctly +// encodes this as CP437. +$printer -> text(base64_decode("wqM=") . " 2.95\n"); + +// C) Pasted in file. Tests that your files are being saved as UTF-8, which +// escpos-php is able to convert automatically to a mix of code pages. +$printer -> text("£ 3.95\n"); + +$printer -> cut(); +$printer -> close(); diff --git a/htdocs/includes/mike42/escpos-php/example/specific/50-P-822D-greek.php b/htdocs/includes/mike42/escpos-php/example/specific/50-P-822D-greek.php new file mode 100644 index 00000000000..2d88cf1a2a8 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/specific/50-P-822D-greek.php @@ -0,0 +1,16 @@ + text($text . "\n"); +$printer -> cut(); + +// Close the connection +$printer -> close(); diff --git a/htdocs/includes/mike42/escpos-php/example/specific/54-gfx-sidebyside.php b/htdocs/includes/mike42/escpos-php/example/specific/54-gfx-sidebyside.php new file mode 100644 index 00000000000..316099cf304 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/specific/54-gfx-sidebyside.php @@ -0,0 +1,58 @@ + setJustification(Escpos::JUSTIFY_CENTER); + $printer -> graphics($img); + $printer -> cut(); + } finally { + // Always close the connection + $printer -> close(); + } +} catch (Exception $e) { + // Print out any errors: Eg. printer connection, image loading & external image manipulation. + echo $e -> getMessage() . "\n"; + echo $e -> getTraceAsString(); +} finally { + unlink ( $imgCombined_path ); + unlink ( $tmpf_path ); +} diff --git a/htdocs/includes/mike42/escpos-php/example/specific/6-arabic-epos-tep-220m.php b/htdocs/includes/mike42/escpos-php/example/specific/6-arabic-epos-tep-220m.php new file mode 100644 index 00000000000..c2b0fb5aa57 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/specific/6-arabic-epos-tep-220m.php @@ -0,0 +1,47 @@ + utf8Glyphs($text); + +/* + * Set up and use the printer + */ +$buffer = new ImagePrintBuffer(); +$profile = EposTepCapabilityProfile::getInstance(); +$connector = new FilePrintConnector("php://output"); + // = WindowsPrintConnector("LPT2"); + // Windows LPT2 was used in the bug tracker + +$printer = new Escpos($connector, $profile); +$printer -> setPrintBuffer($buffer); +$printer -> text($text . "\n"); +$printer -> close(); diff --git a/htdocs/includes/mike42/escpos-php/example/specific/README.md b/htdocs/includes/mike42/escpos-php/example/specific/README.md new file mode 100644 index 00000000000..7433e7aa247 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/specific/README.md @@ -0,0 +1,7 @@ +Specific examples +----------------- + +These examples are designed for specific combinations of language, +printer and interface. + +They are documented here because the general examples all set up the printer in a similar way. diff --git a/htdocs/includes/mike42/escpos-php/example/text-size.php b/htdocs/includes/mike42/escpos-php/example/text-size.php new file mode 100644 index 00000000000..2f9272e434b --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/example/text-size.php @@ -0,0 +1,62 @@ + + */ +require_once(dirname(__FILE__) . "/../Escpos.php"); +$printer = new Escpos(); + +/* Initialize */ +$printer -> initialize(); + +/* Text of various (in-proportion) sizes */ +title($printer, "Change height & width\n"); +for($i = 1; $i <= 8; $i++) { + $printer -> setTextSize($i, $i); + $printer -> text($i); +} +$printer -> text("\n"); + +/* Width changing only */ +title($printer, "Change width only (height=4):\n"); +for($i = 1; $i <= 8; $i++) { + $printer -> setTextSize($i, 4); + $printer -> text($i); +} +$printer -> text("\n"); + +/* Height changing only */ +title($printer, "Change height only (width=4):\n"); +for($i = 1; $i <= 8; $i++) { + $printer -> setTextSize(4, $i); + $printer -> text($i); +} +$printer -> text("\n"); + +/* Very narrow text */ +title($printer, "Very narrow text:\n"); +$printer -> setTextSize(1, 8); +$printer -> text("The quick brown fox jumps over the lazy dog.\n"); + +/* Very flat text */ +title($printer, "Very wide text:\n"); +$printer -> setTextSize(4, 1); +$printer -> text("Hello world!\n"); + +/* Very large text */ +title($printer, "Largest possible text:\n"); +$printer -> setTextSize(8,8); +$printer -> text("Hello\nworld!\n"); + +$printer -> cut(); +$printer -> close(); + +function title(Escpos $printer, $text) { + $printer -> selectPrintMode(Escpos::MODE_EMPHASIZED); + $printer -> text("\n" . $text); + $printer -> selectPrintMode(); // Reset +} + +?> diff --git a/htdocs/includes/mike42/escpos-php/src/AbstractCapabilityProfile.php b/htdocs/includes/mike42/escpos-php/src/AbstractCapabilityProfile.php new file mode 100644 index 00000000000..db2e8e01f58 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/AbstractCapabilityProfile.php @@ -0,0 +1,61 @@ + CodePage::CP437, + 1 => CodePage::CP932, + 2 => CodePage::CP850, + 3 => CodePage::CP860, + 4 => CodePage::CP863, + 5 => CodePage::CP865, + 6 => false, // Hiragana + 7 => false, // One-pass printing Kanji characters + 8 => false, // Page 8 [One-pass printing Kanji characters] + 11 => CodePage::CP851, + 12 => CodePage::CP853, + 13 => CodePage::CP857, + 14 => CodePage::CP737, + 15 => CodePage::ISO8859_7, + 16 => CodePage::CP1252, + 17 => CodePage::CP866, + 18 => CodePage::CP852, + 19 => CodePage::CP858, + 20 => false, // Thai Character Code 42 + 21 => CodePage::CP874, // Thai Character Code 11 + 22 => false, // Thai Character Code 13 + 23 => false, // Thai Character Code 14 + 24 => false, // Thai Character Code 16 + 25 => false, // Thai Character Code 17 + 26 => false, // Thai Character Code 18 + 30 => false, // TCVN-3: Vietnamese + 31 => false, // TCVN-3: Vietnamese + 32 => CodePage::CP720, + 33 => CodePage::CP775, + 34 => CodePage::CP855, + 35 => CodePage::CP861, + 36 => CodePage::CP862, + 37 => CodePage::CP864, + 38 => CodePage::CP869, + 39 => CodePage::ISO8859_2, + 40 => CodePage::ISO8859_15, + 41 => CodePage::CP1098, // PC1098: Farsi + 42 => CodePage::CP774, + 43 => CodePage::CP772, + 44 => CodePage::CP1125, + 45 => CodePage::CP1250, + 46 => CodePage::CP1251, + 47 => CodePage::CP1253, + 48 => CodePage::CP1254, + 49 => CodePage::CP1255, + 50 => CodePage::CP1256, + 51 => CodePage::CP1257, + 52 => CodePage::CP1258, + 53 => CodePage::RK1048, + 66 => false, // Devanagari + 67 => false, // Bengali + 68 => false, // Tamil + 69 => false, // Telugu + 70 => false, // Assamese + 71 => false, // Oriya + 72 => false, // Kannada + 73 => false, // Malayalam + 74 => false, // Gujarati + 75 => false, // Punjabi + 82 => false, // Marathi + 254 => false, + 255 => false); + } + + function getSupportsBarcodeB() { + return true; + } + + function getSupportsBitImage() { + return true; + } + + function getSupportsGraphics() { + return true; + } + + function getSupportsStarCommands() { + return false; + } + + function getSupportsQrCode() { + return true; + } +} diff --git a/htdocs/includes/mike42/escpos-php/src/DummyPrintConnector.php b/htdocs/includes/mike42/escpos-php/src/DummyPrintConnector.php new file mode 100644 index 00000000000..e1a197b7c7b --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/DummyPrintConnector.php @@ -0,0 +1,78 @@ +, + * incorporating modifications by: + * - Roni Saha + * - Gergely Radics + * - Warren Doyle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Print connector that writes to nowhere, but allows the user to retrieve the + * buffered data. Used for testing. + */ +final class DummyPrintConnector implements PrintConnector { + /** + * @var array Buffer of accumilated data. + */ + private $buffer; + + /** + * @var string data which the printer will provide on next read + */ + private $readData; + + /** + * Create new print connector + */ + public function __construct() { + $this -> buffer = array(); + } + + public function __destruct() { + if($this -> buffer !== null) { + trigger_error("Print connector was not finalized. Did you forget to close the printer?", E_USER_NOTICE); + } + } + + public function finalize() { + $this -> buffer = null; + } + + /** + * @return string Get the accumulated data that has been sent to this buffer. + */ + public function getData() { + return implode($this -> buffer); + } + + /* (non-PHPdoc) + * @see PrintConnector::read() + */ + public function read($len) { + return $len >= strlen($this -> readData) ? $this -> readData : substr($this -> readData, 0, $len); + } + + public function write($data) { + $this -> buffer[] = $data; + } +} diff --git a/htdocs/includes/mike42/escpos-php/src/EposTepCapabilityProfile.php b/htdocs/includes/mike42/escpos-php/src/EposTepCapabilityProfile.php new file mode 100644 index 00000000000..2803fe3f69c --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/EposTepCapabilityProfile.php @@ -0,0 +1,4 @@ +, + * incorporating modifications by: + * - Roni Saha + * - Gergely Radics + * - Warren Doyle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This class deals with images in raster formats, and converts them into formats + * which are suitable for use on thermal receipt printers. Currently, only PNG + * images (in) and ESC/POS raster format (out) are implemeted. + * + * Input formats: + * - Currently, only PNG is supported. + * - Other easily read raster formats (jpg, gif) will be added at a later date, as this is not complex. + * - The BMP format can be directly read by some commands, but this has not yet been implemented. + * + * Output formats: + * - Currently, only ESC/POS raster format is supported + * - ESC/POS 'column format' support is partially implemented, but is not yet used by Escpos.php library. + * - Output as multiple rows of column format image is not yet in the works. + * + * Libraries: + * - Currently, php-gd is used to read the input. Support for imagemagick where gd is not installed is + * also not complex to add, and is a likely future feature. + * - Support for native use of the BMP format is a goal, for maximum compatibility with target environments. + */ +class EscposImage { + /** + * @var string The image's bitmap data (if it is a Windows BMP). + */ + protected $imgBmpData; + + /** + * @var string image data in rows: 1 for black, 0 for white. + */ + protected $imgData; + + /** + * @var string cached raster format data to avoid re-computation + */ + protected $imgRasterData; + + /** + * @var int height of the image + */ + protected $imgHeight; + + /** + * @var int width of the image + */ + protected $imgWidth; + + /** + * Load up an image from a filename + * + * @param string $imgPath The path to the image to load, or null to skip + * loading the image (some other functions are available for + * populating the data). Supported graphics types depend on your PHP configuration. + */ + public function __construct($imgPath = null) { + /* Can't use bitmaps yet */ + $this -> imgBmpData = null; + $this -> imgRasterData = null; + if($imgPath === null) { + // Blank image + $this -> imgHeight = 0; + $this -> imgWidth = 0; + $this -> imgData = ""; + return; + } + + /* Load up using GD */ + if(!file_exists($imgPath)) { + throw new Exception("File '$imgPath' does not exist."); + } + $ext = pathinfo($imgPath, PATHINFO_EXTENSION); + if($ext == "bmp") { + // The plan is to implement BMP handling directly in + // PHP, as some printers understand this format themselves. + // TODO implement PHP bitmap handling + throw new Exception("Native bitmaps not yet supported. Please convert the file to a supported raster format."); + } + if($this -> isGdSupported()) { + // Prefer to use gd. It is installed by default, so + // most systems will have it, giving a consistent UX. + switch($ext) { + case "png": + $im = @imagecreatefrompng($imgPath); + $this -> readImageFromGdResource($im); + return; + case "jpg": + $im = @imagecreatefromjpeg($imgPath); + $this -> readImageFromGdResource($im); + return; + case "gif": + $im = @imagecreatefromgif($imgPath); + $this -> readImageFromGdResource($im); + return; + } + } + if($this -> isImagickSupported()) { + $im = new Imagick(); + try { + // Throws an ImagickException if the format is not supported or file is not found + $im -> readImage($imgPath); + } catch(ImagickException $e) { + // Wrap in normal exception, so that classes which call this do not themselves require imagick as a dependency. + throw new Exception($e); + } + /* Flatten by doing a composite over white, in case of transparency */ + $flat = new Imagick(); + $flat -> newImage($im -> getimagewidth(), $im -> getimageheight(), "white"); + $flat -> compositeimage($im, Imagick::COMPOSITE_OVER, 0, 0); + $this -> readImageFromImagick($flat); + return; + } + throw new Exception("Images are not supported on your PHP. Please install either the gd or imagick extension."); + } + + /** + * @return int height of the image in pixels + */ + public function getHeight() { + return $this -> imgHeight; + } + + /** + * @return int Number of bytes to represent a row of this image + */ + public function getHeightBytes() { + return (int)(($this -> imgHeight + 7) / 8); + } + + /** + * @return int Width of the image + */ + public function getWidth() { + return $this -> imgWidth; + } + + /** + * @return int Number of bytes to represent a row of this image + */ + public function getWidthBytes() { + return (int)(($this -> imgWidth + 7) / 8); + } + + /** + * @return string binary data of the original file, for function which accept bitmaps. + */ + public function getWindowsBMPData() { + return $this -> imgBmpData; + } + + /** + * @return boolean True if the image was a windows bitmap, false otherwise + */ + public function isWindowsBMP() { + return $this -> imgBmpData != null; + } + + /** + * Load actual image pixels from GD resource. + * + * @param resouce $im GD resource to use + * @throws Exception Where the image can't be read. + */ + public function readImageFromGdResource($im) { + if(!is_resource($im)) { + throw new Exception("Failed to load image."); + } else if(!$this -> isGdSupported()) { + throw new Exception(__FUNCTION__ . " requires 'gd' extension."); + } + /* Make a string of 1's and 0's */ + $this -> imgHeight = imagesy($im); + $this -> imgWidth = imagesx($im); + $this -> imgData = str_repeat("\0", $this -> imgHeight * $this -> imgWidth); + for($y = 0; $y < $this -> imgHeight; $y++) { + for($x = 0; $x < $this -> imgWidth; $x++) { + /* Faster to average channels, blend alpha and negate the image here than via filters (tested!) */ + $cols = imagecolorsforindex($im, imagecolorat($im, $x, $y)); + $greyness = (int)(($cols['red'] + $cols['green'] + $cols['blue']) / 3) >> 7; // 1 for white, 0 for black + $black = (1 - $greyness) >> ($cols['alpha'] >> 6); // 1 for black, 0 for white, taking into account transparency + $this -> imgData[$y * $this -> imgWidth + $x] = $black; + } + } + } + + /** + * Load actual image pixels from Imagick object + * + * @param Imagick $im Image to load from + */ + public function readImageFromImagick(Imagick $im) { + /* Threshold */ + $im -> setImageType(Imagick::IMGTYPE_TRUECOLOR); // Remove transparency (good for PDF's) + $max = $im->getQuantumRange(); + $max = $max["quantumRangeLong"]; + $im -> thresholdImage(0.5 * $max); + /* Make a string of 1's and 0's */ + $geometry = $im -> getimagegeometry(); + $this -> imgHeight = $im -> getimageheight(); + $this -> imgWidth = $im -> getimagewidth(); + $this -> imgData = str_repeat("\0", $this -> imgHeight * $this -> imgWidth); + + for($y = 0; $y < $this -> imgHeight; $y++) { + for($x = 0; $x < $this -> imgWidth; $x++) { + /* Faster to average channels, blend alpha and negate the image here than via filters (tested!) */ + $cols = $im -> getImagePixelColor($x, $y); + $cols = $cols -> getcolor(); + $greyness = (int)(($cols['r'] + $cols['g'] + $cols['b']) / 3) >> 7; // 1 for white, 0 for black + $this -> imgData[$y * $this -> imgWidth + $x] = (1 - $greyness); // 1 for black, 0 for white + } + } + + } + + /** + * Output the image in raster (row) format. This can result in padding on the right of the image, if its width is not divisible by 8. + * + * @throws Exception Where the generated data is unsuitable for the printer (indicates a bug or oversized image). + * @return string The image in raster format. + */ + public function toRasterFormat() { + if($this -> imgRasterData != null) { + /* Use previous calculation */ + return $this -> imgRasterData; + } + /* Loop through and convert format */ + $widthPixels = $this -> getWidth(); + $heightPixels = $this -> getHeight(); + $widthBytes = $this -> getWidthBytes(); + $heightBytes = $this -> getHeightBytes(); + $x = $y = $bit = $byte = $byteVal = 0; + $data = str_repeat("\0", $widthBytes * $heightPixels); + if(strlen($data) == 0) { + return $data; + } + do { + $byteVal |= (int)$this -> imgData[$y * $widthPixels + $x] << (7 - $bit); + $x++; + $bit++; + if($x >= $widthPixels) { + $x = 0; + $y++; + $bit = 8; + if($y >= $heightPixels) { + $data[$byte] = chr($byteVal); + break; + } + } + if($bit >= 8) { + $data[$byte] = chr($byteVal); + $byteVal = 0; + $bit = 0; + $byte++; + } + } while(true); + if(strlen($data) != ($this -> getWidthBytes() * $this -> getHeight())) { + throw new Exception("Bug in " . __FUNCTION__ . ", wrong number of bytes."); + } + $this -> imgRasterData = $data; + return $this -> imgRasterData; + } + + /** + * Output image in column format. This format results in padding at the base and right of the image, if its height and width are not divisible by 8. + */ + private function toColumnFormat() { + /* Note: This function is marked private, as it is not yet used/tested and may be buggy. */ + $widthPixels = $this -> getWidth(); + $heightPixels = $this -> getHeight(); + $widthBytes = $this -> getWidthBytes(); + $heightBytes = $this -> getHeightBytes(); + $x = $y = $bit = $byte = $byteVal = 0; + $data = str_repeat("\0", $widthBytes * $heightBytes * 8); + do { + $byteVal |= (int)$this -> imgData[$y * $widthPixels + $x] << (8 - $bit); + $y++; + $bit++; + if($y >= $heightPixels) { + $y = 0; + $x++; + $bit = 8; + if($x >= $widthPixels) { + $data[$byte] = chr($byteVal); + break; + } + } + if($bit >= 8) { + $data[$byte] = chr($byteVal); + $byteVal = 0; + $bit = 0; + $byte++; + } + } while(true); + if(strlen($data) != ($widthBytes * $heightBytes * 8)) { + throw new Exception("Bug in " . __FUNCTION__ . ", wrong number of bytes. Should be " . ($widthBytes * $heightBytes * 8) . " but was " . strlen($data)); + } + return $data; + } + + /** + * @return boolean True if GD is supported, false otherwise (a wrapper for the static version, for mocking in tests) + */ + protected function isGdSupported() { + return self::isGdLoaded(); + } + + /** + * @return boolean True if Imagick is supported, false otherwise (a wrapper for the static version, for mocking in tests) + */ + protected function isImagickSupported() { + return self::isImagickLoaded(); + } + + + /** + * @return boolean True if GD is loaded, false otherwise + */ + public static function isGdLoaded() { + return extension_loaded('gd'); + } + + /** + * @return boolean True if Imagick is loaded, false otherwise + */ + public static function isImagickLoaded() { + return extension_loaded('imagick'); + } + + /** + * Load a PDF for use on the printer + * + * @param string $pdfFile The file to load + * @param string $pageWidth The width, in pixels, of the printer's output. The first page of the PDF will be scaled to approximately fit in this area. + * @param array $range array indicating the first and last page (starting from 0) to load. If not set, the entire document is loaded. + * @throws Exception Where Imagick is not loaded, or where a missing file or invalid page number is requested. + * @return multitype:EscposImage Array of images, retrieved from the PDF file. + */ + public static function loadPdf($pdfFile, $pageWidth = 550, array $range = null) { + if(!extension_loaded('imagick')) { + throw new Exception(__FUNCTION__ . " requires imagick extension."); + } + /* + * Load first page at very low density (resolution), to figure out what + * density to use to achieve $pageWidth + */ + try { + $image = new Imagick(); + $testRes = 2; // Test resolution + $image -> setresolution($testRes, $testRes); + $image -> readimage($pdfFile."[0]"); + $geo = $image -> getimagegeometry(); + $image -> destroy(); + $width = $geo['width']; + $newRes = $pageWidth / $width * $testRes; + /* Load actual document (can be very slow!) */ + $rangeStr = ""; // Set to [0] [0-1] page range if $range is set + if($range != null) { + if(count($range) != 2 || !isset($range[0]) || !is_integer($range[0]) || !isset($range[1]) || !is_integer($range[1]) || $range[0] > $range[1]) { + throw new Exception("Invalid range. Must be two numbers in the array: The start and finish page indexes, starting from 0."); + } + $rangeStr = "[" . ($range[0] == $range[1] ? $range[0] : implode($range, "-")) . "]"; + } + $image -> setresolution($newRes, $newRes); + $image -> readImage($pdfFile."$rangeStr"); + $pages = $image -> getNumberImages(); + /* Convert images to Escpos objects */ + $ret = array(); + for($i = 0;$i < $pages; $i++) { + $image -> setIteratorIndex($i); + $ep = new EscposImage(); + $ep -> readImageFromImagick($image); + $ret[] = $ep; + } + return $ret; + } catch(ImagickException $e) { + // Wrap in normal exception, so that classes which call this do not themselves require imagick as a dependency. + throw new Exception($e); + } + } +} diff --git a/htdocs/includes/mike42/escpos-php/src/EscposPrintBuffer.php b/htdocs/includes/mike42/escpos-php/src/EscposPrintBuffer.php new file mode 100644 index 00000000000..703bc0a57c0 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/EscposPrintBuffer.php @@ -0,0 +1,304 @@ +, + * incorporating modifications by: + * - Roni Saha + * - Gergely Radics + * - Warren Doyle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This class manages newlines and character encoding for the target printer, and + * can be interchanged for an image-bassed buffer (ImagePrintBuffer) if you can't + * get it operating properly on your machine. + */ +class EscposPrintBuffer implements PrintBuffer { + /** + * @var boolean True to cache output as .gz, false to leave un-compressed (useful for debugging) + */ + const COMPRESS_CACHE = true; + + /** + * @var string The input encoding of the buffer. + */ + const INPUT_ENCODING = "UTF-8"; + + /** + * @var string Un-recorgnised characters will be replaced with this. + */ + const REPLACEMENT_CHAR = "?"; + + /** + * This array Maps ESC/POS character tables to names iconv encodings + */ + private $available = null; + + /** + * @var array Maps of UTF-8 to code-pages + */ + private $encode = null; + + /** + * @var Escpos Printer for output + */ + private $printer; + + /** + * Empty print buffer. + */ + function __construct() { + $this -> printer = null; + } + + public function flush() { + if($this -> printer == null) { + throw new LogicException("Not attached to a printer."); + } + // TODO Not yet implemented for this buffer: This indicates that the printer needs the current line to be ended. + } + + public function getPrinter() { + return $this -> printer; + } + + public function setPrinter(Escpos $printer = null) { + $this -> printer = $printer; + if($printer != null) { + $this -> loadAvailableCharacters(); + } + } + + public function writeText($text) { + if($this -> printer == null) { + throw new LogicException("Not attached to a printer."); + } + if($text == null) { + return; + } + if(!mb_detect_encoding($text, self::INPUT_ENCODING, true)) { + // Assume that the user has already put non-UTF8 into the target encoding. + return $this -> writeTextRaw($text); + } + $i = 0; + $j = 0; + $len = mb_strlen($text, self::INPUT_ENCODING); + while($i < $len) { + $matching = true; + if(($encoding = $this -> identifyText(mb_substr($text, $i, 1, self::INPUT_ENCODING))) === false) { + // Un-encodeable text + $encoding = $this -> getPrinter() -> getCharacterTable(); + } + $i++; + $j = 1; + do { + $char = mb_substr($text, $i, 1, self::INPUT_ENCODING); + $matching = !isset($this -> available[$char]) || isset($this -> available[$char][$encoding]); + if($matching) { + $i++; + $j++; + } + } while($matching && $i < $len); + $this -> writeTextUsingEncoding(mb_substr($text, $i - $j, $j, self::INPUT_ENCODING), $encoding); + } + } + + public function writeTextRaw($text) { + if($this -> printer == null) { + throw new LogicException("Not attached to a printer."); + } + if(strlen($text) == 0) { + return; + } + // Pass only printable characters + for($i = 0; $i < strlen($text); $i++) { + $c = substr($text, $i, 1); + if(!self::asciiCheck($c, true)) { + $text[$i] = self::REPLACEMENT_CHAR; + } + } + $this -> write($text); + } + + /** + * Return an encoding which we can start to use for outputting this text. Later parts of the text need not be included in the returned code page. + * + * @param string $text Input text to check. + * @return boolean|integer Code page number, or FALSE if the text is not printable on any supported encoding. + */ + private function identifyText($text) { + // TODO Replace this with an algorithm to choose the encoding which will encode the farthest into the string, to minimise code page changes. + $char = mb_substr($text, 0, 1, self::INPUT_ENCODING); + if(!isset($this -> available[$char])) { + /* Character not available anywhere */ + return false; + } + foreach($this -> available[$char] as $encodingNo => $true) { + /* Return first code-page where it is available */ + return $encodingNo; + } + return false; + } + + /** + * Based on the printer's connector, compute (or load a cached copy of) maps of UTF character to unicode characters for later use. + */ + private function loadAvailableCharacters() { + $supportedCodePages = $this -> printer -> getPrinterCapabilityProfile() -> getSupportedCodePages(); + $capabilityClassName = get_class($this -> printer -> getPrinterCapabilityProfile()); + $cacheFile = dirname(__FILE__) . "/cache/Characters-" . $capabilityClassName . ".ser" . (self::COMPRESS_CACHE ? ".gz" : ""); + $cacheKey = md5(serialize($supportedCodePages)); + /* Check for pre-generated file */ + if(file_exists($cacheFile)) { + $cacheData = file_get_contents($cacheFile); + if(self::COMPRESS_CACHE) { + $cacheData = gzdecode($cacheData); + } + if($cacheData) { + $dataArray = unserialize($cacheData); + if(isset($dataArray["key"]) && isset($dataArray["available"]) && isset($dataArray["encode"]) && $dataArray["key"] == $cacheKey) { + $this -> available = $dataArray["available"]; + $this -> encode = $dataArray["encode"]; + return; + } + } + } + /* Generate conversion tables */ + $encode = array(); + $available = array(); + $custom = $this -> printer -> getPrinterCapabilityProfile() -> getCustomCodePages(); + + foreach($supportedCodePages as $num => $characterMap) { + $encode[$num] = array(); + if($characterMap === false) { + continue; + } else if(strpos($characterMap, ":") !== false) { + /* Load a pre-defined custom map (vendor-specific code pages) */ + $i = strpos($characterMap, ":"); + if(substr($characterMap, 0, $i) !== "custom") { + continue; + } + $i++; + $mapName = substr($characterMap, $i, strlen($characterMap) - $i); + if(!isset($custom[$mapName]) || mb_strlen($custom[$mapName], self::INPUT_ENCODING) != 128) { + throw new Exception("Capability profile referenced invalid custom map '$mapName'."); + } + $map = $custom[$mapName]; + for($char = 128; $char <= 255; $char++) { + $utf8 = mb_substr($map, $char - 128, 1, self::INPUT_ENCODING); + if($utf8 == " ") { // Skip placeholders + continue; + } + if(!isset($available[$utf8])) { + $available[$utf8] = array(); + } + $available[$utf8][$num] = true; + $encode[$num][$utf8] = chr($char); + } + } else { + /* Generate map using iconv */ + for($char = 128; $char <= 255; $char++) { + $utf8 = @iconv($characterMap, self::INPUT_ENCODING, chr($char)); + if($utf8 == '') { + continue; + } + if(iconv(self::INPUT_ENCODING, $characterMap, $utf8) != chr($char)) { + // Avoid non-canonical conversions + continue; + } + if(!isset($available[$utf8])) { + $available[$utf8] = array(); + } + $available[$utf8][$num] = true; + $encode[$num][$utf8] = chr($char); + } + } + } + /* Use generated data */ + $dataArray = array("available" => $available, "encode" => $encode, "key" => $cacheKey); + $this -> available = $dataArray["available"]; + $this -> encode = $dataArray["encode"]; + $cacheData = serialize($dataArray); + if(self::COMPRESS_CACHE) { + $cacheData = gzencode($cacheData); + } + /* Attempt to cache, but don't worry if we can't */ + @file_put_contents($cacheFile, $cacheData); + } + + /** + * Encode a block of text using the specified map, and write it to the printer. + * + * @param string $text Text to print, UTF-8 format. + * @param integer $encodingNo Encoding number to use- assumed to exist. + */ + private function writeTextUsingEncoding($text, $encodingNo) { + $encodeMap = $this -> encode[$encodingNo]; + $len = mb_strlen($text, self::INPUT_ENCODING); + $rawText = str_repeat(self::REPLACEMENT_CHAR, $len); + for($i = 0; $i < $len; $i++) { + $char = mb_substr($text, $i, 1, self::INPUT_ENCODING); + if(isset($encodeMap[$char])) { + $rawText[$i] = $encodeMap[$char]; + } else if(self::asciiCheck($char)) { + $rawText[$i] = $char; + } + } + if($this -> printer -> getCharacterTable() != $encodingNo) { + $this -> printer -> selectCharacterTable($encodingNo); + } + $this -> writeTextRaw($rawText); + } + + /** + * Write data to the underlying printer. + * + * @param string $data + */ + private function write($data) { + $this -> printer -> getPrintConnector() -> write($data); + } + + /** + * Return true if a character is an ASCII printable character. + * + * @param string $char Character to check + * @param boolean $extended True to allow 128-256 values also (excluded by default) + * @return boolean True if the character is printable, false if it is not. + */ + private static function asciiCheck($char, $extended = false) { + if(strlen($char) != 1) { + // Multi-byte string + return false; + } + $num = ord($char); + if($num > 31 && $num < 127) { // Printable + return true; + } + if($num == 10) { // New-line (printer will take these) + return true; + } + if($extended && $num > 127) { + return true; + } + return false; + } +} diff --git a/htdocs/includes/mike42/escpos-php/src/FilePrintConnector.php b/htdocs/includes/mike42/escpos-php/src/FilePrintConnector.php new file mode 100644 index 00000000000..8d87f0a527a --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/FilePrintConnector.php @@ -0,0 +1,80 @@ +, + * incorporating modifications by: + * - Roni Saha + * - Gergely Radics + * - Warren Doyle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * PrintConnector for passing print data to a file. + */ +class FilePrintConnector implements PrintConnector { + /** + * @var resource The file pointer to send data to. + */ + protected $fp; + + /** + * Construct new connector, given a filename + * + * @param string $filename + */ + public function __construct($filename) { + $this -> fp = fopen($filename, "wb+"); + if($this -> fp === false) { + throw new Exception("Cannot initialise FilePrintConnector."); + } + } + + public function __destruct() { + if($this -> fp !== false) { + trigger_error("Print connector was not finalized. Did you forget to close the printer?", E_USER_NOTICE); + } + } + + /** + * Close file pointer + */ + public function finalize() { + fclose($this -> fp); + $this -> fp = false; + } + + /* (non-PHPdoc) + * @see PrintConnector::read() + */ + public function read($len) { + rewind($this -> fp); + return fgets($this -> fp, $len + 1); + } + + /** + * Write data to the file + * + * @param string $data + */ + public function write($data) { + fwrite($this -> fp, $data); + } +} diff --git a/htdocs/includes/mike42/escpos-php/src/ImagePrintBuffer.php b/htdocs/includes/mike42/escpos-php/src/ImagePrintBuffer.php new file mode 100644 index 00000000000..08ca64ac43e --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/ImagePrintBuffer.php @@ -0,0 +1,99 @@ +, + * incorporating modifications by: + * - Roni Saha + * - Gergely Radics + * - Warren Doyle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This class renders text to small images on-the-fly. It attempts to mimic the + * behaviour of text output, whilst supporting any fonts & character encodings + * which your system can handle. This class currently requires Imagick. + */ +class ImagePrintBuffer implements PrintBuffer { + private $printer; + + function __construct() { + if(!EscposImage::isImagickLoaded()) { + throw new Exception("ImagePrintBuffer requires the imagick extension"); + } + } + + function flush() { + if($this -> printer == null) { + throw new LogicException("Not attached to a printer."); + } + } + + function getPrinter() { + return $this -> printer; + } + + function setPrinter(Escpos $printer = null) { + $this -> printer = $printer; + } + + function writeText($text) { + if($this -> printer == null) { + throw new LogicException("Not attached to a printer."); + } + if($text == null) { + return; + } + $text = trim($text, "\n"); + /* Create Imagick objects */ + $image = new Imagick(); + $draw = new ImagickDraw(); + $color = new ImagickPixel('#000000'); + $background = new ImagickPixel('white'); + + /* Create annotation */ + //$draw -> setFont('Arial');// (not necessary?) + $draw -> setFontSize(24); // Size 21 looks good for FONT B + $draw -> setFillColor($color); + $draw -> setStrokeAntialias(true); + $draw -> setTextAntialias(true); + $metrics = $image -> queryFontMetrics($draw, $text); + $draw -> annotation(0, $metrics['ascender'], $text); + + /* Create image & draw annotation on it */ + $image -> newImage($metrics['textWidth'], $metrics['textHeight'], $background); + $image -> setImageFormat('png'); + $image -> drawImage($draw); + //$image -> writeImage("test.png"); + + /* Save image */ + $escposImage = new EscposImage(); + $escposImage -> readImageFromImagick($image); + $size = Escpos::IMG_DEFAULT; + $this -> printer -> bitImage($escposImage, $size); + } + + function writeTextRaw($text) { + if($this -> printer == null) { + throw new LogicException("Not attached to a printer."); + } + $this -> printer -> getPrintConnector() -> write($data); + } +} diff --git a/htdocs/includes/mike42/escpos-php/src/NetworkPrintConnector.php b/htdocs/includes/mike42/escpos-php/src/NetworkPrintConnector.php new file mode 100644 index 00000000000..4dd2c39f115 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/NetworkPrintConnector.php @@ -0,0 +1,39 @@ +, + * incorporating modifications by: + * - Roni Saha + * - Gergely Radics + * - Warren Doyle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * PrintConnector for directly opening a network socket to a printer to send it commands. + */ +class NetworkPrintConnector extends FilePrintConnector { + public function __construct($ip, $port = "9100") { + $this -> fp = @fsockopen($ip, $port, $errno, $errstr); + if($this -> fp === false) { + throw new Exception("Cannot initialise NetworkPrintConnector: " . $errstr); + } + } +} diff --git a/htdocs/includes/mike42/escpos-php/src/P822DCapabilityProfile.php b/htdocs/includes/mike42/escpos-php/src/P822DCapabilityProfile.php new file mode 100644 index 00000000000..7c3b5abe6dd --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/P822DCapabilityProfile.php @@ -0,0 +1,90 @@ + CodePage::CP437, + 1 => false, // Katakana + 2 => CodePage::CP850, + 3 => CodePage::CP860, + 4 => CodePage::CP863, + 5 => CodePage::CP865, + 6 => false, // Western Europe + 7 => false, // Greek + 8 => false, // Hebrew + 9 => false, // Eastern europe + 10 => false, // Iran + 16 => CodePage::CP1252 , + 17 => CodePage::CP866 , + 18 => CodePage::CP852 , + 19 => CodePage::CP858, + 20 => false, // Iran II + 21 => false, // latvian + 22 => false, //Arabic + 23 => false, // PT151, 1251 + 24 => CodePage::CP747, + 25 => CodePage::CP1257, + 27 => false, // Vietnam, + 28 => CodePage::CP864, + 29 => CodePage::CP1001, + 30 => false, // Uigur + 31 => false, // Hebrew + 32 => CodePage::CP1255, + 33 => CodePage::CP720, + 34 => CodePage::CP1256, + 35 => CodePage::CP1257, + 255 => false, // Thai + + 50 => CodePage::CP437, + 51 => false, // Jatakana, + 52 => CodePage::CP437, + 53 => CodePage::CP858, + 54 => CodePage::CP852, + 55 => CodePage::CP860, + 56 => CodePage::CP861, + 57 => CodePage::CP863, + 58 => CodePage::CP865, + 59 => CodePage::CP866, + 60 => CodePage::CP855, + 61 => CodePage::CP857, + 62 => CodePage::CP862, + 63 => CodePage::CP864, + 64 => CodePage::CP737, + 65 => CodePage::CP851, + 66 => CodePage::CP869, + 67 => CodePage::CP928, + 68 => CodePage::CP772, + 69 => CodePage::CP774, + 70 => CodePage::CP874, + 71 => CodePage::CP1252, + 72 => CodePage::CP1250, + 73 => CodePage::CP1251, + 74 => CodePage::CP3840, + 75 => CodePage::CP3841, + 76 => CodePage::CP3843, + 77 => CodePage::CP3844, + 78 => CodePage::CP3845, + 79 => CodePage::CP3846, + 80 => CodePage::CP3847, + 81 => CodePage::CP3848, + 82 => CodePage::CP1001, + 83 => CodePage::CP2001, + 84 => CodePage::CP3001, + 85 => CodePage::CP3002, + 86 => CodePage::CP3011, + 87 => CodePage::CP3012, + 88 => CodePage::CP3021, + 89 => CodePage::CP3041 + ); + } + + public function getSupportsGraphics() { + /* Ask the driver to use bitImage wherever possible instead of graphics */ + return false; + } +} diff --git a/htdocs/includes/mike42/escpos-php/src/PrintBuffer.php b/htdocs/includes/mike42/escpos-php/src/PrintBuffer.php new file mode 100644 index 00000000000..9e3b110085f --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/PrintBuffer.php @@ -0,0 +1,75 @@ +, + * incorporating modifications by: + * - Roni Saha + * - Gergely Radics + * - Warren Doyle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Print buffers manage newlines and character encoding for the target printer. + * They are used as a swappable component: text or image-based output. + * + * - Text output (EscposPrintBuffer) is the fast default, and is recommended for + * most people, as the text output can be more directly manipulated by ESC/POS + * commands. + * - Image output (ImagePrintBuffer) is designed to accept more encodings than the + * physical printer supports, by rendering the text to small images on-the-fly. + * This takes a lot more CPU than sending text, but is necessary for some users. + * - If your use case fits outside these, then a further speed/flexibility trade-off + * can be made by printing directly from generated HTML or PDF. + */ +interface PrintBuffer { + /** + * Cause the buffer to send any partial input and wait on a newline. + * If the printer is already on a new line, this does nothing. + */ + function flush(); + + /** + * Used by Escpos to check if a printer is set. + */ + function getPrinter(); + + /** + * Used by Escpos to hook up one-to-one link between buffers and printers. + * + * @param Escpos $printer New printer + */ + function setPrinter(Escpos $printer = null); + + /** + * Accept UTF-8 text for printing. + * + * @param string $text Text to print + */ + function writeText($text); + + /** + * Accept 8-bit text in the current encoding and add it to the buffer. + * + * @param string $text Text to print, already the target encoding. + */ + function writeTextRaw($text); +} +?> \ No newline at end of file diff --git a/htdocs/includes/mike42/escpos-php/src/PrintConnector.php b/htdocs/includes/mike42/escpos-php/src/PrintConnector.php new file mode 100644 index 00000000000..f1d37be9c55 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/PrintConnector.php @@ -0,0 +1,56 @@ +, + * incorporating modifications by: + * - Roni Saha + * - Gergely Radics + * - Warren Doyle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Interface passed to Escpos class for receiving print data. Print connectors + * are responsible for transporting this to the actual printer. + */ +interface PrintConnector { + /** + * Print connectors should cause a NOTICE if they are deconstructed + * when they have not been finalized. + */ + public function __destruct(); + + /** + * Finish using this print connector (close file, socket, send + * accumulated output, etc). + */ + public function finalize(); + + /** + * @param string $data + * @return Data read from the printer, or false where reading is not possible. + */ + public function read($len); + + /** + * @param string $data + */ + public function write($data); +} diff --git a/htdocs/includes/mike42/escpos-php/src/SimpleCapabilityProfile.php b/htdocs/includes/mike42/escpos-php/src/SimpleCapabilityProfile.php new file mode 100644 index 00000000000..7076b5e16cb --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/SimpleCapabilityProfile.php @@ -0,0 +1,17 @@ + CodePage::CP437); + } + + public function getSupportsGraphics() { + /* Ask the driver to use bitImage wherever possible instead of graphics */ + return false; + } +} diff --git a/htdocs/includes/mike42/escpos-php/src/StarCapabilityProfile.php b/htdocs/includes/mike42/escpos-php/src/StarCapabilityProfile.php new file mode 100644 index 00000000000..f61774a5e30 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/StarCapabilityProfile.php @@ -0,0 +1,82 @@ + "ÇüéâäàåçêëèïîìÄÅ" . + "ÉæÆôöòûùÿÖÜ¢£¥₧ƒ" . + "áíóúñѪº¿⌐¬½¼¡«»" . + "░▒▓│┤Ā╢ņ╕╣║╗╝╜╛┐" . + "└┴┬├─┼ā╟╚╔╩╦╠═╬╧" . + "Š╤čČ╘╒ģĪī┘┌█▄ūŪ▀" . + "αßΓπΣσµτΦΘΩδ∞φε∩" . + "ĒēĢķĶļĻžŽ∙·√Ņš■ ", + 'CP3012' => "АБВГДЕЖЗИЙКЛМНОП" . + "РСТУФХЦЧШЩЪЫЬЭЮЯ" . + "абвгдежзийклмноп" . + "░▒▓│┤Ā╢ņ╕╣║╗╝Ō╛┐" . + "└┴┬├─┼ā╟╚╔╩╦╠═╬╧" . + "Š╤čČ╘╒ģĪī┘┌█▄ūŪ▀" . + "рстуфхцчшщъыьэюя" . + "ĒēĢķĶļĻžŽ∙·√Ņš■ " + ); + } + + function getSupportedCodePages() { + return array( + 0 => CodePage::CP437, // "Normal" + 1 => CodePage::CP437, + 2 => CodePage::CP932, + 3 => CodePage::CP437, + 4 => CodePage::CP858, + 5 => CodePage::CP852, + 6 => CodePage::CP860, + 7 => CodePage::CP861, + 8 => CodePage::CP863, + 9 => CodePage::CP865, + 10 => CodePage::CP866, + 11 => CodePage::CP855, + 12 => CodePage::CP857, + 13 => CodePage::CP862, + 14 => CodePage::CP864, + 15 => CodePage::CP737, + 16 => CodePage::CP851, + 17 => CodePage::CP869, + 18 => CodePage::CP928, + 19 => CodePage::CP772, + 20 => CodePage::CP774, + 21 => CodePage::CP874, + 32 => CodePage::CP1252, + 33 => CodePage::CP1250, + 34 => CodePage::CP1251, + 64 => CodePage::CP3840, + 65 => CodePage::CP3841, + 66 => CodePage::CP3843, + 67 => CodePage::CP3844, + 68 => CodePage::CP3845, + 69 => CodePage::CP3846, + 70 => CodePage::CP3847, + 71 => CodePage::CP3848, + 72 => CodePage::CP1001, + 73 => CodePage::CP2001, + 74 => CodePage::CP3001, + 75 => CodePage::CP3002, + 76 => 'custom:CP3011', + 77 => 'custom:CP3012', + 78 => CodePage::CP3021, + 79 => CodePage::CP3041, + 96 => false, // Thai Character Code 42 + 97 => false, // Thai Character Code 11 + 98 => false, // Thai Character Code 13 + 99 => false, // Thai Character Code 14 + 100 => false, // Thai Character Code 16 + 101 => false, // Thai Character Code 17 + 102 => false, // Thai Character Code 18 + 255 => false); + } + + function getSupportsStarCommands() { + /* Allows Escpos.php to substitute emulated ESC/POS commands with native ones for this printer. */ + return true; + } +} \ No newline at end of file diff --git a/htdocs/includes/mike42/escpos-php/src/WindowsPrintConnector.php b/htdocs/includes/mike42/escpos-php/src/WindowsPrintConnector.php new file mode 100644 index 00000000000..717e9c1a572 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/src/WindowsPrintConnector.php @@ -0,0 +1,356 @@ +, + * incorporating modifications by: + * - Roni Saha + * - Gergely Radics + * - Warren Doyle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Connector for sending print jobs to + * - local ports on windows (COM1, LPT1, etc) + * - shared (SMB) printers from any platform (\\server\foo) + * For USB printers or other ports, the trick is to share the printer with a generic text driver, then access it locally. + */ +class WindowsPrintConnector implements PrintConnector { + /** + * @var array Accumulated lines of output for later use. + */ + private $buffer; + + /** + * @var string The hostname of the target machine, or null if this is a local connection. + */ + private $hostname; + + /** + * @var boolean True if a port is being used directly (must be Windows), false if network shares will be used. + */ + private $isLocal; + + /** + * @var int Platform we're running on, for selecting different commands. See PLATFORM_* constants. + */ + private $platform; + + /** + * @var string The name of the target printer (eg "Foo Printer") or port ("COM1", "LPT1"). + */ + private $printerName; + + /** + * @var string Login name for network printer, or null if not using authentication. + */ + private $userName; + + /** + * @var string Password for network printer, or null if no password is required. + */ + private $userPassword; + + /** + * @var string Workgroup that the printer is located on + */ + private $workgroup; + + /** + * @var int represents Linux + */ + const PLATFORM_LINUX = 0; + + /** + * @var int represents Mac + */ + const PLATFORM_MAC = 1; + + /** + * @var int represents Windows + */ + const PLATFORM_WIN = 2; + + /** + * @var string Valid local ports. + */ + const REGEX_LOCAL = "/^(LPT\d|COM\d)$/"; + + /** + * @var string Valid printer name. + */ + const REGEX_PRINTERNAME = "/^[\w-]+(\s[\w-]+)*$/"; + + /** + * @var string Valid smb:// URI containing hostname & printer with optional user & optional password only. + */ + const REGEX_SMB = "/^smb:\/\/([\s\w-]+(:[\s\w-]+)?@)?[\w-]+\/([\w-]+\/)?[\w-]+(\s[\w-]+)*$/"; + + /** + * @param string $dest + * @throws BadMethodCallException + */ + public function __construct($dest) { + $this -> platform = $this -> getCurrentPlatform(); + $this -> isLocal = false; + $this -> buffer = null; + $this -> userName = null; + $this -> userPassword = null; + $this -> workgroup = null; + if(preg_match(self::REGEX_LOCAL, $dest) == 1) { + // Straight to LPT1, COM1 or other local port. Allowed only if we are actually on windows. + if($this -> platform !== self::PLATFORM_WIN) { + throw new BadMethodCallException("WindowsPrintConnector can only be used to print to a local printer ('".$dest."') on a Windows computer."); + } + $this -> isLocal = true; + $this -> hostname = null; + $this -> printerName = $dest; + } else if(preg_match(self::REGEX_SMB, $dest) == 1) { + // Connect to samba share, eg smb://host/printer + $part = parse_url($dest); + $this -> hostname = $part['host']; + /* Printer name and optional workgroup */ + $path = ltrim($part['path'], '/'); + if(strpos($path, "/") !== false) { + $pathPart = explode("/", $path); + $this -> workgroup = $pathPart[0]; + $this -> printerName = $pathPart[1]; + } else { + $this -> printerName = $path; + } + /* Username and password if set */ + if(isset($part['user'])) { + $this -> userName = $part['user']; + if(isset($part['pass'])) { + $this -> userPassword = $part['pass']; + } + } + } else if(preg_match(self::REGEX_PRINTERNAME, $dest) == 1) { + // Just got a printer name. Assume it's on the current computer. + $hostname = gethostname(); + if(!$hostname) { + $hostname = "localhost"; + } + $this -> hostname = $hostname; + $this -> printerName = $dest; + } else { + throw new BadMethodCallException("Printer '" . $dest . "' is not a valid printer name. Use local port (LPT1, COM1, etc) or smb://computer/printer notation."); + } + $this -> buffer = array(); + } + + public function __destruct() { + if($this -> buffer !== null) { + trigger_error("Print connector was not finalized. Did you forget to close the printer?", E_USER_NOTICE); + } + } + + public function finalize() { + $data = implode($this -> buffer); + $this -> buffer = null; + if($this -> platform == self::PLATFORM_WIN) { + $this -> finalizeWin($data); + } else if($this -> platform == self::PLATFORM_LINUX) { + $this -> finalizeLinux($data); + } else { + $this -> finalizeMac($data); + } + } + + /** + * Send job to printer -- platform-specific Linux code. + * + * @param string $data Print data + * @throws Exception + */ + protected function finalizeLinux($data) { + /* Non-Windows samba printing */ + $device = "//" . $this -> hostname . "/" . $this -> printerName; + if($this -> userName !== null) { + $user = ($this -> workgroup != null ? ($this -> workgroup . "\\") : "") . $this -> userName; + if($this -> userPassword == null) { + // No password + $command = sprintf("smbclient %s -U %s -c %s -N", + escapeshellarg($device), + escapeshellarg($user), + escapeshellarg("print -")); + $redactedCommand = $command; + } else { + // With password + $command = sprintf("smbclient %s %s -U %s -c %s", + escapeshellarg($device), + escapeshellarg($this -> userPassword), + escapeshellarg($user), + escapeshellarg("print -")); + $redactedCommand = sprintf("smbclient %s %s -U %s -c %s", + escapeshellarg($device), + escapeshellarg("*****"), + escapeshellarg($user), + escapeshellarg("print -")); + } + } else { + // No authentication information at all + $command = sprintf("smbclient %s -c %s -N", + escapeshellarg($device), + escapeshellarg("print -")); + $redactedCommand = $command; + } + $retval = $this -> runCommand($command, $outputStr, $errorStr, $data); + if($retval != 0) { + throw new Exception("Failed to print. Command \"$redactedCommand\" failed with exit code $retval: " . trim($outputStr)); + } + } + + protected function finalizeMac($data) { + throw new Exception("Mac printing not implemented."); + } + + /** + * Send data to printer -- platform-specific Windows code. + * + * @param string $data + */ + protected function finalizeWin($data) { + /* Windows-friendly printing of all sorts */ + if(!$this -> isLocal) { + /* Networked printing */ + $device = "\\\\" . $this -> hostname . "\\" . $this -> printerName; + if($this -> userName !== null) { + /* Log in */ + $user = "/user:" . ($this -> workgroup != null ? ($this -> workgroup . "\\") : "") . $this -> userName; + if($this -> userPassword == null) { + $command = sprintf("net use %s %s", + escapeshellarg($device), + escapeshellarg($user)); + $redactedCommand = $command; + } else { + $command = sprintf("net use %s %s %s", + escapeshellarg($device), + escapeshellarg($user), + escapeshellarg($this -> userPassword)); + $redactedCommand = sprintf("net use %s %s %s", + escapeshellarg($device), + escapeshellarg($user), + escapeshellarg("*****")); + } + $retval = $this -> runCommand($command, $outputStr, $errorStr); + if($retval != 0) { + throw new Exception("Failed to print. Command \"$redactedCommand\" failed with exit code $retval: " . trim($errorStr)); + } + } + /* Final print-out */ + $filename = tempnam(sys_get_temp_dir(), "escpos"); + file_put_contents($filename, $data); + if(!$this -> runCopy($filename, $device)){ + throw new Exception("Failed to copy file to printer"); + } + unlink($filename); + } else { + /* Drop data straight on the printer */ + if(!$this -> runWrite($data, $this -> printerName)) { + throw new Exception("Failed to write file to printer at " . $this -> printerName); + } + } + } + + /** + * @return string Current platform. Separated out for testing purposes. + */ + protected function getCurrentPlatform() { + if(PHP_OS == "WINNT") { + return self::PLATFORM_WIN; + } + if(PHP_OS == "Darwin") { + return self::PLATFORM_MAC; + } + return self::PLATFORM_LINUX; + } + + /* (non-PHPdoc) + * @see PrintConnector::read() + */ + public function read($len) { + /* Two-way communication is not supported */ + return false; + } + + /** + * Run a command, pass it data, and retrieve its return value, standard output, and standard error. + * + * @param string $command the command to run. + * @param string $outputStr variable to fill with standard output. + * @param string $errorStr variable to fill with standard error. + * @param string $inputStr text to pass to the command's standard input (optional). + * @return number + */ + protected function runCommand($command, &$outputStr, &$errorStr, $inputStr = null) { + $descriptors = array( + 0 => array("pipe", "r"), + 1 => array("pipe", "w"), + 2 => array("pipe", "w"), + ); + $process = proc_open($command, $descriptors, $fd); + if (is_resource($process)) { + /* Write to input */ + if($inputStr !== null) { + fwrite($fd[0], $inputStr); + } + fclose($fd[0]); + /* Read stdout */ + $outputStr = stream_get_contents($fd[1]); + fclose($fd[1]); + /* Read stderr */ + $errorStr = stream_get_contents($fd[2]); + fclose($fd[2]); + /* Finish up */ + $retval = proc_close($process); + return $retval; + } else { + /* Method calling this should notice a non-zero exit and print an error */ + return -1; + } + } + + /** + * Copy a file. Separated out so that nothing is actually printed during test runs. + * + * @param string $from Source file + * @param string $to Destination file + * @return boolean True if copy was successful, false otherwise + */ + protected function runCopy($from, $to) { + return copy($from, $to); + } + + /** + * Write data to a file. Separated out so that nothing is actually printed during test runs. + * + * @param string $data Data to print + * @param string $to Destination file + * @return boolean True if write was successful, false otherwise + */ + protected function runWrite($data, $to) { + return file_put_contents($data, $to) !== false; + } + + public function write($data) { + $this -> buffer[] = $data; + } +} diff --git a/htdocs/includes/mike42/escpos-php/src/cache/Characters-DefaultCapabilityProfile.ser.gz b/htdocs/includes/mike42/escpos-php/src/cache/Characters-DefaultCapabilityProfile.ser.gz new file mode 100644 index 0000000000000000000000000000000000000000..43ad39d229582af5ad7121088bf2c6287e067e11 GIT binary patch literal 12928 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

AN>wkF3^lWB>v&YPaMdeJ# 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 literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/src/cache/Characters-SimpleCapabilityProfile.ser.gz b/htdocs/includes/mike42/escpos-php/src/cache/Characters-SimpleCapabilityProfile.ser.gz new file mode 100644 index 0000000000000000000000000000000000000000..288b7d3740782fbcc9d9a28d71a6ecc330683eb4 GIT binary patch literal 1116 zcmV-i1f%;OiwFP!0000019jGCa}-Aq2H^j4zu=@ppj-W#6S@x+l>LD!S5XvI%G|-( zDBPZM&N)j+LdafhIRGb;P0kqyYz!D2xgGlLa@lWxQt7$b*`As1-br{d@8Etf?nM$G zCHCw~lRM>N;gO`CvM?<}uDCNAj@HmhwhZCfOE_CYw{>ZDVf! zrP{9H4RVQ(<`;%MzXIgRu7}KTVtJ#+Q=|sNQ26~KPXc$d7?qYbz8mBu+;Ah|(?)@0 zjDpP=pY|1FdXkS}l(WWSN%@7tjoDL1*Q(+dr+{3_TvGGPaqvGb_)ag9{9yNo70G}7 zmiW)zet+SINM-m}ZuJhT0Am8xfPR4*z@R`apjV&{FeFe97!f!O7#BzZCIr%eQGp|X z8G)mKX@Lx2Qs84it-vRMI)P(=v_J!3SfCM5FVF;76lex43uFQF0zP0}AO~0#XaQ^r zv;uMhZGcXJc7Xur(g7F{=mg*xy28J>Cp+nuAzMuXJ$Ox@KrdiQpbxMh&<|J>7y!%( z3<4Sjh7cK!e;9x{MgZ85QN*5U5E;V|9QQb2Ltp}cI&S!5oPtrl5OLD?)qd_=RPf-_xKK_0BA_#($uaPX@th1r_p{3XUpJ!MjeWREm7Ag5&!_BEvnp6uUIi7lt%62&P6e58UImqUL8VRPqDrgCB^Bi1w<^fP?^KZB zmsL=ES9G5-^Hr6M$TiLIl*n}zG}jv{cYw-2>5zioy zpY$0_`y7%+U-%i(V~>8(AxM}PDoCndAsHm_OBGD~3VXdG@LH$AIsb-t&kDTJfRUPS zF$A;xu0v3Re?Y3x&;P_j_(=YxseyR^#t^_i5qG&M4BDGK;D#4|jh0lzkjbXRFnB3hb^MN8s^XWjvL>zN7b6aWBu>k}&g literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/src/cache/Characters-StarCapabilityProfile.ser.gz b/htdocs/includes/mike42/escpos-php/src/cache/Characters-StarCapabilityProfile.ser.gz new file mode 100644 index 0000000000000000000000000000000000000000..e0bb0e5fec5c59a4131ae1f2f4151d392f435b91 GIT binary patch literal 9416 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 $c) { + $code = ord($c); + if($code < 32 || $code > 126) { + $chars[$i] = "\\x" . bin2hex($c); + } + } + return implode($chars); +} diff --git a/htdocs/includes/mike42/escpos-php/test/integration/ExampleTest.php b/htdocs/includes/mike42/escpos-php/test/integration/ExampleTest.php new file mode 100644 index 00000000000..689891e2432 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/test/integration/ExampleTest.php @@ -0,0 +1,126 @@ + exampleDir = dirname(__FILE__) . "/../../example/"; + } + + public function testBitImage() { + $this -> requireGraphicsLibrary(); + $outp = $this -> runExample("bit-image.php"); + $this -> outpTest($outp, "bit-image.bin"); + } + + public function testCharacterEncodings() { + $outp = $this -> runExample("character-encodings.php"); + $this -> outpTest($outp, "character-encodings.bin"); + } + + public function testCharacterTables() { + $outp = $this -> runExample("character-tables.php"); + $this -> outpTest($outp, "character-tables.bin"); + } + + private function outpTest($outp, $fn) { + $file = dirname(__FILE__) . "/resources/output/".$fn; + if(!file_exists($file)) { + file_put_contents($file, $outp); + } + $this -> assertEquals($outp, file_get_contents($file)); + } + + public function testDemo() { + $this -> requireGraphicsLibrary(); + $outp = $this -> runExample("demo.php"); + $this -> outpTest($outp, "demo.bin"); + } + + public function testGraphics() { + $this -> requireGraphicsLibrary(); + $outp = $this -> runExample("graphics.php"); + $this -> outpTest($outp, "graphics.bin"); + } + + public function testReceiptWithLogo() { + $this -> requireGraphicsLibrary(); + $outp = $this -> runExample("receipt-with-logo.php"); + $this -> outpTest($outp, "receipt-with-logo.bin"); + } + + public function testQrCode() { + $outp = $this -> runExample("qr-code.php"); + $this -> outpTest($outp, "qr-code.bin"); + } + + public function testBarcode() { + $outp = $this -> runExample("barcode.php"); + $this -> outpTest($outp, "barcode.bin"); + } + + public function testTextSize() { + $outp = $this -> runExample("text-size.php"); + $this -> outpTest($outp, "text-size.bin"); + } + + /** + * @large + */ + public function testPrintFromPdf() { + if(!EscposImage::isImagickLoaded()) { + $this -> markTestSkipped("imagick plugin required for this test"); + } + $outp = $this -> runExample("print-from-pdf.php"); + $this -> outpTest(gzcompress($outp, 9), "print-from-pdf.bin.z"); // Compressing output because it's ~1MB + } + + public function testInterfaceEthernet() { + // Test attempts DNS lookup on some machine + $outp = $this -> runExample("interface/ethernet.php"); + $this -> outpTest($outp, "interface.bin"); + } + + public function testInterfaceLinuxUSB() { + $outp = $this -> runExample("interface/linux-usb.php"); + $this -> outpTest($outp, "interface.bin"); + } + + public function testInterfaceWindowsUSB() { + // Output varies between platforms, not checking. + $outp = $this -> runExample("interface/windows-usb.php"); + $this -> outpTest($outp, "interface.bin"); + } + + public function testInterfaceSMB() { + // Output varies between platforms, not checking. + $outp = $this -> runExample("interface/smb.php"); + $this -> outpTest($outp, "interface.bin"); + } + + public function testInterfaceWindowsLPT() { + // Output varies between platforms, not checking. + $outp = $this -> runExample("interface/windows-lpt.php"); + $this -> outpTest($outp, "interface.bin"); + } + + private function runExample($fn) { + // Change directory and check script + chdir($this -> exampleDir); + $this -> assertTrue(file_exists($fn), "Script $fn not found."); + // Run command and save output + ob_start(); + passthru("php " . escapeshellarg($fn), $retval); + $outp = ob_get_contents(); + ob_end_clean(); + // Check return value + $this -> assertEquals(0, $retval, "Example $fn exited with status $retval"); + return $outp; + } + + protected function requireGraphicsLibrary() { + if(!EscposImage::isGdLoaded() && !EscposImage::isImagickLoaded()) { + $this -> markTestSkipped("This test requires a graphics library."); + } + } +} diff --git a/htdocs/includes/mike42/escpos-php/test/integration/resources/output/barcode.bin b/htdocs/includes/mike42/escpos-php/test/integration/resources/output/barcode.bin new file mode 100644 index 0000000000000000000000000000000000000000..79a2daf803edde452d5e0e47aa7e2545bb108962 GIT binary patch literal 2342 zcmbVOOK;Oa5N<#~apB0Rsx*}VrKNc|DSdDO$4wy|BBZnx*Nwd-t2$oGyJ=eG&og6h z5^s}~7F9~u-kope`{uFU{3uKkWF_~=6 z1i%v|qvqI-U>&ea$oDE>xBk=Xy3Jafe%B`J;d*>LHHzR~kC9YO zOJ8`fzSoj#5cbU$lQ;>}L>W ziHYl6oUfw0V9IZJZ1{lulTSZ&;Gi2lk_bmSU4phK zfEDE7-@i`Dr$Y?$tRCnfMO8 zp5N(!P{3}Oh%z}5y+pNbc|lOp6NCIgeVMHdy4%P`!BgO=`9N`vx{Q;ArvXzOK7zU% z^<`vt!<6_?KGAjO_2LPGoTId0z+;|bduCrA#rt~NGUg`w&RhFU5ypqv9cr4|0lLlU myC4{*<%Ub&1!=k2$p0TU_biE~{#W=!QNEb^?`U=9J^u^w*`<2` literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/integration/resources/output/bit-image.bin b/htdocs/includes/mike42/escpos-php/test/integration/resources/output/bit-image.bin new file mode 100644 index 0000000000000000000000000000000000000000..462eb1a0bf32bbe4bd68b8e9dc8304bc6559549b GIT binary patch literal 9789 zcmeI2F>l*O6o4N~Km-iK^i~;FL=GL2p@BvZhRz)ebP9|X-K1KqB~%s#T524iFxjbN zmdwphF^m3y{)0q7hm2K`Au8;Meed0)cvM`p2s#yahROTx9q-<~dptee>7(P5VRR9N z(PjU9I*!88dH*cB2>VGCPLt7O77fD3quDT=4Wlp~529q}>(NZt(iOtf`24&-89WTX zis#|QFrJSGVLTZ>3FjBl&d1a6lWvHQd`9iwe-Mto33b_7(w`1Trx#c@h_S~>JllC3 zC*R_;;gfitNcYLZot=B%bzmQ!!M{wZs8w`|A~y^CqF9<76veH{X;G|AE-)F4%@svf z6)@)&yCgT*BVJVUZANu!3jGrYRDV^q_g-uan4hon52iuX(*kPjtsyn)cimf11|K=^ z@P1H}uZ~PUoUXnP41*soS63!CSF5$jt!=(}T9@BCb-sjJ`|T;Tl+j;s3|?V!22f)( zwOPN792oUmTYLcEn(~7T_Dnv?p;_Y|^pw$Fx9Q}u;c#lthhI(pdFA?s177u@<(a$> z9nWyU^)BQ!Zn&9eIOH1V%dOrBBpg7+IJ*E&<#58A3H8VoonkWppI!4E-V7k%cdvSb z6mz;!I2B%Cvy{>WUDZZmol&KNPRT2(Mw;hz3rY_y`C1efOZhKFVX>tDyC~?tq{qQ~ zBOF|poN-2Jc5lSu>WYiqw=#fKvs3bw?$=q%0M?>M$!|sPwH~)qaw`0%ELExEitdfD z+GagK@AZQ90ky@!bMDxClWV!KUojiReKDy_2zOSRW%$tRrX>|`~zUU!CCqU&>(9&$6hIJ*4?W<6r* zkBq{zoQqj5|&yb^2Rw+y?1WjC-? z`9_o*R`gYpQpwlCb^}Y>#cp7|AVb&w__dkCx&QG7)=7UnzB{~fZFr^E zMH^n(^Fez)XwL@~f5WQI3w8Wx&j)3dwC95uEKs;$&j;=Kpu7|Cc|SSR+gGCr{;VrW ZqSG1v3@e_-$!vr_%c`9j-uvQH_b;K8L%;w4 literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/integration/resources/output/character-encodings.bin b/htdocs/includes/mike42/escpos-php/test/integration/resources/output/character-encodings.bin new file mode 100644 index 0000000000000000000000000000000000000000..0a762c8c160e39da6be9fc7c587b76a53e73c74e GIT binary patch literal 1780 zcmZuyTWl0n7zTZ-F;NqrHYQ&a6ELYh_(Xg`2<0M;K_p^)J3DiB=X7W0tmn*ZFHiIa zR0;;u2!aW1xoo-1r9iotise>dmtH}zh0+Txkg&LkVxs@9`xS6bN@--@RpuT>_RtePo=rc;uJs_ z5d@ViaS@WGDE6JGhKvz?%7H{GFM}y_(Y}nnkf_i`RJh>^;Yuv$uEDj>!*n6Et%W3X*MCK$>PQjM|zY@1Xp$iSd|BEyw1B@AX*QYo3uaXzJN z&m`@Y-0Npem*<2SOszxC% z4KpPstw9EBQL)ByFW|lNOs#=e5vf<9t*R+p8O;$fICaO@hHne6)}&BlC$Ijs$PCoU-ww=1cT}wtLqjp z%WIYIR3RaFv>0=l#SoEHbmfKYx*9(2BUVKRAd9s`J~!AB!*Z{$LJ7PiRyUd3$;jKt z0e%p2qXQjL>BRywtp24{b=X(=*hYntBIo5Rztyhj8ZpVQWN4t~PPvY?98>dg*&ZCAi zYq?Yr4dIgSWLR-M)rFRk9We8AIs+C{%*dJ6kS3XzJR@utwD}Z)cUzO^E+yhS&L$2W zn83i*`01{riN=c)rxK%aoa`SPI5?Wz-7^|*>+VeM>OMViVx)C@Yg6*v){etC(%;x} zqrIc~#Qv`1iI%R$WP5Ys_?fZJ_=V(o{E}=vpJ;4NUZL;X`VL**+ZErLXldWIFNr4V_g}cU#PTDC>AD_a?EDGbmigY!AtM6^IjRe0{2|>kZ;>Bi$1v}hdGIydrYCd zGBta0NrvnkYBR#X%%#hU=q!8GZlY1C=todMU4aCqW(J|P&-IkNA9ts3<7#T~PKxfs z*V6bSbuWE+FpZAEuGGC7X`D}A$Awffbt`oV8}KmI**`&ZJ*g`XZ~lo3>H8fRN?jT3 zXiMQ<`et88BAwx}kOvhn`ClHkpq~xRk{MrR@_tn%)^FJOJ$_n^irTeUx1wgv$_muf j)o=O%>wa8;x*Dvi*jSAfwcnw7LtX9a>b&>9n)CX9MQ`KH literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/integration/resources/output/character-tables.bin b/htdocs/includes/mike42/escpos-php/test/integration/resources/output/character-tables.bin new file mode 100644 index 0000000000000000000000000000000000000000..f3fb990cea3c56a9631360b2d12cb0e5b64254c6 GIT binary patch literal 8174 zcmdT}Rdf?k5N#=TaJL*_3)C%}WF1NkjpY#0?C(h z45ESa7c5+~c*)Xb%U7&iwR+9kb?Y~5+_ZVi)@|E&?A*0`&)$7B&_Vhzhj=gwcace>RFz*0_>*o&=LuZ-FAn_8 zyq&8A!x+ev&DZTp0><9q6%(p+E(QF#JA7%tkPgq6r!s&cU6l`07BHlf@nOmVhIha9fTyV6?T6 zf^0nT0sapA!}aF*0)Nf^L~@ZqRsVh=e6D|S4On}V27_b0_`zt|hz;J&iumXmASyhK&6z-)(UBGZpkRgEKURs0#hI>I2 l1{m%IQ8-|@7erAEqjKxXWEf&yyxX3ve?UC!%Etz|`~{yY7?A(~ literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/integration/resources/output/demo.bin b/htdocs/includes/mike42/escpos-php/test/integration/resources/output/demo.bin new file mode 100644 index 0000000000000000000000000000000000000000..6b426d91ff0563a2ec43c93217f26a492e8d9396 GIT binary patch literal 73643 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 M5AW>X{`!mPPivkA#sB~S literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/integration/resources/output/interface.bin b/htdocs/includes/mike42/escpos-php/test/integration/resources/output/interface.bin new file mode 100644 index 00000000000..729d4f4481b --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/test/integration/resources/output/interface.bin @@ -0,0 +1,2 @@ +@Hello World! +VA \ No newline at end of file diff --git a/htdocs/includes/mike42/escpos-php/test/integration/resources/output/print-from-pdf.bin.z b/htdocs/includes/mike42/escpos-php/test/integration/resources/output/print-from-pdf.bin.z new file mode 100644 index 0000000000000000000000000000000000000000..f89eb5d2115e388de90586ff30d80a47fc0ad083 GIT binary patch literal 17081 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 baI$Cav;BIBA!wfEw|@W6|LqUE=-dSWqat^g literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/integration/resources/output/qr-code.bin b/htdocs/includes/mike42/escpos-php/test/integration/resources/output/qr-code.bin new file mode 100644 index 0000000000000000000000000000000000000000..a1e687f151c8f8db633da3fa63fcc0d67fbb6671 GIT binary patch literal 1551 zcmbW1%}>HG6u_t8We4M(m^^M6J#6Dcy@^WVK@6zzUbhOBwVhq3gTLN4zC;SLLcwb^4Fs%YZ^axwI=)>$o;5TW;ISE+xyoDb3D~gXf{;_MB%LYbFNZdi7bT z?{sCXA!a-ZDbSQ;r)^ir9>$GXPlPWaXW&zz6%7z(p`3F|Gyy757!7vWo@TZVYBrj! z^NY*vx5ckKB%CVd+x9m1DVeteJ5hw$8y10bHnYCKyln$-3me8OwupM3J#v(WyfXo0TM?&e18G=`?b5 z4$KhE97Hy8CJALw;# k<|_&F8YT^J0>~w5QL=Ip$*1cQ(OqxX~7|PHI^$;+biUCaG-b< zbkNsYg1nc0z4RhHU7dT3|(d)C_d?ac6hZ+6UB)o-dT=fRgd zpFgbk>W0|726xg0-U0->UoL0HJX{(VYKP7ZxnIhPHVA``3=CP%Cj z)(PW)x!Rogn@(h!TjJOMkctJMfLO3m27V5VHXZk-fGi$t`eOX{#ni!!#C|Pk>xBS` z`hakY$!44yirQVwNsRVY#6gS$aOrxBBEZa0ZkBtZ@UUQr4q}q~iKG(5G@xC(jF2bJ zy=0SWmd0-8Cz(eSp*Tu|iz#Z8C8U6fV3@LNMP*DBZW;^<)0b>dK-K;*^{!=<-P9Ag zn+3#_BdT7ABKafXUCZ59QO*qI72ppfbtSwM6QTt5^=AsvL!l^kCEQfHG9)USrCiJ= zRE&2}fcb>-aX7y(2#N{i;pIF^&Q?=u_7LCPK|wrrLVzcf6Em}?zJQD+NmuWnJnIe$ZtAnUuvO3s6 zW`qyyf~HMMi4s&ve+DMMx*chEjGd7H?6XwD8}cEb5r-e6X4 z0!BL8Oh74l(p1V*rzfh`l%GN=0dx$f#ZoZRRFFZb6apikPAW=iDy9PttqfwLAO|B& z$s;?hoQ|7H#dO+NN>TKmGCHzTDjY0DM}1>ys=(ot+yY5Xf$xk88FtL$t0|50m^94e zOCwEX!S7Rgp9Q3=Ecjrgsno}X(v)CylCwaXN<3UBO$}VUQw}w03LIQ0MNzdt=c*Op zmxbC;tpK0R(-#2KlJ47L9k333yaV*Ln0rufR#*A?xaAL znZFHB`9av{CltG$bgC6i$F}kvwMG5bzHI7*r~Q-1oWkBBgWhycx)E>dJgB`6k1Evf uZg{-kddr_i9UdO=tzN%-$eZ=XIv)hMO>9TUc%2ib6th}+ZV#S^)@eM5#$Umje%7B$&R5Dh^tV~#$w#q`7 z2J74y?a_(7i}%morNHl9wfe**k7``&J#KbiTZ{Tf7<4>V`Jur=mv`Y)0pk1OKhu8^ fN@?`ksDeWFnwwHshg)rJwNxjy4|Ni2SUh!)7zJM* literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/phpunit.xml b/htdocs/includes/mike42/escpos-php/test/phpunit.xml new file mode 100644 index 00000000000..f9a6579cf45 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/test/phpunit.xml @@ -0,0 +1,16 @@ + + + + unit + + + integration + + + \ No newline at end of file diff --git a/htdocs/includes/mike42/escpos-php/test/unit/AbstractCapabilityProfileTest.php b/htdocs/includes/mike42/escpos-php/test/unit/AbstractCapabilityProfileTest.php new file mode 100644 index 00000000000..b3e72452a9f --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/test/unit/AbstractCapabilityProfileTest.php @@ -0,0 +1,69 @@ + profiles = array('DefaultCapabilityProfile', 'EposTepCapabilityProfile', 'SimpleCapabilityProfile', 'StarCapabilityProfile', 'P822DCapabilityProfile'); + $this -> checklist = array(); + foreach($this -> profiles as $profile) { + $this-> checklist[] = $profile::getInstance(); + } + } + + function testSupportedCodePages() { + foreach($this -> checklist as $obj) { + $check = $obj -> getSupportedCodePages(); + $this -> assertTrue(is_array($check) && isset($check[0]) && $check[0] == 'CP437'); + $custom = $obj -> getCustomCodePages(); + foreach($check as $num => $page) { + $this -> assertTrue(is_numeric($num) && ($page === false || is_string($page))); + if($page === false || strpos($page, ":") === false) { + continue; + } + $part = explode(":", $page); + if(!array_shift($part) == "custom") { + continue; + } + $this -> assertTrue(isset($custom[implode(":", $part)])); + } + } + } + + function testCustomCodePages() { + foreach($this -> checklist as $obj) { + $check = $obj -> getCustomCodePages(); + $this -> assertTrue(is_array($check)); + foreach($check as $name => $customMap) { + $this -> assertTrue(is_string($name)); + $this -> assertTrue(is_string($customMap) && mb_strlen($customMap, 'UTF-8') == 128); + } + } + } + + function testSupportsBitImage() { + foreach($this -> checklist as $obj) { + $check = $obj -> getSupportsBitImage(); + $this -> assertTrue(is_bool($check)); + } + } + + function testSupportsGraphics() { + foreach($this -> checklist as $obj) { + $check = $obj -> getSupportsGraphics(); + $this -> assertTrue(is_bool($check)); + } + } + + function testSupportsQrCode() { + foreach($this -> checklist as $obj) { + $check = $obj -> getSupportsQrCode(); + $this -> assertTrue(is_bool($check)); + } + } +} +?> \ No newline at end of file diff --git a/htdocs/includes/mike42/escpos-php/test/unit/EscposImageTest.php b/htdocs/includes/mike42/escpos-php/test/unit/EscposImageTest.php new file mode 100644 index 00000000000..f152f400a2f --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/test/unit/EscposImageTest.php @@ -0,0 +1,235 @@ + loadAndCheckImg(null, false, false, 0, 0, ""); + } + + /** + * BMP handling not yet implemented, but these will use + * a native PHP bitmap reader. + * This just tests that they are not being passed on to another library. + */ + public function testBmpBadFilename() { + $this -> setExpectedException('Exception'); + $this -> loadAndCheckImg('not a real file.bmp', false, false, 1, 1, "\x80"); + } + + public function testBmpBlack() { + $this -> setExpectedException('Exception'); + $this -> loadAndCheckImg("canvas_black.bmp", false, false, 0, 0, "\x80"); + } + + public function testBmpBlackWhite() { + $this -> setExpectedException('Exception'); + $this -> loadAndCheckImg("black_white.bmp", false, false, 0, 0, "\xc0\x00"); + } + + public function testBmpWhite() { + $this -> setExpectedException('Exception'); + $this -> loadAndCheckImg("canvas_white.bmp", false, false, 0, 0, "\x00"); + } + + /** + * GD tests - Load tiny images and check how they are printed. + * These are skipped if you don't have gd. + */ + public function testGdBadFilename() { + $this -> setExpectedException('Exception'); + $this -> loadAndCheckImg('not a real file.png', true, false, 1, 1, "\x80"); + } + + public function testGdBlack() { + foreach(array('png', 'jpg', 'gif') as $format) { + $this -> loadAndCheckImg('canvas_black.' . $format, true, false, 1, 1, "\x80"); + } + } + + public function testGdBlackTransparent() { + foreach(array('png', 'gif') as $format) { + $this -> loadAndCheckImg('black_transparent.' . $format, true, false, 2, 2, "\xc0\x00"); + } + } + + public function testGdBlackWhite() { + foreach(array('png', 'jpg', 'gif') as $format) { + $this -> loadAndCheckImg('black_white.' . $format, true, false, 2, 2, "\xc0\x00"); + } + } + + public function testGdWhite() { + foreach(array('png', 'jpg', 'gif') as $format) { + $this -> loadAndCheckImg('canvas_white.' . $format, true, false, 1, 1, "\x00"); + } + } + + /** + * Imagick tests - Load tiny images and check how they are printed + * These are skipped if you don't have imagick + */ + public function testImagickBadFilename() { + $this -> setExpectedException('Exception'); + $this -> loadAndCheckImg('not a real file.png', false, true, 1, 1, "\x80"); + } + + public function testImagickBlack() { + foreach(array('png', 'jpg', 'gif') as $format) { + $this -> loadAndCheckImg('canvas_black.' . $format, false, true, 1, 1, "\x80"); + } + } + + public function testImagickBlackTransparent() { + foreach(array('png', 'gif') as $format) { + $this -> loadAndCheckImg('black_transparent.' . $format, false, true, 2, 2, "\xc0\x00"); + } + } + + public function testImagickBlackWhite() { + foreach(array('png', 'jpg', 'gif') as $format) { + $this -> loadAndCheckImg('black_white.' . $format, false, true, 2, 2, "\xc0\x00"); + } + } + + public function testImagickWhite() { + foreach(array('png', 'jpg', 'gif') as $format) { + $this -> loadAndCheckImg('canvas_white.' . $format, false, true, 1, 1, "\x00"); + } + } + + /** + * Mixed test - Same as above, but confirms that each tiny image can be loaded + * under any supported library configuration with the same results. + * These are skipped if you don't have gd AND imagick + */ + public function testLibraryDifferences() { + if(!EscposImage::isGdLoaded() || !EscposImage::isImagickLoaded()) { + $this -> markTestSkipped("both gd and imagick plugin are required for this test"); + } + $inFile = array('black_white.png', 'canvas_black.png', 'canvas_white.png'); + foreach($inFile as $fn) { + // Default check + $im = new EscposImage(dirname(__FILE__) . "/resources/$fn"); + $width = $im -> getWidth(); + $height = $im -> getHeight(); + $data = $im -> toRasterFormat(); + // Gd check + $this -> loadAndCheckImg($fn, true, false, $width, $height, $data); + // Imagick check + $this -> loadAndCheckImg($fn, false, true, $width, $height, $data); + } + } + + /** + * PDF tests - load tiny PDF and check for well-formedness + * These are also skipped if you don't have imagick + * @medium + */ + public function testPdfAllPages() { + $this -> loadAndCheckPdf('doc.pdf', null, 1, 1, array("\x00", "\x80")); + } + + public function testPdfBadFilename() { + $this -> setExpectedException('Exception'); + $this -> loadAndCheckPdf('not a real file', null, 1, 1, array()); + } + + /** + * @medium + */ + public function testPdfBadRange() { + // Start page is after end page. + $this -> setExpectedException('Exception'); + $this -> loadAndCheckPdf('doc.pdf', array(1, 0), 1, 1, array("\x00", "\x80")); + } + + /** + * @medium + */ + public function testPdfFirstPage() { + $this -> loadAndCheckPdf('doc.pdf', array(0, 0), 1, 1, array("\x00")); + } + + /** + * @medium + */ + public function testPdfMorePages() { + $this -> loadAndCheckPdf('doc.pdf', array(1, 20), 1, 1, array("\x80")); + } + + /** + * @medium + */ + public function testPdfSecondPage() { + $this -> loadAndCheckPdf('doc.pdf', array(1, 1), 1, 1, array("\x80")); + } + + /** + * @medium + */ + public function testPdfStartPastEndOfDoc() { + // Doc only has pages 0 and 1, can't start reading from 2. + $this -> markTestIncomplete("Test needs revising- produces output due to apparent imagick bug."); + $this -> setExpectedException('ImagickException'); + $this -> loadAndCheckPdf('doc.pdf', array(2, 3), 1, 1, array()); + } + + /** + * Load an EscposImage with (optionally) certain libraries disabled and run a check. + */ + private function loadAndCheckImg($fn, $gd, $imagick, $width, $height, $rasterFormat = null) { + $img = $this -> getMockImage($fn === null ? null : dirname(__FILE__) . "/resources/$fn", $gd, $imagick); + $this -> checkImg($img, $width, $height, $rasterFormat); + } + + /** + * Same as above, loading document and checking pages against some expected values. + */ + private function loadAndCheckPdf($fn, array $range = null, $width, $height, array $rasterFormat = null) { + if(!EscposImage::isImagickLoaded()) { + $this -> markTestSkipped("imagick plugin required for this test"); + } + $pdfPages = EscposImage::loadPdf(dirname(__FILE__) . "/resources/$fn", $width, $range); + $this -> assertTrue(count($pdfPages) == count($rasterFormat), "Got back wrong number of pages"); + foreach($pdfPages as $id => $img) { + $this -> checkImg($img, $width, $height, $rasterFormat[$id]); + } + } + + /** + * Check image against known width, height, output. + */ + private function checkImg(EscposImage $img, $width, $height, $rasterFormat = null) { + if($rasterFormat === null) { + echo "\nImage was: " . $img -> getWidth() . "x" . $img -> getHeight() . ", data \"" . friendlyBinary($img -> toRasterFormat()) . "\""; + } + $this -> assertTrue($img -> getHeight() == $height); + $this -> assertTrue($img -> getWidth() == $width); + $this -> assertTrue($img -> toRasterFormat() == $rasterFormat); + } + + /** + * Load up an EsposImage with given libraries disabled or enabled. Marks the test + * as skipped if you ask for a library which is not loaded. + */ + private function getMockImage($path, $gd, $imagick) { + /* Sanity checks */ + if($gd && !EscposImage::isGdLoaded()) { + $this -> markTestSkipped("gd plugin required for this test"); + } + if($imagick && !EscposImage::isImagickLoaded()) { + $this -> markTestSkipped("imagick plugin required for this test"); + } + $stub = $this -> getMockBuilder('EscposImage') + -> setMethods(array('isGdSupported', 'isImagickSupported')) + -> disableOriginalConstructor() + -> getMock(); + $stub -> method('isGdSupported') + -> willReturn($gd); + $stub -> method('isImagickSupported') + -> willReturn($imagick); + $stub -> __construct($path); + return $stub; + } +} \ No newline at end of file diff --git a/htdocs/includes/mike42/escpos-php/test/unit/EscposPrintBufferTest.php b/htdocs/includes/mike42/escpos-php/test/unit/EscposPrintBufferTest.php new file mode 100644 index 00000000000..2bbdb34c20c --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/test/unit/EscposPrintBufferTest.php @@ -0,0 +1,150 @@ + outputConnector = new DummyPrintConnector(); + $printer = new Escpos($this -> outputConnector); + $this -> buffer = $printer -> getPrintBuffer(); + } + + protected function checkOutput($expected = null) { + /* Check those output strings */ + $outp = $this -> outputConnector -> getData(); + if($expected === null) { + echo "\nOutput was:\n\"" . friendlyBinary($outp) . "\"\n"; + } + $this -> assertEquals($expected, $outp); + } + + protected function tearDown() { + $this -> outputConnector -> finalize(); + } + + public function testRawTextNonprintable() { + $this -> buffer -> writeTextRaw("Test" . Escpos::ESC . "v1\n"); + $this -> checkOutput("\x1b@Test?v1\x0a"); // ASCII ESC character is substituted for '?' + } + + public function testDanish() { + $this -> buffer -> writeText("Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther spillede på xylofon.\n"); + $this -> checkOutput("\x1b@Quizdeltagerne spiste jordb\x91r med fl\x1bt\x02\x9bde, mens cirkusklovnen Wolther spillede p\x86 xylofon.\x0a"); + } + + public function testGerman() { + $this -> buffer -> writeText("Falsches Üben von Xylophonmusik quält jeden größeren Zwerg.\n"); + $this -> checkOutput("\x1b@Falsches \x9aben von Xylophonmusik qu\x84lt jeden gr\x94\xe1eren Zwerg.\x0a"); + } + + public function testGreek() { + $this -> buffer -> writeText("Ξεσκεπάζω την ψυχοφθόρα βδελυγμία"); + $this -> checkOutput("\x1b@\x1bt\x0b\xbd\xde\xec\xe4\xde\xea\x9b\xe0\xfa \xee\xe1\xe7 \xf6\xf2\xf4\xe9\xf3\xe2\xa2\xeb\xd6 \xd7\xdd\xde\xe5\xf2\xd8\xe6\x9f\xd6"); + } + + public function testGreekWithDiacritics() { + // This is a string which is known to be un-printable in ESC/POS (the grave-accented letters are not in any code page), + // so we are checking the substitution '?' for unknown characters. + $this -> buffer -> writeText("Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο.\n"); + $this -> checkOutput("\x1b@\xe2\xe0\x1bt\x0b\xe0\x9d\xde\xed \xe4\xd6? \xe6\xf2\xeb\xee\xe3?\xed \xdd?\xe7 \xe2? \xd7\xeb? \xea\xe3? \xec\xee? \xf4\xeb\xf2\xec\xd6\xf3? \xe8\x9d\xf3\xfa\xee\xe9.\x0a"); + } + + public function testEnglish() { + $this -> buffer -> writeText("The quick brown fox jumps over the lazy dog.\n"); + $this -> checkOutput("\x1b@The quick brown fox jumps over the lazy dog.\n"); + } + + public function testSpanish() { + // This one does not require changing code-pages at all, so characters are just converted from Unicode to CP437. + $this -> buffer -> writeText("El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro.\n"); + $this -> checkOutput("\x1b@El ping\x81ino Wenceslao hizo kil\xa2metros bajo exhaustiva lluvia y fr\xa1o, a\xa4oraba a su querido cachorro.\x0a"); + } + + public function testFrench() { + $this -> buffer -> writeText("Le cœur déçu mais l'âme plutôt naïve, Louÿs rêva de crapaüter en canoë au delà des îles, près du mälström où brûlent les novæ.\n"); + $this -> checkOutput("\x1b@Le c\x1bt\x10\x9cur d\xe9\xe7u mais l'\xe2me plut\xf4t na\xefve, Lou\xffs r\xeava de crapa\xfcter en cano\xeb au del\xe0 des \xeeles, pr\xe8s du m\xe4lstr\xf6m o\xf9 br\xfblent les nov\xe6.\x0a"); + } + + public function testIrishGaelic() { + // Note that some letters with diacritics cannot be printed for Irish Gaelic text, so text may need to be simplified. + $this -> buffer -> writeText("D'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh.\n"); + $this -> checkOutput("\x1b@D'fhuascail \x1bt\x02\xd6osa, \xe9rmhac na h\xe0ighe Beannaithe, p\xa2r \x90ava agus \xb5dhaimh.\x0a"); + } + + public function testHungarian() { + $this -> buffer -> writeText("Árvíztűrő tükörfúrógép.\n"); + $this -> checkOutput("\x1b@\x1bt\x02\xb5rv\xa1zt\x1bt\x12\xfbr\x8b t\x81k\x94rf\xa3r\xa2g\x82p.\x0a"); + } + + public function testIcelandic() { + $this -> buffer -> writeText("Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa."); + $this -> checkOutput("\x1b@K\x91mi n\x1bt\x02\xec \x94xi h\x82r ykist \xe7j\xa2fum n\xa3 b\x91\xd0i v\xa1l og \xa0drepa."); + } + + public function testJapaneseHiragana() { + $this -> markTestIncomplete("Non-ASCII character sets not yet supported."); + $this -> buffer -> writeText(implode("\n", array("いろはにほへとちりぬるを", " わかよたれそつねならむ", "うゐのおくやまけふこえて", "あさきゆめみしゑひもせす")) . "\n"); + $this -> checkOutput(); + } + + public function testJapaneseKatakana() { + $this -> markTestIncomplete("Non-ASCII character sets not yet supported."); + $this -> buffer -> writeText(implode("\n", array("イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム", "ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン")) . "\n"); + $this -> checkOutput("\x1b@\x1bt\x01\xb2\xdb\xca\xc6\xce\xcd\xc4 \xc1\xd8\xc7\xd9\xa6 \xdc\xb6\xd6\xc0\xda\xbf \xc2\xc8\xc5\xd7\xd1\x0a\xb3\xb2\xc9\xb5\xb8\xd4\xcf \xb9\xcc\xba\xb4\xc3 \xb1\xbb\xb7\xd5\xd2\xd0\xbc \xb4\xcb\xd3\xbe\xbd\xdd\x0a"); + } + + public function testJapaneseKataKanaHalfWidth() { + $this -> buffer -> writeText(implode("\n", array("イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム", "ウイノオクヤマ ケフコエテ アサキユメミシ エヒモセスン")) . "\n"); + $this -> checkOutput("\x1b@\x1bt\x01\xb2\xdb\xca\xc6\xce\xcd\xc4 \xc1\xd8\xc7\xd9\xa6 \xdc\xb6\xd6\xc0\xda\xbf \xc2\xc8\xc5\xd7\xd1\x0a\xb3\xb2\xc9\xb5\xb8\xd4\xcf \xb9\xcc\xba\xb4\xc3 \xb1\xbb\xb7\xd5\xd2\xd0\xbc \xb4\xcb\xd3\xbe\xbd\xdd\x0a"); + } + + public function testLatvian() { + $this -> buffer -> writeText("Glāžšķūņa rūķīši dzērumā čiepj Baha koncertflīģeļu vākus.\n"); + $this -> checkOutput("\x1b@Gl\x1bt!\x83\xd8\xd5\xe9\xd7\xeca r\xd7\xe9\x8c\xd5i dz\x89rum\x83 \xd1iepj Baha koncertfl\x8c\x85e\xebu v\x83kus.\x0a"); + } + + public function testPolish() { + $this -> buffer -> writeText("Pchnąć w tę łódź jeża lub ośm skrzyń fig.\n"); + $this -> checkOutput("\x1b@Pchn\x1bt\x12\xa5\x86 w t\xa9 \x88\xa2d\xab je\xbea lub o\x98m skrzy\xe4 fig.\x0a"); + } + + public function testRussian() { + $this -> buffer -> writeText("В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!\n"); + $this -> checkOutput("\x1b@\x1bt\x11\x82 \xe7\xa0\xe9\xa0\xe5 \xee\xa3\xa0 \xa6\xa8\xab \xa1\xeb \xe6\xa8\xe2\xe0\xe3\xe1? \x84\xa0, \xad\xae \xe4\xa0\xab\xec\xe8\xa8\xa2\xeb\xa9 \xed\xaa\xa7\xa5\xac\xaf\xab\xef\xe0!\x0a"); + } + + public function testThai() { + $this -> markTestIncomplete("Non-ASCII character sets not yet supported."); + $this -> buffer -> writeText("นายสังฆภัณฑ์ เฮงพิทักษ์ฝั่ง ผู้เฒ่าซึ่งมีอาชีพเป็นฅนขายฃวด ถูกตำรวจปฏิบัติการจับฟ้องศาล ฐานลักนาฬิกาคุณหญิงฉัตรชฎา ฌานสมาธิ\n"); // Quotation from Wikipedia + $this -> checkOutput(); + } + + public function testTurkish() { + $this -> buffer -> writeText("Pijamalı hasta, yağız şoföre çabucak güvendi.\n"); + $this -> checkOutput("\x1b@Pijamal\x1bt\x02\xd5 hasta, ya\x1bt\x0d\xa7\x8dz \x9fof\x94re \x87abucak g\x81vendi.\x0a"); + } + + public function testArabic() { + $this -> markTestIncomplete("Right-to-left text not yet supported."); + $this -> buffer -> writeText("صِف خَلقَ خَودِ كَمِثلِ الشَمسِ إِذ بَزَغَت — يَحظى الضَجيعُ بِها نَجلاءَ مِعطارِ" . "\n"); // Quotation from Wikipedia + $this -> checkOutput(); + } + + public function testHebrew() { + // RTL text is more complex than the above. + $this -> markTestIncomplete("Right-to-left text not yet supported."); + $this -> buffer -> writeText("דג סקרן שט בים מאוכזב ולפתע מצא לו חברה איך הקליטה" . "\n"); + $this -> checkOutput(); + } +} + diff --git a/htdocs/includes/mike42/escpos-php/test/unit/EscposTest.php b/htdocs/includes/mike42/escpos-php/test/unit/EscposTest.php new file mode 100644 index 00000000000..6fd0bd7d204 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/test/unit/EscposTest.php @@ -0,0 +1,765 @@ + outputConnector = new DummyPrintConnector(); + $this -> printer = new Escpos($this -> outputConnector); + } + + protected function checkOutput($expected = null) { + /* Check those output strings */ + $outp = $this -> outputConnector -> getData(); + if($expected === null) { + echo "\nOutput was:\n\"" . friendlyBinary($outp) . "\"\n"; + } + $this -> assertEquals($expected, $outp); + } + + protected function tearDown() { + $this -> outputConnector -> finalize(); + } + + protected function requireGraphicsLibrary() { + if(!EscposImage::isGdLoaded() && !EscposImage::isImagickLoaded()) { + // If the test is about to do something which requires a library, + // something must throw an exception. + $this -> setExpectedException('Exception'); + } + } + + public function testInitializeOutput() { + $this -> checkOutput("\x1b\x40"); + } + + public function testTextStringOutput() { + $this -> printer -> text("The quick brown fox jumps over the lazy dog\n"); + $this -> checkOutput("\x1b@The quick brown fox jumps over the lazy dog\n"); + } + + public function testTextDefault() { + $this -> printer -> text(); + $this -> checkOutput("\x1b@"); + } + + public function testTextString() { + $this -> printer -> text("String"); + $this -> printer -> text(123); + $this -> printer -> text(); + $this -> printer -> text(null); + $this -> printer -> text(1.2); + $this -> printer -> text(new FooBar("FooBar")); + $this -> checkOutput("\x1b@String1231.2FooBar"); + } + + public function testTextObject() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> text(new DateTime()); + } + + public function testFeedDefault() { + $this -> printer -> feed(); + $this -> checkOutput("\x1b@\x0a"); + } + + public function testFeed3Lines() { + $this -> printer -> feed(3); + $this -> checkOutput("\x1b@\x1bd\x03"); + } + + public function testFeedZero() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> feed(0); + } + + public function testFeedNonInteger() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> feed("ab"); + } + + public function testFeedTooLarge() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> feed(256); + } + + /* Print mode */ + public function testSelectPrintModeDefault() { + $this -> printer -> selectPrintMode(); + $this -> checkOutput("\x1b@\x1b!\x00"); + } + + public function testSelectPrintModeAcceptedValues() { + /* This iterates over a bunch of numbers, figures out which + ones contain invalid flags, and checks that the driver + rejects those, but accepts the good inputs */ + + for($i = -1; $i <= 256; $i++) { + $invalid = ($i < 0) || ($i > 255) || (($i & 2) == 2) || (($i & 4) == 4) || (($i & 64) == 64); + $failed = false; + try { + $this -> printer -> selectPrintMode($i); + } catch(Exception $e) { + $failed = true; + } + $this -> assertEquals($failed, $invalid); + } + } + + /* Underline */ + public function testSetUnderlineDefault() { + $this -> printer -> setUnderline(); + $this -> checkOutput("\x1b@\x1b-\x01"); + } + + public function testSetUnderlineOff() { + $this -> printer -> setUnderline(Escpos::UNDERLINE_NONE); + $this -> checkOutput("\x1b@\x1b-\x00"); + } + + public function testSetUnderlineOn() { + $this -> printer -> setUnderline(Escpos::UNDERLINE_SINGLE); + $this -> checkOutput("\x1b@\x1b-\x01"); + } + + public function testSetUnderlineDbl() { + $this -> printer -> setUnderline(Escpos::UNDERLINE_DOUBLE); + $this -> checkOutput("\x1b@\x1b-\x02"); + } + + public function testSetUnderlineAcceptedValues() { + $this -> printer -> setUnderline(0); + $this -> printer -> setUnderline(1); + $this -> printer -> setUnderline(2); + /* These map to 0 & 1 for interchangeability with setEmphasis */ + $this -> printer -> setUnderline(true); + $this -> printer -> setUnderline(false); + $this -> checkOutput("\x1b@\x1b-\x00\x1b-\x01\x1b-\x02\x1b-\x01\x1b-\x00"); + } + + public function testSetUnderlineTooLarge() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setUnderline(3); + } + + public function testSetUnderlineNegative() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setUnderline(-1); + } + + public function testSetUnderlineNonInteger() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setUnderline("Hello"); + } + + /* Emphasis */ + public function testSetEmphasisDefault() { + $this -> printer -> setEmphasis(); + $this -> checkOutput("\x1b@\x1bE\x01"); + } + + public function testSetEmphasisOn() { + $this -> printer -> setEmphasis(true); + $this -> checkOutput("\x1b@\x1bE\x01"); + } + + public function testSetEmphasisOff() { + $this -> printer -> setEmphasis(false); + $this -> checkOutput("\x1b@\x1bE\x00"); + } + + public function testSetEmphasisNonBoolean() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setEmphasis(7); + } + + /* Double strike */ + public function testSetDoubleStrikeDefault() { + $this -> printer -> setDoubleStrike(); + $this -> checkOutput("\x1b@\x1bG\x01"); + } + + public function testSetDoubleStrikeOn() { + $this -> printer -> setDoubleStrike(true); + $this -> checkOutput("\x1b@\x1bG\x01"); + } + + public function testSetDoubleStrikeOff() { + $this -> printer -> setDoubleStrike(false); + $this -> checkOutput("\x1b@\x1bG\x00"); + } + + public function testSetDoubleStrikeNonBoolean() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setDoubleStrike(4); + } + + /* Font */ + public function testSetFontDefault() { + $this -> printer -> setFont(); + $this -> checkOutput("\x1b@\x1bM\x00"); + } + + public function testSetFontAcceptedValues() { + $this -> printer -> setFont(Escpos::FONT_A); + $this -> printer -> setFont(Escpos::FONT_B); + $this -> printer -> setFont(Escpos::FONT_C); + $this -> checkOutput("\x1b@\x1bM\x00\x1bM\x01\x1bM\x02"); + } + + public function testSetFontNegative() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setFont(-1); + } + + + public function testSetFontTooLarge() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setFont(3); + } + + public function testSetFontNonInteger() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setFont('hello'); + } + + /* Justification */ + public function testSetJustificationDefault() { + $this -> printer -> setJustification(); + $this -> checkOutput("\x1b@\x1ba\x00"); + } + + public function testSetJustificationLeft() { + $this -> printer -> setJustification(Escpos::JUSTIFY_LEFT); + $this -> checkOutput("\x1b@\x1ba\x00"); + } + + public function testSetJustificationRight() { + $this -> printer -> setJustification(Escpos::JUSTIFY_RIGHT); + $this -> checkOutput("\x1b@\x1ba\x02"); + } + + public function testSetJustificationCenter() { + $this -> printer -> setJustification(Escpos::JUSTIFY_CENTER); + $this -> checkOutput("\x1b@\x1ba\x01"); + } + + public function testSetJustificationNegative() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setJustification(-1); + } + + + public function testSetJustificationTooLarge() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setFont(3); + } + + public function testSetJustificationNonInteger() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setJustification('hello'); + } + + /* Reverse feed */ + public function testFeedReverseDefault() { + $this -> printer -> feedReverse(); + $this -> checkOutput("\x1b@\x1be\x01"); + } + + public function testFeedReverse3() { + $this -> printer -> feedReverse(3); + $this -> checkOutput("\x1b@\x1be\x03"); + } + + public function testFeedReverseNegative() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> feedReverse(-1); + } + + public function testFeedReverseTooLarge() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> feedReverse(256); + } + + public function testFeedReverseNonInteger() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> feedReverse('hello'); + } + + /* Cut */ + public function testCutDefault() { + // TODO check what the accepted range of values should be for $line + // cut($mode = self::CUT_FULL, $lines = 3) + $this -> printer -> cut(); + $this -> checkOutput("\x1b@\x1dVA\x03"); + } + + /* Set barcode height */ + public function testSetBarcodeHeightDefault() { + $this -> printer -> setBarcodeHeight(); + $this -> checkOutput("\x1b@\x1dh\x08"); + } + + public function testBarcodeHeight10() { + $this -> printer -> setBarcodeHeight(10); + $this -> checkOutput("\x1b@\x1dh\x0a"); + } + + public function testSetBarcodeHeightNegative() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setBarcodeHeight(-1); + } + + public function testSetBarcodeHeightTooLarge() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setBarcodeHeight(256); + } + + public function tesSetBarcodeHeightNonInteger() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setBarcodeHeight('hello'); + } + + /* Barcode text position */ + public function testSetBarcodeTextPositionDefault() { + $this -> printer -> setBarcodeTextPosition(); + $this -> checkOutput("\x1b@\x1dH\x00"); + } + + public function testSetBarcodeTextPositionBelow() { + $this -> printer -> setBarcodeTextPosition(Escpos::BARCODE_TEXT_BELOW); + $this -> checkOutput("\x1b@\x1dH\x02"); + } + + public function testSetBarcodeTextPositionBoth() { + $this -> printer -> setBarcodeTextPosition(Escpos::BARCODE_TEXT_BELOW | Escpos::BARCODE_TEXT_ABOVE); + $this -> checkOutput("\x1b@\x1dH\x03"); + } + + public function testSetBarcodeTextPositionNegative() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setBarcodeTextPosition(-1); + } + + public function testSetBarcodeTextPositionTooLarge() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setBarcodeTextPosition(4); + } + + public function tesSetBarcodeTextPositionNonInteger() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setBarcodeTextPosition('hello'); + } + + /* Barcode - UPC-A */ + public function testBarcodeUpcaNumeric11Char() { + $this -> printer -> barcode("01234567890", Escpos::BARCODE_UPCA); + $this -> checkOutput("\x1b@\x1dkA\x0b01234567890"); + } + + public function testBarcodeUpcaNumeric12Char() { + $this -> printer -> barcode("012345678901", Escpos::BARCODE_UPCA); + $this -> checkOutput("\x1b@\x1dkA\x0c012345678901"); + } + + public function testBarcodeUpcaNumeric13Char() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("0123456789012", Escpos::BARCODE_UPCA); + } + + public function testBarcodeUpcaNonNumeric12Char() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("A12345678901", Escpos::BARCODE_UPCA); + } + + /* Barcode - UPC-E */ + public function testBarcodeUpceNumeric6Char() { + $this -> printer -> barcode("123456", Escpos::BARCODE_UPCE); + $this -> checkOutput("\x1b@\x1dkB\x06123456"); + } + + public function testBarcodeUpceNumeric7Char() { + $this -> printer -> barcode("0123456", Escpos::BARCODE_UPCE); + $this -> checkOutput("\x1b@\x1dkB\x070123456"); + } + + public function testBarcodeUpceNumeric8Char() { + $this -> printer -> barcode("01234567", Escpos::BARCODE_UPCE); + $this -> checkOutput("\x1b@\x1dkB\x0801234567"); + } + + public function testBarcodeUpceNumeric11Char() { + $this -> printer -> barcode("01234567890", Escpos::BARCODE_UPCE); + $this -> checkOutput("\x1b@\x1dkB\x0b01234567890"); + } + + public function testBarcodeUpceNumeric12Char() { + $this -> printer -> barcode("012345678901", Escpos::BARCODE_UPCE); + $this -> checkOutput("\x1b@\x1dkB\x0c012345678901"); + } + + public function testBarcodeUpceNumeric9Char() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("012345678", Escpos::BARCODE_UPCE); + } + + public function testBarcodeUpceNonNumeric12Char() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("A12345678901", Escpos::BARCODE_UPCE); + } + + /* Barcode - JAN13 */ + public function testBarcodeJan13Numeric12Char() { + $this -> printer -> barcode("012345678901", Escpos::BARCODE_JAN13); + $this -> checkOutput("\x1b@\x1dkC\x0c012345678901"); + } + + public function testBarcodeJan13Numeric13Char() { + $this -> printer -> barcode("0123456789012", Escpos::BARCODE_JAN13); + $this -> checkOutput("\x1b@\x1dkC\x0d0123456789012"); + } + + public function testBarcodeJan13Numeric11Char() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("01234567890", Escpos::BARCODE_JAN13); + } + + public function testBarcodeJan13NonNumeric13Char() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("A123456789012", Escpos::BARCODE_JAN13); + } + + /* Barcode - JAN8 */ + public function testBarcodeJan8Numeric7Char() { + $this -> printer -> barcode("0123456", Escpos::BARCODE_JAN8); + $this -> checkOutput("\x1b@\x1dkD\x070123456"); + } + + public function testBarcodeJan8Numeric8Char() { + $this -> printer -> barcode("01234567", Escpos::BARCODE_JAN8); + $this -> checkOutput("\x1b@\x1dkD\x0801234567"); + } + + public function testBarcodeJan8Numeric9Char() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("012345678", Escpos::BARCODE_JAN8); + } + + public function testBarcodeJan8NonNumeric8Char() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("A1234567", Escpos::BARCODE_JAN8); + } + + /* Barcode - Code39 */ + public function testBarcodeCode39AsDefault() { + $this -> printer -> barcode("1234"); + $this -> checkOutput("\x1b@\x1dkE\x041234"); + } + + public function testBarcodeCode39Text() { + $this -> printer -> barcode("ABC 012", Escpos::BARCODE_CODE39); + $this -> checkOutput("\x1b@\x1dkE\x07ABC 012"); + } + + public function testBarcodeCode39SpecialChars() { + $this -> printer -> barcode("$%+-./", Escpos::BARCODE_CODE39); + $this -> checkOutput("\x1b@\x1dkE\x06$%+-./"); + } + + public function testBarcodeCode39Asterisks() { + $this -> printer -> barcode("*TEXT*", Escpos::BARCODE_CODE39); + $this -> checkOutput("\x1b@\x1dkE\x06*TEXT*"); + } + + public function testBarcodeCode39AsterisksUnmatched() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("*TEXT", Escpos::BARCODE_CODE39); + } + + public function testBarcodeCode39AsteriskInText() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("12*34", Escpos::BARCODE_CODE39); + } + + public function testBarcodeCode39Lowercase() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("abcd", Escpos::BARCODE_CODE39); + } + + public function testBarcodeCode39Empty() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("**", Escpos::BARCODE_CODE39); + } + + /* Barcode - ITF */ + public function testBarcodeItfNumericEven() { + $this -> printer -> barcode("1234", Escpos::BARCODE_ITF); + $this -> checkOutput("\x1b@\x1dkF\x041234"); + } + + public function testBarcodeItfNumericOdd() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("123", Escpos::BARCODE_ITF); + } + + public function testBarcodeItfNonNumericEven() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("A234", Escpos::BARCODE_ITF); + } + + /* Barcode - Codabar */ + public function testBarcodeCodabarNumeric() { + $this -> printer -> barcode("A012345A", Escpos::BARCODE_CODABAR); + $this -> checkOutput("\x1b@\x1dkG\x08A012345A"); + } + + public function testBarcodeCodabarSpecialChars() { + $this -> printer -> barcode("A012$+-./:A", Escpos::BARCODE_CODABAR); + $this -> checkOutput("\x1b@\x1dkG\x0bA012$+-./:A"); + } + + public function testBarcodeCodabarNotWrapped() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("012345", Escpos::BARCODE_CODABAR); + } + + public function testBarcodeCodabarStartStopWrongPlace() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("012A45", Escpos::BARCODE_CODABAR); + } + + /* Barcode - Code93 */ + public function testBarcodeCode93Valid() { + $this -> printer -> barcode("012abcd", Escpos::BARCODE_CODE93); + $this -> checkOutput("\x1b@\x1dkH\x07012abcd"); + } + + public function testBarcodeCode93Empty() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("", Escpos::BARCODE_CODE93); + } + + /* Barcode - Code128 */ + public function testBarcodeCode128ValidA() { + $this -> printer -> barcode("{A" . "012ABCD", Escpos::BARCODE_CODE128); + $this -> checkOutput("\x1b@\x1dkI\x09{A012ABCD"); + } + + public function testBarcodeCode128ValidB() { + $this -> printer -> barcode("{B" . "012ABCDabcd", Escpos::BARCODE_CODE128); + $this -> checkOutput("\x1b@\x1dkI\x0d{B012ABCDabcd"); + } + + public function testBarcodeCode128ValidC() { + $this -> printer -> barcode("{C" . chr ( 21 ) . chr ( 32 ) . chr ( 43 ), Escpos::BARCODE_CODE128); + $this -> checkOutput("\x1b@\x1dkI\x05{C\x15 +"); + } + + public function testBarcodeCode128NoCodeSet() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> barcode("ABCD", Escpos::BARCODE_CODE128); + } + + /* Pulse */ + function testPulseDefault() { + $this -> printer -> pulse(); + $this -> checkOutput("\x1b@\x1bp0 printer -> pulse(1); + $this -> checkOutput("\x1b@\x1bp1 printer -> pulse(0, 2, 2); + $this -> checkOutput("\x1b@\x1bp0\x01\x01"); + } + + function testPulseOddMs() { + $this -> printer -> pulse(0, 3, 3); // Should be rounded down and give same output + $this -> checkOutput("\x1b@\x1bp0\x01\x01"); + } + + function testPulseTooHigh() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> pulse(0, 512, 2); + } + + function testPulseTooLow() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> pulse(0, 0, 2); + } + + function testPulseNotANumber() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> pulse("fish"); + } + + /* Set reverse */ + public function testSetReverseColorsDefault() { + $this -> printer -> setReverseColors(); + $this -> checkOutput("\x1b@\x1dB\x01"); + } + + public function testSetReverseColorsOn() { + $this -> printer -> setReverseColors(true); + $this -> checkOutput("\x1b@\x1dB\x01"); + } + + public function testSetReverseColorsOff() { + $this -> printer -> setReverseColors(false); + $this -> checkOutput("\x1b@\x1dB\x00"); + } + + public function testSetReverseColorsNonBoolean() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setReverseColors(7); + } + + /* Bit image print */ + public function testBitImageBlack() { + $this -> requireGraphicsLibrary(); + $img = new EscposImage(dirname(__FILE__)."/resources/canvas_black.png"); + $this -> printer -> bitImage($img); + $this -> checkOutput("\x1b@\x1dv0\x00\x01\x00\x01\x00\x80"); + } + + public function testBitImageWhite() { + $this -> requireGraphicsLibrary(); + $img = new EscposImage(dirname(__FILE__)."/resources/canvas_white.png"); + $this -> printer -> bitImage($img); + $this -> checkOutput("\x1b@\x1dv0\x00\x01\x00\x01\x00\x00"); + } + + public function testBitImageBoth() { + $this -> requireGraphicsLibrary(); + $img = new EscposImage(dirname(__FILE__)."/resources/black_white.png"); + $this -> printer -> bitImage($img); + $this -> checkOutput("\x1b@\x1dv0\x00\x01\x00\x02\x00\xc0\x00"); + } + + public function testBitImageTransparent() { + $this -> requireGraphicsLibrary(); + $img = new EscposImage(dirname(__FILE__)."/resources/black_transparent.png"); + $this -> printer -> bitImage($img); + $this -> checkOutput("\x1b@\x1dv0\x00\x01\x00\x02\x00\xc0\x00"); + } + + /* Graphics print */ + public function testGraphicsWhite() { + $this -> requireGraphicsLibrary(); + $img = new EscposImage(dirname(__FILE__)."/resources/canvas_white.png"); + $this -> printer -> graphics($img); + $this -> checkOutput("\x1b@\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x00\x1d(L\x02\x0002"); + } + + public function testGraphicsBlack() { + $this -> requireGraphicsLibrary(); + $img = new EscposImage(dirname(__FILE__)."/resources/canvas_black.png"); + $this -> printer -> graphics($img); + $this -> checkOutput("\x1b@\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x80\x1d(L\x02\x0002"); + } + + public function testGraphicsBoth() { + $this -> requireGraphicsLibrary(); + $img = new EscposImage(dirname(__FILE__)."/resources/black_white.png"); + $this -> printer -> graphics($img); + $this -> checkOutput("\x1b@\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002"); + } + + public function testGraphicsTransparent() { + $this -> requireGraphicsLibrary(); + $img = new EscposImage(dirname(__FILE__)."/resources/black_transparent.png"); + $this -> printer -> graphics($img); + $this -> checkOutput("\x1b@\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002"); + } + + /* QR code */ + public function testQRCodeDefaults() { + // Test will fail if default values change + $this -> printer -> qrCode("1234"); + $this -> checkOutput("\x1b@\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d(k\x07\x001P01234\x1d(k\x03\x001Q0"); + } + + public function testQRCodeDefaultsAreCorrect() { + // Below tests assume that defaults are as written here (output string should be same as above) + $this -> printer -> qrCode("1234", Escpos::QR_ECLEVEL_L, 3, Escpos::QR_MODEL_2); + $this -> checkOutput("\x1b@\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d(k\x07\x001P01234\x1d(k\x03\x001Q0"); + } + + public function testQRCodeEmpty() { + $this -> printer -> qrCode(''); + $this -> checkOutput("\x1b@"); // No commands actually sent + } + + public function testQRCodeChangeEC() { + $this -> printer -> qrCode("1234", Escpos::QR_ECLEVEL_H); + $this -> checkOutput("\x1b@\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E3\x1d(k\x07\x001P01234\x1d(k\x03\x001Q0"); + } + + public function testQRCodeChangeSize() { + $this -> printer -> qrCode("1234", Escpos::QR_ECLEVEL_L, 7); + $this -> checkOutput("\x1b@\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x07\x1d(k\x03\x001E0\x1d(k\x07\x001P01234\x1d(k\x03\x001Q0"); + } + + public function testQRCodeChangeModel() { + $this -> printer -> qrCode("1234", Escpos::QR_ECLEVEL_L, 3, Escpos::QR_MODEL_1); + $this -> checkOutput("\x1b@\x1d(k\x04\x001A1\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d(k\x07\x001P01234\x1d(k\x03\x001Q0"); + } + + /* Feed form - Required on page-mode only printers */ + public function testFeedForm() { + $this -> printer -> feedForm(); + $this -> checkOutput("\x1b@\x0c"); + } + + /* Get status */ + public function testGetStatus() { + $this -> markTestIncomplete("Status check test code not implemented."); + // TODO some unit testing here on statuses + // $a = $this -> printer -> getPrinterStatus(Escpos::STATUS_PRINTER); + } + + /* Set text size */ + public function testSetTextSizeNormal() { + $this -> printer -> setTextSize(1, 1); + $this -> checkOutput("\x1b@\x1d!\x00"); + } + + public function testSetTextSizeWide() { + $this -> printer -> setTextSize(4, 1); + $this -> checkOutput("\x1b@\x1d!0"); + } + + public function testSetTextSizeNarrow() { + $this -> printer -> setTextSize(1, 4); + $this -> checkOutput("\x1b@\x1d!\x03"); + } + + public function testSetTextSizeLarge() { + $this -> printer -> setTextSize(4, 4); + $this -> checkOutput("\x1b@\x1d!3"); + } + + public function testSetTextSizeInvalid() { + $this -> setExpectedException('InvalidArgumentException'); + $this -> printer -> setTextSize(0, 9); + } +} + +/* + * For testing that string-castable objects are handled + */ +class FooBar { + private $foo; + public function __construct($foo) { + $this -> foo = $foo; + } + + public function __toString() { + return $this -> foo; + } +} +?> diff --git a/htdocs/includes/mike42/escpos-php/test/unit/WindowsPrintConnectorTest.php b/htdocs/includes/mike42/escpos-php/test/unit/WindowsPrintConnectorTest.php new file mode 100644 index 00000000000..392775d3277 --- /dev/null +++ b/htdocs/includes/mike42/escpos-php/test/unit/WindowsPrintConnectorTest.php @@ -0,0 +1,278 @@ + getMockConnector("LPT1", WindowsPrintConnector::PLATFORM_WIN); + $connector -> expects($this -> once()) + -> method('runWrite') + -> with($this -> equalTo(''), $this -> equalTo("LPT1")); + $connector -> expects($this -> exactly(0)) + -> method('runCommand'); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> finalize(); + } + + public function testLptMac() { + // Cannot print to local printer on Mac with this connector + $this -> setExpectedException('BadMethodCallException'); + $connector = $this -> getMockConnector("LPT1", WindowsPrintConnector::PLATFORM_MAC); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> expects($this -> exactly(0)) + -> method('runCommand'); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> finalize(); + } + + public function testLptLinux() { + // Cannot print to local printer on Linux with this connector + $this -> setExpectedException('BadMethodCallException'); + $connector = $this -> getMockConnector("LPT1", WindowsPrintConnector::PLATFORM_LINUX); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> expects($this -> exactly(0)) + -> method('runCommand'); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> finalize(); + } + + public function testComWindows() { + // Simple file write + $connector = $this -> getMockConnector("COM1", WindowsPrintConnector::PLATFORM_WIN); + $connector -> expects($this -> once()) + -> method('runWrite') + -> with($this -> equalTo(''), $this -> equalTo("COM1")); + $connector -> expects($this -> exactly(0)) + -> method('runCommand'); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> finalize(); + } + + public function testComMac() { + // Cannot print to local printer on Mac with this connector + $this -> setExpectedException('BadMethodCallException'); + $connector = $this -> getMockConnector("COM1", WindowsPrintConnector::PLATFORM_MAC); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> expects($this -> exactly(0)) + -> method('runCommand'); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> finalize(); + } + + public function testComLinux() { + // Cannot print to local printer on Linux with this connector + $this -> setExpectedException('BadMethodCallException'); + $connector = $this -> getMockConnector("COM1", WindowsPrintConnector::PLATFORM_LINUX); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> expects($this -> exactly(0)) + -> method('runCommand'); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> finalize(); + } + + public function testLocalShareWindows() { + $connector = $this -> getMockConnector("Printer", WindowsPrintConnector::PLATFORM_WIN); + $connector -> expects($this -> exactly(0)) + -> method('runCommand'); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> expects($this -> once()) + -> method('runCopy') + -> with($this -> anything(), $this -> stringContains('\\Printer')); + $connector -> finalize(); + } + + public function testSharedPrinterWindows() { + $connector = $this -> getMockConnector("smb://example-pc/Printer", WindowsPrintConnector::PLATFORM_WIN); + $connector -> expects($this -> exactly(0)) + -> method('runCommand'); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> expects($this -> once()) + -> method('runCopy') + -> with($this -> anything(), $this -> equalTo('\\\\example-pc\\Printer')); + $connector -> finalize(); + } + + public function testSharedPrinterWindowsUsername() { + $connector = $this -> getMockConnector("smb://bob@example-pc/Printer", WindowsPrintConnector::PLATFORM_WIN); + $connector -> expects($this -> once()) + -> method('runCommand') + -> with($this -> equalTo('net use \'\\\\example-pc\\Printer\' \'/user:bob\'')); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> expects($this -> once()) + -> method('runCopy') + -> with($this -> anything(), $this -> equalTo('\\\\example-pc\\Printer')); + $connector -> finalize(); + } + + public function testSharedPrinterWindowsUsernameDomain() { + $connector = $this -> getMockConnector("smb://bob@example-pc/home/Printer", WindowsPrintConnector::PLATFORM_WIN); + $connector -> expects($this -> once()) + -> method('runCommand') + -> with($this -> equalTo('net use \'\\\\example-pc\\Printer\' \'/user:home\\bob\'')); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> expects($this -> once()) + -> method('runCopy') + -> with($this -> anything(), $this -> equalTo('\\\\example-pc\\Printer')); + $connector -> finalize(); + } + + public function testSharedPrinterWindowsUsernamePassword() { + $connector = $this -> getMockConnector("smb://bob:secret@example-pc/Printer", WindowsPrintConnector::PLATFORM_WIN); + $connector -> expects($this -> once()) + -> method('runCommand') + -> with($this -> equalTo('net use \'\\\\example-pc\\Printer\' \'/user:bob\' \'secret\'')); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> expects($this -> once()) + -> method('runCopy') + -> with($this -> anything(), $this -> equalTo('\\\\example-pc\\Printer')); + $connector -> finalize(); + } + + public function testSharedPrinterMac() { + // Not implemented + $this -> setExpectedException('Exception'); + $connector = $this -> getMockConnector("smb://Guest@example-pc/Printer", WindowsPrintConnector::PLATFORM_MAC); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> expects($this -> exactly(0)) + -> method('runCommand'); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> finalize(); + } + + public function testSharedPrinterLinux() { + $connector = $this -> getMockConnector("smb://example-pc/Printer", WindowsPrintConnector::PLATFORM_LINUX); + $connector -> expects($this -> once()) + -> method('runCommand') + -> with($this -> equalTo('smbclient \'//example-pc/Printer\' -c \'print -\' -N')); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> finalize(); + } + + public function testSharedPrinterLinuxUsername() { + $connector = $this -> getMockConnector("smb://bob@example-pc/Printer", WindowsPrintConnector::PLATFORM_LINUX); + $connector -> expects($this -> once()) + -> method('runCommand') + -> with($this -> equalTo('smbclient \'//example-pc/Printer\' -U \'bob\' -c \'print -\' -N')); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> finalize(); + } + + public function testSharedPrinterLinuxUsernameDomain() { + $connector = $this -> getMockConnector("smb://bob@example-pc/home/Printer", WindowsPrintConnector::PLATFORM_LINUX); + $connector -> expects($this -> once()) + -> method('runCommand') + -> with($this -> equalTo('smbclient \'//example-pc/Printer\' -U \'home\\bob\' -c \'print -\' -N')); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> finalize(); + } + + public function testSharedPrinterLinuxUsernamePassword() { + $connector = $this -> getMockConnector("smb://bob:secret@example-pc/Printer", WindowsPrintConnector::PLATFORM_LINUX); + $connector -> expects($this -> once()) + -> method('runCommand') + -> with($this -> equalTo('smbclient \'//example-pc/Printer\' \'secret\' -U \'bob\' -c \'print -\'')); + $connector -> expects($this -> exactly(0)) + -> method('runCopy'); + $connector -> expects($this -> exactly(0)) + -> method('runWrite'); + $connector -> finalize(); + } + + private function getMockConnector($path, $platform) { + $stub = $this -> getMockBuilder('WindowsPrintConnector') + -> setMethods(array('runCopy', 'runCommand', 'getCurrentPlatform', 'runWrite')) + -> disableOriginalConstructor() + -> getMock(); + $stub -> method('runCommand') + -> willReturn(0); + $stub -> method('runCopy') + -> willReturn(true); + $stub -> method('runWrite') + -> willReturn(true); + $stub -> method('getCurrentPlatform') + -> willReturn($platform); + $stub -> __construct($path); + return $stub; + } + + /** + * Test for correct identification of bogus or non-supported Samba strings. + */ + public function testSambaRegex() { + $good = array("smb://foo/bar", + "smb://foo/bar baz", + "smb://bob@foo/bar", + "smb://bob:secret@foo/bar", + "smb://foo-computer/FooPrinter", + "smb://foo-computer/workgroup/FooPrinter", + "smb://foo-computer/Foo-Printer", + "smb://foo-computer/workgroup/Foo-Printer", + "smb://foo-computer/Foo Printer"); + $bad = array("", + "http://google.com", + "smb:/foo/bar", + "smb://", + "smb:///bar", + "smb://@foo/bar", + "smb://bob:@foo/bar", + "smb://:secret@foo/bar", + "smb://foo/bar/baz/quux", + "smb://foo-computer//FooPrinter"); + foreach($good as $item) { + $this -> assertTrue(preg_match(WindowsPrintConnector::REGEX_SMB, $item) == 1, "Windows samba regex should pass '$item'."); + } + foreach($bad as $item) { + $this -> assertTrue(preg_match(WindowsPrintConnector::REGEX_SMB, $item) != 1, "Windows samba regex should fail '$item'."); + } + } + + public function testPrinterNameRegex() { + $good = array("a", + "ab", + "a b", + "a-b", + "Abcd Efg-", + "-a" + ); + $bad = array("", + " ", + "a ", + " a", + " a ", + "a/B", + "A:b" + ); + foreach($good as $item) { + $this -> assertTrue(preg_match(WindowsPrintConnector::REGEX_PRINTERNAME, $item) == 1, "Windows printer name regex should pass '$item'."); + } + foreach($bad as $item) { + $this -> assertTrue(preg_match(WindowsPrintConnector::REGEX_PRINTERNAME, $item) != 1, "Windows printer name regex should fail '$item'."); + } + } +} diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/black_transparent.gif b/htdocs/includes/mike42/escpos-php/test/unit/resources/black_transparent.gif new file mode 100644 index 0000000000000000000000000000000000000000..6c54bad9eea0d1cd39e55504d181b6607b6028d4 GIT binary patch literal 65 zcmZ?wbhEHbWMW`q_`m=Kia%Kx85kHD6#of27o{eaq^2m8XO?6rxO@5rFzA33fs`{a LF|!D<>&kwgnNLhfd<&=O9H5Y7iEBiObAE1aYF-J0b5UwyNotBh zd1gt5g1e`0KzJjcI8YJ4r;B3<$MxitgoGdG8+aJ_#26XR&Dh)tlw$C7^>bP0l+XkK D$`C0q literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.bmp b/htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0acbf66506f8ae152df4bfabc835e03505112878 GIT binary patch literal 138 ycmZ?r?P7p{Dj<~u#7t1k$RGih5CD?G+z<>F01{5_LB%jOu{6kBWbhvh5F!BXKMAt{ literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.gif b/htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.gif new file mode 100644 index 0000000000000000000000000000000000000000..0a044a671f59c3c4a5f859eb6a09a71d809be683 GIT binary patch literal 65 zcmZ?wbhEHbWMW`q_`t{j1poj4SNzEWVlgQG6Lc<0O)N=GQ7F$W$xv|j^bKIp0m*=r NGcYl;2(dC)0{}064pjgE literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.jpg b/htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6539cece59e6c5ce41bf50f1ced121096ead5aed GIT binary patch literal 175 zcmex=>ukC3pCfH06P@c#e52OWR?s5zX<@pgBjQW literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.png b/htdocs/includes/mike42/escpos-php/test/unit/resources/black_white.png new file mode 100644 index 0000000000000000000000000000000000000000..33ba331fa1e8642247b0cd1e48e03e7bb24a624b GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^Od!kwBL7~QRScv!3p^r$G`BDaGcwGYBLNg-FY)ws zWxvnN$08&+x9_71P)M@GHKN2hKQ}iuuY|$5C^fMpHASI3vm`^o-P1Q9ypc~Fs7TDy o#W95AdU65;{Q3Xio|&1MfuD`(|Am>%Vn7uPp00i_>zopr06?22>i_@% literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_black.bmp b/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_black.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2ee723c59d87be01c0901a2662fc414e7898d904 GIT binary patch literal 142 zcmZ?r?PGv|E+AC{#Eft(0hV9^ljy{M1_lTMN&I(m4=Qdr^i?Bahn%QF>uW{_V`Jfl cv@|t`v^2H_GiP$za9w9siRm{&HU*ox0Ab1$MF0Q* literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_black.gif b/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_black.gif new file mode 100644 index 0000000000000000000000000000000000000000..49b19dbc179f5995e4ff5338e7039d3a47b4a888 GIT binary patch literal 72 zcmZ?wbhEHbWMp7u_`t{j1poj4SNzEWVlgQG=l0A^Oi%SqOwUZt=1ot`%}um5&@(YL UF*Rk-0jU6KV_;%(VPvod0K2{rbpQYW literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_black.jpg b/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_black.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d059f431b267596da878937214280915c89f51ee GIT binary patch literal 160 zcmZ9GK@P$|3!+y-7X048hz>mPHCTnRfR>gOhxmUMTR+ao=IO?3^7bqx(d49u-eEv*bK owG9oe3=Ayq=dVT4kei>9nO2EggGbYKL7)Z(Pgg&ebxsLQ05fwqK>z>% literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_white.bmp b/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_white.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e47e0ef617583acde08ae6d8ddf8045fb1a4e55e GIT binary patch literal 142 zcmZ?r?PGv|E+AC{#Eft(0hV9^ljy{M1_lTMN&I(m4=Qdr^i?Bahn%QF>uW{_V`Jfl hv@|t`v^2H_GiP$za9w9siRm{&HU-_>|Ns9p003&i7eW94 literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_white.gif b/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_white.gif new file mode 100644 index 0000000000000000000000000000000000000000..7881ce629acf8d6918eb5ac07169bc67b99a904b GIT binary patch literal 72 zcmZ?wbhEHbWMp7u_`t{j1poj4SNzEWVlgQG=l0A^Oi%SqOwUZt=1ot`%}um5&@(YL UF*Rk-0jU6KV_;(PVPvod0K480eEnh z;e>7%gm;ZT^#P}}Pr|CgB3h=RdCVe19mdJ5nwEK)ZE4y3hRm@?RN;3VV)+M&*8W@g M3*4UIi0j**UsypDH2?qr literal 0 HcmV?d00001 diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_white.png b/htdocs/includes/mike42/escpos-php/test/unit/resources/canvas_white.png new file mode 100644 index 0000000000000000000000000000000000000000..4231a4bb5400c5554d146b0f8b32b717c109c1bf GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^j35jm7|ip2ssJgLbVpxD28NCO++aH!(Ecb readImage("doc.pdf[5]"); + $im -> destroy(); +} catch(ImagickException $e) { + echo "Error: " . $e -> getMessage() . "\n"; +} + +$im = new Imagick(); +try { + ob_start(); + @$im -> readImage("doc.pdf[5]"); + ob_end_clean(); + $im -> destroy(); +} catch(ImagickException $e) { + echo "Error: " . $e -> getMessage() . "\n"; +} + diff --git a/htdocs/includes/mike42/escpos-php/test/unit/resources/doc.pdf b/htdocs/includes/mike42/escpos-php/test/unit/resources/doc.pdf new file mode 100644 index 0000000000000000000000000000000000000000..22247b918dbbcbfb0981d9442c966c99a47361b4 GIT binary patch literal 6681 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 bQ5=#CZ9g8L&gBcKi39DzR8)+usIdP4$C)m7 literal 0 HcmV?d00001 From bba3dc7dcd63f1c668c659f1f760b373c472c185 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 18 May 2016 13:39:57 +0200 Subject: [PATCH 46/61] Make composer cleaner --- build/composer/README | 7 +- composer.json | 4 +- composer.lock | 1233 ++++++- htdocs/includes/ckeditor/ckeditor/CHANGES.md | 2145 +++++------ htdocs/includes/ckeditor/ckeditor/LICENSE.md | 2840 +++++++------- .../ckeditor/ckeditor/build-config.js | 102 +- htdocs/includes/ckeditor/ckeditor/ckeditor.js | 2122 +++++------ .../includes/ckeditor/ckeditor/contents.css | 270 +- htdocs/includes/ckeditor/ckeditor/lang/af.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/ar.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/bg.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/bn.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/bs.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/ca.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/cs.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/cy.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/da.js | 8 +- .../includes/ckeditor/ckeditor/lang/de-ch.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/de.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/el.js | 8 +- .../includes/ckeditor/ckeditor/lang/en-au.js | 8 +- .../includes/ckeditor/ckeditor/lang/en-ca.js | 8 +- .../includes/ckeditor/ckeditor/lang/en-gb.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/en.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/eo.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/es.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/et.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/eu.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/fa.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/fi.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/fo.js | 8 +- .../includes/ckeditor/ckeditor/lang/fr-ca.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/fr.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/gl.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/gu.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/he.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/hi.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/hr.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/hu.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/id.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/is.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/it.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/ja.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/ka.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/km.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/ko.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/ku.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/lt.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/lv.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/mk.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/mn.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/ms.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/nb.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/nl.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/no.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/pl.js | 8 +- .../includes/ckeditor/ckeditor/lang/pt-br.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/pt.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/ro.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/ru.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/si.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/sk.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/sl.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/sq.js | 8 +- .../ckeditor/ckeditor/lang/sr-latn.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/sr.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/sv.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/th.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/tr.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/tt.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/ug.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/uk.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/vi.js | 8 +- .../includes/ckeditor/ckeditor/lang/zh-cn.js | 8 +- htdocs/includes/ckeditor/ckeditor/lang/zh.js | 8 +- .../includes/ckeditor/ckeditor/package.json | 2 +- .../dialogs/lang/_translationstatus.txt | 50 +- .../colordialog/dialogs/colordialog.css | 20 + .../colordialog/dialogs/colordialog.js | 19 +- .../ckeditor/plugins/find/dialogs/find.js | 21 +- .../ckeditor/ckeditor/plugins/icons.png | Bin 16152 -> 20634 bytes .../ckeditor/ckeditor/plugins/icons_hidpi.png | Bin 56750 -> 67753 bytes .../ckeditor/plugins/preview/preview.html | 26 +- .../ckeditor/plugins/scayt/CHANGELOG.md | 40 +- .../ckeditor/plugins/scayt/LICENSE.md | 56 +- .../ckeditor/ckeditor/plugins/scayt/README.md | 50 +- .../plugins/scayt/dialogs/toolbar.css | 142 +- .../dialogs/lang/_translationstatus.txt | 40 +- .../plugins/templates/dialogs/templates.css | 168 +- .../ckeditor/ckeditor/plugins/wsc/LICENSE.md | 56 +- .../ckeditor/ckeditor/plugins/wsc/README.md | 50 +- .../ckeditor/plugins/wsc/dialogs/ciframe.html | 132 +- .../plugins/wsc/dialogs/tmpFrameset.html | 104 +- .../ckeditor/plugins/wsc/dialogs/wsc.css | 164 +- .../ckeditor/ckeditor/samples/css/samples.css | 3280 ++++++++--------- .../ckeditor/ckeditor/samples/index.html | 256 +- .../ckeditor/ckeditor/samples/js/sample.js | 106 +- .../ckeditor/ckeditor/samples/old/ajax.html | 170 +- .../ckeditor/ckeditor/samples/old/api.html | 420 +-- .../ckeditor/samples/old/appendto.html | 118 +- .../old/assets/outputxhtml/outputxhtml.css | 408 +- .../samples/old/assets/posteddata.php | 118 +- .../ckeditor/samples/old/datafiltering.html | 1016 ++--- .../samples/old/dialog/assets/my_dialog.js | 96 +- .../ckeditor/samples/old/dialog/dialog.html | 380 +- .../ckeditor/samples/old/divreplace.html | 288 +- .../samples/old/enterkey/enterkey.html | 212 +- .../old/htmlwriter/outputforflash.html | 566 +-- .../samples/old/htmlwriter/outputhtml.html | 448 +-- .../ckeditor/ckeditor/samples/old/index.html | 224 +- .../ckeditor/samples/old/inlineall.html | 628 ++-- .../ckeditor/samples/old/inlinebycode.html | 248 +- .../ckeditor/samples/old/inlinetextarea.html | 226 +- .../ckeditor/ckeditor/samples/old/jquery.html | 206 +- .../samples/old/magicline/magicline.html | 418 +-- .../ckeditor/samples/old/readonly.html | 152 +- .../ckeditor/samples/old/replacebyclass.html | 120 +- .../ckeditor/samples/old/replacebycode.html | 118 +- .../ckeditor/ckeditor/samples/old/sample.css | 714 ++-- .../ckeditor/ckeditor/samples/old/sample.js | 100 +- .../samples/old/sample_posteddata.php | 32 +- .../ckeditor/samples/old/tabindex.html | 156 +- .../ckeditor/samples/old/toolbar/toolbar.html | 470 +-- .../ckeditor/samples/old/uicolor.html | 144 +- .../ckeditor/samples/old/uilanguages.html | 244 +- .../samples/old/wysiwygarea/fullpage.html | 160 +- .../ckeditor/samples/old/xhtmlstyle.html | 468 +-- .../toolbarconfigurator/css/fontello.css | 110 +- .../toolbarconfigurator/font/LICENSE.txt | 20 +- .../samples/toolbarconfigurator/index.html | 892 ++--- .../lib/codemirror/codemirror.css | 650 ++-- .../lib/codemirror/neo.css | 72 +- .../lib/codemirror/show-hint.css | 76 +- .../ckeditor/ckeditor/skins/moono/dialog.css | 10 +- .../ckeditor/skins/moono/dialog_ie.css | 10 +- .../ckeditor/skins/moono/dialog_ie7.css | 10 +- .../ckeditor/skins/moono/dialog_ie8.css | 10 +- .../ckeditor/skins/moono/dialog_iequirks.css | 10 +- .../ckeditor/ckeditor/skins/moono/editor.css | 10 +- .../ckeditor/skins/moono/editor_gecko.css | 10 +- .../ckeditor/skins/moono/editor_ie.css | 10 +- .../ckeditor/skins/moono/editor_ie7.css | 10 +- .../ckeditor/skins/moono/editor_ie8.css | 10 +- .../ckeditor/skins/moono/editor_iequirks.css | 10 +- .../ckeditor/ckeditor/skins/moono/icons.png | Bin 16152 -> 20634 bytes .../ckeditor/skins/moono/icons_hidpi.png | Bin 56750 -> 67753 bytes .../ckeditor/ckeditor/skins/moono/readme.md | 98 +- htdocs/includes/ckeditor/ckeditor/styles.js | 222 +- 148 files changed, 13279 insertions(+), 12080 deletions(-) create mode 100644 htdocs/includes/ckeditor/ckeditor/plugins/colordialog/dialogs/colordialog.css diff --git a/build/composer/README b/build/composer/README index 2464b0d246e..d58359defec 100644 --- a/build/composer/README +++ b/build/composer/README @@ -1,5 +1,10 @@ -To upgrade a lib with composer: +To test upgrade of a lib with composer: composer update --no-dev --no-autoloader --dry-run ccampbell/chromephp +To upgrade a lib with composer: + +composer update --no-dev --no-autoloader ccampbell/chromephp + + diff --git a/composer.json b/composer.json index ab60ce4f1b2..113a04e3163 100644 --- a/composer.json +++ b/composer.json @@ -15,9 +15,9 @@ "require": { "php": ">=5.3.0", "ext-curl": "*", - "ccampbell/chromephp": "^4.1", + "ccampbell/chromephp": "4.1.0", "ckeditor/ckeditor": "dev-full/stable", - "mike42/escpos-php": "dev-master", + "mike42/escpos-php": "1.2.1", "mobiledetect/mobiledetectlib": "2.8.17", "phpoffice/phpexcel": "1.8.1", "restler/framework": "3.0.0-RC6", diff --git a/composer.lock b/composer.lock index df333f5571c..b0771bd8054 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "c2b53c577364dbe3a56137043081b511", - "content-hash": "8f7a86cfbc13f45e13b73c49531818cb", + "hash": "b4b2182f7db9f00a023fce81370be0bb", + "content-hash": "8d110e7d8fca6eca1aa814ee35d0032b", "packages": [ { "name": "ccampbell/chromephp", @@ -56,12 +56,12 @@ "source": { "type": "git", "url": "https://github.com/ckeditor/ckeditor-releases.git", - "reference": "e3eb254641c4c349ffc19e49bd4a1a6831b5b6d0" + "reference": "4a7a6d717f9a408fa8f9ea53ef2dba4d64b83e91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ckeditor/ckeditor-releases/zipball/e3eb254641c4c349ffc19e49bd4a1a6831b5b6d0", - "reference": "e3eb254641c4c349ffc19e49bd4a1a6831b5b6d0", + "url": "https://api.github.com/repos/ckeditor/ckeditor-releases/zipball/4a7a6d717f9a408fa8f9ea53ef2dba4d64b83e91", + "reference": "4a7a6d717f9a408fa8f9ea53ef2dba4d64b83e91", "shasum": "" }, "type": "library", @@ -89,27 +89,28 @@ "text", "wysiwyg" ], - "time": "2016-03-31 16:19:25" + "time": "2016-05-12 15:36:04" }, { "name": "mike42/escpos-php", - "version": "dev-master", + "version": "v1.2.1", "source": { "type": "git", "url": "https://github.com/mike42/escpos-php.git", - "reference": "96f05cbf460f5b67c2184ee4e91aedfbcedeb788" + "reference": "cfea4c4fc95516ac953e1e5b623f854632afa2ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mike42/escpos-php/zipball/96f05cbf460f5b67c2184ee4e91aedfbcedeb788", - "reference": "96f05cbf460f5b67c2184ee4e91aedfbcedeb788", + "url": "https://api.github.com/repos/mike42/escpos-php/zipball/cfea4c4fc95516ac953e1e5b623f854632afa2ee", + "reference": "cfea4c4fc95516ac953e1e5b623f854632afa2ee", "shasum": "" }, "require": { "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "4.5.*" + "phpunit/phpunit": "4.5.*", + "squizlabs/php_codesniffer": "2.*" }, "type": "library", "autoload": { @@ -152,7 +153,7 @@ "print", "receipt" ], - "time": "2016-03-27 23:08:27" + "time": "2016-04-25 01:14:07" }, { "name": "mobiledetect/mobiledetectlib", @@ -272,12 +273,12 @@ "source": { "type": "git", "url": "https://github.com/Luracast/Restler-Framework.git", - "reference": "6ee10b3e5dbc6376916fed55ec2340a37cce436b" + "reference": "bfe1139b233852b745c6a0ec14d7244ceb3b3fc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Luracast/Restler-Framework/zipball/6ee10b3e5dbc6376916fed55ec2340a37cce436b", - "reference": "6ee10b3e5dbc6376916fed55ec2340a37cce436b", + "url": "https://api.github.com/repos/Luracast/Restler-Framework/zipball/bfe1139b233852b745c6a0ec14d7244ceb3b3fc6", + "reference": "bfe1139b233852b745c6a0ec14d7244ceb3b3fc6", "shasum": "" }, "require": { @@ -286,23 +287,14 @@ "replace": { "luracast/restler": "3.*" }, - "require-dev": { - "bshaffer/oauth2-server-php": "v1.0", - "luracast/explorer": "*", - "mustache/mustache": "dev-master", - "rodneyrehm/plist": "dev-master", - "symfony/yaml": "*", - "twig/twig": "v1.13.0", - "zendframework/zendamf": "dev-master" - }, "suggest": { - "bshaffer/oauth2-server-php": "Restler can provide OAuth2 authentication using this library (see require-dev for details)", - "luracast/explorer": "Restler's very own api explorer (see require-dev for details)", - "mustache/mustache": "Restler can render HtmlView using mustache/handlebar templates (see require-dev for details)", - "rodneyrehm/plist": "Restler supports tho Apple plist xml format (see require-dev for details)", - "symfony/yaml": "Restler can produce content in yaml format as well (see require-dev for details)", - "twig/twig": "Restler can render HtmlView using twig templates (see require-dev for details)", - "zendframework/zendamf": "Support for the amf document format (see require-dev for details)" + "bshaffer/oauth2-server-php": "If you want to use OAuth2 for authentication", + "illuminate/view": "If you want to use laravel blade templates with Html format", + "mustache/mustache": "If you want to use mustache/handlebar templates with Html format", + "rodneyrehm/plist": "If you need Apple plist binary/xml format", + "symfony/yaml": "If you need YAML format", + "twig/twig": "If you want to use twig templates with Html format", + "zendframework/zendamf": "If you need AMF format" }, "type": "library", "extra": { @@ -323,10 +315,6 @@ { "name": "Luracast", "email": "arul@luracast.com" - }, - { - "name": "Nick nickl- Lombard", - "email": "github@jigsoft.co.za" } ], "description": "Just the Restler Framework without the tests and examples", @@ -337,7 +325,7 @@ "rest", "server" ], - "time": "2015-08-04 07:52:49" + "time": "2016-02-28 15:57:37" }, { "name": "tecnickcom/tcpdf", @@ -403,12 +391,1181 @@ "time": "2015-09-12 10:08:34" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "jakub-onderka/php-console-color", + "version": "0.1", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", + "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/e0b393dacf7703fc36a4efc3df1435485197e6c1", + "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "jakub-onderka/php-code-style": "1.0", + "jakub-onderka/php-parallel-lint": "0.*", + "jakub-onderka/php-var-dump-check": "0.*", + "phpunit/phpunit": "3.7.*", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "JakubOnderka\\PhpConsoleColor": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com", + "homepage": "http://www.acci.cz" + } + ], + "time": "2014-04-08 15:00:19" + }, + { + "name": "jakub-onderka/php-console-highlighter", + "version": "v0.3.2", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "shasum": "" + }, + "require": { + "jakub-onderka/php-console-color": "~0.1", + "php": ">=5.3.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "~1.0", + "jakub-onderka/php-parallel-lint": "~0.5", + "jakub-onderka/php-var-dump-check": "~0.1", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JakubOnderka\\PhpConsoleHighlighter": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "time": "2015-04-20 18:58:01" + }, + { + "name": "jakub-onderka/php-parallel-lint", + "version": "v0.9.2", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Parallel-Lint.git", + "reference": "2ead2e4043ab125bee9554f356e0a86742c2d4fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Parallel-Lint/zipball/2ead2e4043ab125bee9554f356e0a86742c2d4fa", + "reference": "2ead2e4043ab125bee9554f356e0a86742c2d4fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "jakub-onderka/php-console-highlighter": "~0.3", + "nette/tester": "~1.3" + }, + "suggest": { + "jakub-onderka/php-console-highlighter": "Highlight syntax in code snippet" + }, + "bin": [ + "parallel-lint" + ], + "type": "library", + "autoload": { + "classmap": [ + "./" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "description": "This tool check syntax of PHP files about 20x faster than serial check.", + "homepage": "https://github.com/JakubOnderka/PHP-Parallel-Lint", + "time": "2015-12-15 10:42:16" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2015-02-03 12:10:50" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972", + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "~2.0", + "sebastian/comparator": "~1.1", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-02-15 07:46:21" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06 15:47:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12 18:03:57" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.26", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74", + "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-05-17 03:09:28" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-10-02 06:51:40" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-05-17 03:18:57" + }, + { + "name": "sebastian/exporter", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2015-06-21 07:55:53" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "1bcdf03b068a530ac1962ce671dead356eeba43b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1bcdf03b068a530ac1962ce671dead356eeba43b", + "reference": "1bcdf03b068a530ac1962ce671dead356eeba43b", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2016-04-03 22:58:34" + }, + { + "name": "symfony/yaml", + "version": "v3.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "0047c8366744a16de7516622c5b7355336afae96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96", + "reference": "0047c8366744a16de7516622c5b7355336afae96", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-03-04 07:55:57" + } + ], "aliases": [], "minimum-stability": "stable", "stability-flags": { "ckeditor/ckeditor": 20, - "mike42/escpos-php": 20 + "restler/framework": 5 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/htdocs/includes/ckeditor/ckeditor/CHANGES.md b/htdocs/includes/ckeditor/ckeditor/CHANGES.md index f751de99327..e48aaac4dc2 100644 --- a/htdocs/includes/ckeditor/ckeditor/CHANGES.md +++ b/htdocs/includes/ckeditor/ckeditor/CHANGES.md @@ -1,1065 +1,1080 @@ -CKEditor 4 Changelog -==================== - -## CKEditor 4.5.8 - -New Features: - -* [#12440](http://dev.ckeditor.com/ticket/12440): Added the [`config.colorButton_enableAutomatic`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-colorButton_enableAutomatic) option to allow hiding the "Automatic" option in the [color picker](http://ckeditor.com/addon/colorbutton). - -Fixed Issues: - -* [#10448](http://dev.ckeditor.com/ticket/10448): Fixed: Lack of scrollbar in the [right-to-left text direction](http://ckeditor.com/addon/bidi). -* [#12707](http://dev.ckeditor.com/ticket/12707): Fixed: The order of table elements does not comply with the HTML specification. -* [#13756](http://dev.ckeditor.com/ticket/13756): [Edge] Fixed: Context menus are cut-off. - -## CKEditor 4.5.7 - -New Features: - -* [#14327](http://dev.ckeditor.com/ticket/14327): Added Swiss German localization. Thanks to [Miro Grenda](https://twitter.com/mirogrenda)! - -Fixed Issues: - -* [#13816](http://dev.ckeditor.com/ticket/13816): Introduced a new strategy for Filling Character handling to avoid changes in DOM. This fixes the following issues: - * [#12727](http://dev.ckeditor.com/ticket/12727): [Blink] `IndexSizeError` when using the [Div Editing Area](http://ckeditor.com/addon/divarea) and [Content Templates](http://ckeditor.com/addon/templates) plugins. - * [#13377](http://dev.ckeditor.com/ticket/13377): [Widget](http://ckeditor.com/addon/widget) plugin issue when typing in Korean. - * [#13389](http://dev.ckeditor.com/ticket/13389): [Blink] [`editor.getData()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-getData) fails when the cursor is next to an `


` tag. - * [#13513](http://dev.ckeditor.com/ticket/13513): [Blink, WebKit] [Div Editing Area](http://ckeditor.com/addon/divarea) and [`editor.getData()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-getData) throw an error when an image is the only data in the editor. -* [#13884](http://dev.ckeditor.com/ticket/13884): [Firefox] Fixed: Copying and pasting a table results in just the first cell being pasted. -* [#14234](http://dev.ckeditor.com/ticket/14234): Fixed: URL input field is not marked as required in the [Media Embed](http://ckeditor.com/addon/embed) dialog. - -## CKEditor 4.5.6 - -New Features: - -* Introduced the [`CKEDITOR.tools.getCookie()`](http://docs.ckeditor.com/#!/api/CKEDITOR.tools-method-getCookie) and [`CKEDITOR.tools.setCookie()`](http://docs.ckeditor.com/#!/api/CKEDITOR.tools-method-setCookie) methods for accessing cookies. -* Introduced the [`CKEDITOR.tools.getCsrfToken()`](http://docs.ckeditor.com/#!/api/CKEDITOR.tools-method-getCsrfToken) method. The CSRF token is now automatically sent by the [File Browser](http://ckeditor.com/addon/filebrowser) and [File Tools](http://ckeditor.com/addon/filetools) plugins during file uploads. The server-side upload handlers may check it and use it to additionally secure the communication. - -Other Changes: - -* Updated [SCAYT](http://ckeditor.com/addon/scayt) (Spell Check As You Type): - - New features: - - CKEditor [Language](http://ckeditor.com/addon/language) plugin support. - - CKEditor [Placeholder](http://ckeditor.com/addon/placeholder) plugin support. - - [Drag&Drop](http://sdk.ckeditor.com/samples/fileupload.html) support. - - **Experimental** [GRAYT](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-grayt_autoStartup) (Grammar As You Type) functionality. - - Fixed issues: - * [#98](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/98): SCAYT affects dialog double-click. Fixed in SCAYT core. - * [#102](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/102): SCAYT core performance enhancements. - * [#104](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/104): SCAYT's spans leak into the clipboard and after pasting. - * [#105](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/105): A JavaScript error fired in case of multiple instances of CKEditor on one page. - * [#107](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/107): SCAYT should not check non-editable parts of content. - * [#108](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/108): Latest SCAYT copies the ID of the editor element to the iframe. - * SCAYT stops working when CKEditor [Undo plugin](http://ckeditor.com/addon/undo) not enabled. - * Issue with pasting SCAYT markup in CKEditor. - * SCAYT stops working after pressing the *Cancel* button in the WSC dialog. - -## CKEditor 4.5.5 - -Fixed Issues: - -* [#13887](https://dev.ckeditor.com/ticket/13887): Fixed: [Link](http://ckeditor.com/addon/link) plugin alters the `target` attribute value. Thanks to [SamZiemer](https://github.com/SamZiemer)! -* [#12189](http://dev.ckeditor.com/ticket/12189): Fixed: The [Link](http://ckeditor.com/addon/link) plugin dialog does not display the subject of email links if the subject parameter is not lowercase. -* [#9192](http://dev.ckeditor.com/ticket/9192): Fixed: An `undefined` string is appended to an email address added with the [Link](http://ckeditor.com/addon/link) plugin if subject and email body are empty and [`config.emailProtection`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-emailProtection) is set to `encode`. -* [#13790](https://dev.ckeditor.com/ticket/13790): Fixed: It is not possible to destroy the editor `