From c4e4e74301b89ed7fdbad17f476d565a1729d656 Mon Sep 17 00:00:00 2001 From: data Date: Tue, 9 Dec 2025 12:26:51 +0100 Subject: [PATCH 1/2] test --- .idea/.gitignore | 8 + .idea/modules.xml | 8 + .idea/php.xml | 19 + .idea/supplierlink3.iml | 8 + .idea/vcs.xml | 7 + COPYING | 621 ++++++++++++++++++ ChangeLog.md | 5 + README.md | 96 +++ admin/about.php | 118 ++++ admin/setup.php | 629 +++++++++++++++++++ bin/module_supplierlink3-1.0.zip | Bin 0 -> 43234 bytes bin/module_supplierlink3-1.1.zip | Bin 0 -> 43232 bytes build/buildzip.php | 316 ++++++++++ build/makepack-supplierlink3.conf | 11 + class/actions_supplierlink3.class.php | 451 +++++++++++++ class/actions_supplierlink3.class.php.back | 426 +++++++++++++ core/modules/modSupplierLink3.class.php | 571 +++++++++++++++++ core/modules/modSupplierLink3.class.php.back | 540 ++++++++++++++++ img/README.md | 14 + langs/en_US/supplierlink3.lang | 42 ++ lib/supplierlink3.lib.php | 85 +++ modulebuilder.txt | 3 + sql/dolibarr_allversions.sql | 3 + supplierlink3index.php | 259 ++++++++ 24 files changed, 4240 insertions(+) create mode 100755 .idea/.gitignore create mode 100755 .idea/modules.xml create mode 100755 .idea/php.xml create mode 100755 .idea/supplierlink3.iml create mode 100755 .idea/vcs.xml create mode 100755 COPYING create mode 100755 ChangeLog.md create mode 100755 README.md create mode 100755 admin/about.php create mode 100755 admin/setup.php create mode 100755 bin/module_supplierlink3-1.0.zip create mode 100755 bin/module_supplierlink3-1.1.zip create mode 100755 build/buildzip.php create mode 100755 build/makepack-supplierlink3.conf create mode 100755 class/actions_supplierlink3.class.php create mode 100755 class/actions_supplierlink3.class.php.back create mode 100755 core/modules/modSupplierLink3.class.php create mode 100755 core/modules/modSupplierLink3.class.php.back create mode 100755 img/README.md create mode 100755 langs/en_US/supplierlink3.lang create mode 100755 lib/supplierlink3.lib.php create mode 100755 modulebuilder.txt create mode 100755 sql/dolibarr_allversions.sql create mode 100755 supplierlink3index.php diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100755 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100755 index 0000000..7d345c0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100755 index 0000000..f324872 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/supplierlink3.iml b/.idea/supplierlink3.iml new file mode 100755 index 0000000..c956989 --- /dev/null +++ b/.idea/supplierlink3.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100755 index 0000000..a57048e --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/COPYING b/COPYING new file mode 100755 index 0000000..94a0453 --- /dev/null +++ b/COPYING @@ -0,0 +1,621 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100755 index 0000000..ede6a79 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,5 @@ +# CHANGELOG MODULE SUPPLIERLINK3 FOR [DOLIBARR ERP CRM](https://www.dolibarr.org) + +1.1 + +Initial version diff --git a/README.md b/README.md new file mode 100755 index 0000000..5725b14 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +# SUPPLIERLINK3 FOR [DOLIBARR ERP & CRM](https://www.dolibarr.org) + +## Features + +Description of the module... + + + +Other external modules are available on [Dolistore.com](https://www.dolistore.com). + +## Translations + +Translations can be completed manually by editing files in the module directories under `langs`. + + + + +## Installation + +Prerequisites: You must have Dolibarr ERP & CRM software installed. You can download it from [Dolistore.org](https://www.dolibarr.org). +You can also get a ready-to-use instance in the cloud from https://saas.dolibarr.org + + +### From the ZIP file and GUI interface + +If the module is a ready-to-deploy zip file, so with a name `module_xxx-version.zip` (e.g., when downloading it from a marketplace like [Dolistore](https://www.dolistore.com)), +go to menu `Home> Setup> Modules> Deploy external module` and upload the zip file. + + + + + +### Final steps + +Using your browser: + + - Log into Dolibarr as a super-administrator + - Go to "Setup"> "Modules" + - You should now be able to find and enable the module + + + +## Licenses + +### Main code + +GPLv3 or (at your option) any later version. See file COPYING for more information. + +### Documentation + +All texts and readme's are licensed under [GFDL](https://www.gnu.org/licenses/fdl-1.3.en.html). diff --git a/admin/about.php b/admin/about.php new file mode 100755 index 0000000..0be5fcd --- /dev/null +++ b/admin/about.php @@ -0,0 +1,118 @@ + + * Copyright (C) 2025 Eduard Wisch + * Copyright (C) 2024 Frédéric France + * + * 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 supplierlink3/admin/about.php + * \ingroup supplierlink3 + * \brief About page of module SupplierLink3. + */ + +// Load Dolibarr environment +$res = 0; +// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) +if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) { + $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; +} +// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME +$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; +$tmp2 = realpath(__FILE__); +$i = strlen($tmp) - 1; +$j = strlen($tmp2) - 1; +while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { + $i--; + $j--; +} +if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) { + $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php"; +} +if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) { + $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; +} +// Try main.inc.php using relative path +if (!$res && file_exists("../../main.inc.php")) { + $res = @include "../../main.inc.php"; +} +if (!$res && file_exists("../../../main.inc.php")) { + $res = @include "../../../main.inc.php"; +} +if (!$res) { + die("Include of main fails"); +} + +// Libraries +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once '../lib/supplierlink3.lib.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ + +// Translations +$langs->loadLangs(array("errors", "admin", "supplierlink3@supplierlink3")); + +// Access control +if (!$user->admin) { + accessforbidden(); +} + +// Parameters +$action = GETPOST('action', 'aZ09'); +$backtopage = GETPOST('backtopage', 'alpha'); + + +/* + * Actions + */ + +// None + + +/* + * View + */ + +$form = new Form($db); + +$help_url = ''; +$title = "SupplierLink3Setup"; + +llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-supplierlink3 page-admin_about'); + +// Subheader +$linkback = ''.$langs->trans("BackToModuleList").''; + +print load_fiche_titre($langs->trans($title), $linkback, 'title_setup'); + +// Configuration header +$head = supplierlink3AdminPrepareHead(); +print dol_get_fiche_head($head, 'about', $langs->trans($title), 0, 'supplierlink3@supplierlink3'); + +dol_include_once('/supplierlink3/core/modules/modSupplierLink3.class.php'); +$tmpmodule = new modSupplierLink3($db); +print $tmpmodule->getDescLong(); + +// Page end +print dol_get_fiche_end(); +llxFooter(); +$db->close(); diff --git a/admin/setup.php b/admin/setup.php new file mode 100755 index 0000000..cbd6a78 --- /dev/null +++ b/admin/setup.php @@ -0,0 +1,629 @@ + + * Copyright (C) 2024 Frédéric France + * Copyright (C) 2025 Eduard Wisch + * + * 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 supplierlink3/admin/setup.php + * \ingroup supplierlink3 + * \brief SupplierLink3 setup page. + */ + +// Load Dolibarr environment +$res = 0; +// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) +if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) { + $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; +} +// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME +$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; +$tmp2 = realpath(__FILE__); +$i = strlen($tmp) - 1; +$j = strlen($tmp2) - 1; +while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { + $i--; + $j--; +} +if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) { + $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php"; +} +if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) { + $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; +} +// Try main.inc.php using relative path +if (!$res && file_exists("../../main.inc.php")) { + $res = @include "../../main.inc.php"; +} +if (!$res && file_exists("../../../main.inc.php")) { + $res = @include "../../../main.inc.php"; +} +if (!$res) { + die("Include of main fails"); +} + +// Libraries +require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php"; +require_once '../lib/supplierlink3.lib.php'; +//require_once "../class/myclass.class.php"; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ + +// Translations +$langs->loadLangs(array("admin", "supplierlink3@supplierlink3")); + +// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context +/** @var HookManager $hookmanager */ +$hookmanager->initHooks(array('supplierlink3setup', 'globalsetup')); + +// Parameters +$action = GETPOST('action', 'aZ09'); +$backtopage = GETPOST('backtopage', 'alpha'); +$modulepart = GETPOST('modulepart', 'aZ09'); // Used by actions_setmoduleoptions.inc.php + +$value = GETPOST('value', 'alpha'); +$label = GETPOST('label', 'alpha'); +$scandir = GETPOST('scan_dir', 'alpha'); +$type = 'myobject'; + +$error = 0; +$setupnotempty = 0; + +// Access control +if (!$user->admin) { + accessforbidden(); +} + + +// Set this to 1 to use the factory to manage constants. Warning, the generated module will be compatible with version v15+ only +$useFormSetup = 1; + +if (!class_exists('FormSetup')) { + require_once DOL_DOCUMENT_ROOT.'/core/class/html.formsetup.class.php'; +} +$formSetup = new FormSetup($db); + +// Access control +if (!$user->admin) { + accessforbidden(); +} + + +// Enter here all parameters in your setup page + +// Setup conf for selection of an URL +$item = $formSetup->newItem('SUPPLIERLINK3_MYPARAM1'); +$item->fieldParams['isMandatory'] = 1; +$item->fieldAttr['placeholder'] = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://') . $_SERVER['HTTP_HOST']; +$item->cssClass = 'minwidth500'; + +// Setup conf for selection of a simple string input +$item = $formSetup->newItem('SUPPLIERLINK3_MYPARAM2'); +$item->defaultFieldValue = 'default value'; +$item->fieldAttr['placeholder'] = 'A placeholder here'; +$item->helpText = 'Tooltip text'; + +// Setup conf for selection of a simple textarea input but we replace the text of field title +$item = $formSetup->newItem('SUPPLIERLINK3_MYPARAM3'); +$item->nameText = $item->getNameText().' more html text '; + +// Setup conf for a selection of a Thirdparty +$item = $formSetup->newItem('SUPPLIERLINK3_MYPARAM4'); +$item->setAsThirdpartyType(); + +// Setup conf for a selection of a boolean +$formSetup->newItem('SUPPLIERLINK3_MYPARAM5')->setAsYesNo(); + +// Setup conf for a selection of an Email template of type thirdparty +$formSetup->newItem('SUPPLIERLINK3_MYPARAM6')->setAsEmailTemplate('thirdparty'); + +// Setup conf for a selection of a secured key +//$formSetup->newItem('SUPPLIERLINK3_MYPARAM7')->setAsSecureKey(); + +// Setup conf for a selection of a Product +$formSetup->newItem('SUPPLIERLINK3_MYPARAM8')->setAsProduct(); + +// Add a title for a new section +$formSetup->newItem('NewSection')->setAsTitle(); + +$TField = array( + 'test01' => $langs->trans('test01'), + 'test02' => $langs->trans('test02'), + 'test03' => $langs->trans('test03'), + 'test04' => $langs->trans('test04'), + 'test05' => $langs->trans('test05'), + 'test06' => $langs->trans('test06'), +); + +// Setup conf for a simple combo list +$formSetup->newItem('SUPPLIERLINK3_MYPARAM9')->setAsSelect($TField); + +// Setup conf for a multiselect combo list +$item = $formSetup->newItem('SUPPLIERLINK3_MYPARAM10'); +$item->setAsMultiSelect($TField); +$item->helpText = $langs->transnoentities('SUPPLIERLINK3_MYPARAM10'); + +// Setup conf for a category selection +$formSetup->newItem('SUPPLIERLINK3_CATEGORY_ID_XXX')->setAsCategory('product'); + +// Setup conf SUPPLIERLINK3_MYPARAM10 +$item = $formSetup->newItem('SUPPLIERLINK3_MYPARAM10'); +$item->setAsColor(); +$item->defaultFieldValue = '#FF0000'; +//$item->fieldValue = ''; +//$item->fieldAttr = array() ; // fields attribute only for compatible fields like input text +//$item->fieldOverride = false; // set this var to override field output will override $fieldInputOverride and $fieldOutputOverride too +//$item->fieldInputOverride = false; // set this var to override field input +//$item->fieldOutputOverride = false; // set this var to override field output + +$item = $formSetup->newItem('SUPPLIERLINK3_MYPARAM11')->setAsHtml(); +$item->nameText = $item->getNameText().' more html text '; +$item->fieldInputOverride = ''; +$item->helpText = $langs->transnoentities('HelpMessage'); +$item->cssClass = 'minwidth500'; + +$item = $formSetup->newItem('SUPPLIERLINK3_MYPARAM12'); +$item->fieldOverride = "Value forced, can't be modified"; +$item->cssClass = 'minwidth500'; + +//$item = $formSetup->newItem('SUPPLIERLINK3_MYPARAM13')->setAsDate(); // Not yet implemented + +// End of definition of parameters + + +$setupnotempty += count($formSetup->items); + + +$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); + +$moduledir = 'supplierlink3'; +$myTmpObjects = array(); +// TODO Scan list of objects to fill this array +$myTmpObjects['myobject'] = array('label' => 'MyObject', 'includerefgeneration' => 0, 'includedocgeneration' => 0, 'class' => 'MyObject'); + +$tmpobjectkey = GETPOST('object', 'aZ09'); +if ($tmpobjectkey && !array_key_exists($tmpobjectkey, $myTmpObjects)) { + accessforbidden('Bad value for object. Hack attempt ?'); +} + + +/* + * Actions + */ + +// For retrocompatibility Dolibarr < 15.0 +if (versioncompare(explode('.', DOL_VERSION), array(15)) < 0 && $action == 'update' && !empty($user->admin)) { + $formSetup->saveConfFromPost(); +} + +include DOL_DOCUMENT_ROOT.'/core/actions_setmoduleoptions.inc.php'; + +if ($action == 'updateMask') { + $maskconst = GETPOST('maskconst', 'aZ09'); + $maskvalue = GETPOST('maskvalue', 'alpha'); + + if ($maskconst && preg_match('/_MASK$/', $maskconst)) { + $res = dolibarr_set_const($db, $maskconst, $maskvalue, 'chaine', 0, '', $conf->entity); + if (!($res > 0)) { + $error++; + } + } + + if (!$error) { + setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); + } else { + setEventMessages($langs->trans("Error"), null, 'errors'); + } +} elseif ($action == 'specimen' && $tmpobjectkey) { + $modele = GETPOST('module', 'alpha'); + + $className = $myTmpObjects[$tmpobjectkey]['class']; + $tmpobject = new $className($db); + '@phan-var-force MyObject $tmpobject'; + $tmpobject->initAsSpecimen(); + + // Search template files + $file = ''; + $className = ''; + $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); + foreach ($dirmodels as $reldir) { + $file = dol_buildpath($reldir."core/modules/supplierlink3/doc/pdf_".$modele."_".strtolower($tmpobjectkey).".modules.php", 0); + if (file_exists($file)) { + $className = "pdf_".$modele."_".strtolower($tmpobjectkey); + break; + } + } + + if ($className !== '') { + require_once $file; + + $module = new $className($db); + '@phan-var-force ModelePDFMyObject $module'; + + '@phan-var-force ModelePDFMyObject $module'; + + if ($module->write_file($tmpobject, $langs) > 0) { + header("Location: ".DOL_URL_ROOT."/document.php?modulepart=supplierlink3-".strtolower($tmpobjectkey)."&file=SPECIMEN.pdf"); + return; + } else { + setEventMessages($module->error, null, 'errors'); + dol_syslog($module->error, LOG_ERR); + } + } else { + setEventMessages($langs->trans("ErrorModuleNotFound"), null, 'errors'); + dol_syslog($langs->trans("ErrorModuleNotFound"), LOG_ERR); + } +} elseif ($action == 'setmod') { + // TODO Check if numbering module chosen can be activated by calling method canBeActivated + if (!empty($tmpobjectkey)) { + $constforval = 'SUPPLIERLINK3_'.strtoupper($tmpobjectkey)."_ADDON"; + dolibarr_set_const($db, $constforval, $value, 'chaine', 0, '', $conf->entity); + } +} elseif ($action == 'set') { + // Activate a model + $ret = addDocumentModel($value, $type, $label, $scandir); +} elseif ($action == 'del') { + $ret = delDocumentModel($value, $type); + if ($ret > 0) { + if (!empty($tmpobjectkey)) { + $constforval = 'SUPPLIERLINK3_'.strtoupper($tmpobjectkey).'_ADDON_PDF'; + if (getDolGlobalString($constforval) == "$value") { + dolibarr_del_const($db, $constforval, $conf->entity); + } + } + } +} elseif ($action == 'setdoc') { + // Set or unset default model + if (!empty($tmpobjectkey)) { + $constforval = 'SUPPLIERLINK3_'.strtoupper($tmpobjectkey).'_ADDON_PDF'; + if (dolibarr_set_const($db, $constforval, $value, 'chaine', 0, '', $conf->entity)) { + // The constant that was read before the new set + // We therefore requires a variable to have a coherent view + $conf->global->{$constforval} = $value; + } + + // We disable/enable the document template (into llx_document_model table) + $ret = delDocumentModel($value, $type); + if ($ret > 0) { + $ret = addDocumentModel($value, $type, $label, $scandir); + } + } +} elseif ($action == 'unsetdoc') { + if (!empty($tmpobjectkey)) { + $constforval = 'SUPPLIERLINK3_'.strtoupper($tmpobjectkey).'_ADDON_PDF'; + dolibarr_del_const($db, $constforval, $conf->entity); + } +} + +$action = 'edit'; + + +/* + * View + */ + +$form = new Form($db); + +$help_url = ''; +$title = "SupplierLink3Setup"; + +llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-supplierlink3 page-admin'); + +// Subheader +$linkback = ''.$langs->trans("BackToModuleList").''; + +print load_fiche_titre($langs->trans($title), $linkback, 'title_setup'); + +// Configuration header +$head = supplierlink3AdminPrepareHead(); +print dol_get_fiche_head($head, 'settings', $langs->trans($title), -1, "supplierlink3@supplierlink3"); + +// Setup page goes here +echo ''.$langs->trans("SupplierLink3SetupPage").'

'; + + +/*if ($action == 'edit') { + print $formSetup->generateOutput(true); + print '
'; + } elseif (!empty($formSetup->items)) { + print $formSetup->generateOutput(); + print '
'; + print ''.$langs->trans("Modify").''; + print '
'; + } + */ +if (!empty($formSetup->items)) { + print $formSetup->generateOutput(true); + print '
'; +} + + +foreach ($myTmpObjects as $myTmpObjectKey => $myTmpObjectArray) { + if (!empty($myTmpObjectArray['includerefgeneration'])) { + // Numbering models + + $setupnotempty++; + + print load_fiche_titre($langs->trans("NumberingModules", $myTmpObjectArray['label']), '', ''); + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''."\n"; + + clearstatcache(); + + foreach ($dirmodels as $reldir) { + $dir = dol_buildpath($reldir."core/modules/".$moduledir); + + if (is_dir($dir)) { + $handle = opendir($dir); + if (is_resource($handle)) { + while (($file = readdir($handle)) !== false) { + if (strpos($file, 'mod_'.strtolower($myTmpObjectKey).'_') === 0 && substr($file, dol_strlen($file) - 3, 3) == 'php') { + $file = substr($file, 0, dol_strlen($file) - 4); + + require_once $dir.'/'.$file.'.php'; + + $module = new $file($db); + '@phan-var-force ModeleNumRefMyObject $module'; + + // Show modules according to features level + if ($module->version == 'development' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2) { + continue; + } + if ($module->version == 'experimental' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 1) { + continue; + } + + if ($module->isEnabled()) { + dol_include_once('/'.$moduledir.'/class/'.strtolower($myTmpObjectKey).'.class.php'); + + print ''; + + // Show example of numbering model + print ''."\n"; + + print ''; + + $className = $myTmpObjectArray['class']; + $mytmpinstance = new $className($db); + '@phan-var-force MyObject $mytmpinstance'; + $mytmpinstance->initAsSpecimen(); + + // Info + $htmltooltip = ''; + $htmltooltip .= ''.$langs->trans("Version").': '.$module->getVersion().'
'; + + $nextval = $module->getNextValue($mytmpinstance); + if ("$nextval" != $langs->trans("NotAvailable")) { // Keep " on nextval + $htmltooltip .= ''.$langs->trans("NextValue").': '; + if ($nextval) { + if (preg_match('/^Error/', $nextval) || $nextval == 'NotConfigured') { + $nextval = $langs->trans($nextval); + } + $htmltooltip .= $nextval.'
'; + } else { + $htmltooltip .= $langs->trans($module->error).'
'; + } + } + + print ''; + + print "\n"; + } + } + } + closedir($handle); + } + } + } + print "
'.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Example").''.$langs->trans("Status").''.$langs->trans("ShortInfo").'
'.$module->getName($langs)."\n"; + print $module->info($langs); + print ''; + $tmp = $module->getExample(); + if (preg_match('/^Error/', $tmp)) { + $langs->load("errors"); + print '
'.$langs->trans($tmp).'
'; + } elseif ($tmp == 'NotConfigured') { + print $langs->trans($tmp); + } else { + print $tmp; + } + print '
'; + $constforvar = 'SUPPLIERLINK3_'.strtoupper($myTmpObjectKey).'_ADDON'; + $defaultifnotset = 'thevaluetousebydefault'; + $activenumberingmodel = getDolGlobalString($constforvar, $defaultifnotset); + if ($activenumberingmodel == $file) { + print img_picto($langs->trans("Activated"), 'switch_on'); + } else { + print ''; + print img_picto($langs->trans("Disabled"), 'switch_off'); + print ''; + } + print ''; + print $form->textwithpicto('', $htmltooltip, 1, 'info'); + print '

\n"; + } + + if (!empty($myTmpObjectArray['includedocgeneration'])) { + /* + * Document templates generators + */ + $setupnotempty++; + $type = strtolower($myTmpObjectKey); + + print load_fiche_titre($langs->trans("DocumentModules", $myTmpObjectKey), '', ''); + + // Load array def with activated templates + $def = array(); + $sql = "SELECT nom"; + $sql .= " FROM ".$db->prefix()."document_model"; + $sql .= " WHERE type = '".$db->escape($type)."'"; + $sql .= " AND entity = ".$conf->entity; + $resql = $db->query($sql); + if ($resql) { + $i = 0; + $num_rows = $db->num_rows($resql); + while ($i < $num_rows) { + $array = $db->fetch_array($resql); + array_push($def, $array[0]); + $i++; + } + } else { + dol_print_error($db); + } + + print ''."\n"; + print ''."\n"; + print ''; + print ''; + print '\n"; + print '\n"; + print ''; + print ''; + print "\n"; + + clearstatcache(); + + foreach ($dirmodels as $reldir) { + foreach (array('', '/doc') as $valdir) { + $realpath = $reldir."core/modules/".$moduledir.$valdir; + $dir = dol_buildpath($realpath); + + if (is_dir($dir)) { + $handle = opendir($dir); + if (is_resource($handle)) { + $filelist = array(); + while (($file = readdir($handle)) !== false) { + $filelist[] = $file; + } + closedir($handle); + arsort($filelist); + + foreach ($filelist as $file) { + if (preg_match('/\.modules\.php$/i', $file) && preg_match('/^(pdf_|doc_)/', $file)) { + if (file_exists($dir.'/'.$file)) { + $name = substr($file, 4, dol_strlen($file) - 16); + $className = substr($file, 0, dol_strlen($file) - 12); + + require_once $dir.'/'.$file; + $module = new $className($db); + '@phan-var-force ModelePDFMyObject $module'; + + $modulequalified = 1; + if ($module->version == 'development' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2) { + $modulequalified = 0; + } + if ($module->version == 'experimental' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 1) { + $modulequalified = 0; + } + + if ($modulequalified) { + print ''; + + // Active + if (in_array($name, $def)) { + print ''; + } else { + print '"; + } + + // Default + print ''; + + // Info + $htmltooltip = ''.$langs->trans("Name").': '.$module->name; + $htmltooltip .= '
'.$langs->trans("Type").': '.($module->type ? $module->type : $langs->trans("Unknown")); + if ($module->type == 'pdf') { + $htmltooltip .= '
'.$langs->trans("Width").'/'.$langs->trans("Height").': '.$module->page_largeur.'/'.$module->page_hauteur; + } + $htmltooltip .= '
'.$langs->trans("Path").': '.preg_replace('/^\//', '', $realpath).'/'.$file; + + $htmltooltip .= '

'.$langs->trans("FeaturesSupported").':'; + $htmltooltip .= '
'.$langs->trans("Logo").': '.yn($module->option_logo, 1, 1); + $htmltooltip .= '
'.$langs->trans("MultiLanguage").': '.yn($module->option_multilang, 1, 1); + + print ''; + + // Preview + print ''; + + print "\n"; + } + } + } + } + } + } + } + } + + print '
'.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Status")."'.$langs->trans("Default")."'.$langs->trans("ShortInfo").''.$langs->trans("Preview").'
'; + print(empty($module->name) ? $name : $module->name); + print "\n"; + if (method_exists($module, 'info')) { + print $module->info($langs); // @phan-suppress-current-line PhanUndeclaredMethod + } else { + print $module->description; + } + print ''."\n"; + print ''; + print img_picto($langs->trans("Enabled"), 'switch_on'); + print ''; + print ''."\n"; + print 'scandir).'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"), 'switch_off').''; + print "'; + $constforvar = 'SUPPLIERLINK3_'.strtoupper($myTmpObjectKey).'_ADDON_PDF'; + if (getDolGlobalString($constforvar) == $name) { + //print img_picto($langs->trans("Default"), 'on'); + // Even if choice is the default value, we allow to disable it. Replace this with previous line if you need to disable unset + print 'scandir).'&label='.urlencode($module->name).'&type='.urlencode($type).'" alt="'.$langs->trans("Disable").'">'.img_picto($langs->trans("Enabled"), 'on').''; + } else { + print 'scandir).'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"), 'off').''; + } + print ''; + print $form->textwithpicto('', $htmltooltip, 1, 'info'); + print ''; + if ($module->type == 'pdf') { + $newname = preg_replace('/_'.preg_quote(strtolower($myTmpObjectKey), '/').'/', '', $name); + print ''.img_object($langs->trans("Preview"), 'pdf').''; + } else { + print img_object($langs->transnoentitiesnoconv("PreviewNotAvailable"), 'generic'); + } + print '
'; + } +} + +if (empty($setupnotempty)) { + print '
'.$langs->trans("NothingToSetup"); +} + +// Page end +print dol_get_fiche_end(); + +llxFooter(); +$db->close(); diff --git a/bin/module_supplierlink3-1.0.zip b/bin/module_supplierlink3-1.0.zip new file mode 100755 index 0000000000000000000000000000000000000000..8e79792ef1c996cf35bd233ebffde8b1508c070d GIT binary patch literal 43234 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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* + The goal of that php CLI script is to make zip package of your module + as an alternative to web "build zip" or "perl script makepack" +*/ + +// ============================================= configuration + +/** + * list of files & dirs of your module + * + * @var string[] + */ +$listOfModuleContent = [ + 'admin', + 'ajax', + 'backport', + 'class', + 'css', + 'COPYING', + 'core', + 'img', + 'js', + 'langs', + 'lib', + 'sql', + 'tpl', + '*.md', + '*.json', + '*.php', + 'modulebuilder.txt', +]; + +/** + * if you want to exclude some files from your zip + * + * @var string[] + */ +$exclude_list = [ + '/^.git$/', + '/.*js.map/', + '/DEV.md/' +]; + +// ============================================= end of configuration + +/** + * auto detect module name and version from file name + * + * @return (string|string)[] module name and module version + */ +function detectModule() +{ + $name = $version = ""; + $tab = glob("core/modules/mod*.class.php"); + if (count($tab) == 0) { + echo "[fail] Error on auto detect data : there is no mod*.class.php file into core/modules dir\n"; + exit(-1); + } + if (count($tab) == 1) { + $file = $tab[0]; + $pattern = "/.*mod(?.*)\.class\.php/"; + if (preg_match_all($pattern, $file, $matches)) { + $name = strtolower(reset($matches['mod'])); + } + + echo "extract data from $file\n"; + if (!file_exists($file) || $name == "") { + echo "[fail] Error on auto detect data\n"; + exit(-2); + } + } else { + echo "[fail] Error there is more than one mod*.class.php file into core/modules dir\n"; + exit(-3); + } + + //extract version from file + $contents = file_get_contents($file); + $pattern = "/^.*this->version\s*=\s*'(?.*)'\s*;.*\$/m"; + + // search, and store all matching occurrences in $matches + if (preg_match_all($pattern, $contents, $matches)) { + $version = reset($matches['version']); + } + + if (version_compare($version, '0.0.1', '>=') != 1) { + echo "[fail] Error auto extract version fail\n"; + exit(-4); + } + + echo "module name = $name, version = $version\n"; + return [(string) $name, (string) $version]; +} + +/** + * delete recursively a directory + * + * @param string $dir dir path to delete + * + * @return bool true on success or false on failure. + */ +function delTree($dir) +{ + $files = array_diff(scandir($dir), array('.', '..')); + foreach ($files as $file) { + (is_dir("$dir/$file")) ? delTree("$dir/$file") : secureUnlink("$dir/$file"); + } + return rmdir($dir); +} + + +/** + * do a secure delete file/dir with double check + * (don't trust unlink return) + * + * @param string $path full path to delete + * + * @return bool true on success ($path does not exists at the end of process), else exit + */ +function secureUnlink($path) +{ + if (file_exists($path)) { + if (unlink($path)) { + //then check if really deleted + clearstatcache(); + if (file_exists($path)) { // @phpstan-ignore-line + echo "[fail] unlink of $path fail !\n"; + exit(-5); + } + } else { + echo "[fail] unlink of $path fail !\n"; + exit(-6); + } + } + return true; +} + +/** + * create a directory and check if dir exists + * + * @param string $path path to make + * + * @return bool true on success ($path exists at the end of process), else exit + */ +function mkdirAndCheck($path) +{ + if (mkdir($path)) { + clearstatcache(); + if (is_dir($path)) { + return true; + } + } + echo "[fail] Error on $path (mkdir)\n"; + exit(7); +} + +/** + * check if that filename is concerned by exclude filter + * + * @param string $filename file name to check + * + * @return bool true if file is in excluded list + */ +function is_excluded($filename) +{ + global $exclude_list; + $count = 0; + $notused = preg_filter($exclude_list, '1', $filename, -1, $count); + if ($count > 0) { + echo " - exclude $filename\n"; + return true; + } + return false; +} + +/** + * recursive copy files & dirs + * + * @param string $src source dir + * @param string $dst target dir + * + * @return bool true on success or false on failure. + */ +function rcopy($src, $dst) +{ + if (is_dir($src)) { + // Make the destination directory if not exist + mkdirAndCheck($dst); + // open the source directory + $dir = opendir($src); + + // Loop through the files in source directory + while ($file = readdir($dir)) { + if (($file != '.') && ($file != '..')) { + if (is_dir($src . '/' . $file)) { + // Recursively calling custom copy function + // for sub directory + if (!rcopy($src . '/' . $file, $dst . '/' . $file)) { + return false; + } + } else { + if (!is_excluded($file)) { + if (!copy($src . '/' . $file, $dst . '/' . $file)) { + return false; + } + } + } + } + } + closedir($dir); + } elseif (is_file($src)) { + if (!is_excluded($src)) { + if (!copy($src, $dst)) { + return false; + } + } + } + return true; +} + +/** + * build a zip file with only php code and no external depends + * on "zip" exec for example + * + * @param string $folder folder to use as zip root + * @param ZipArchive $zip zip object (ZipArchive) + * @param string $root relative root path into the zip + * + * @return bool true on success or false on failure. + */ +function zipDir($folder, &$zip, $root = "") +{ + foreach (new \DirectoryIterator($folder) as $f) { + if ($f->isDot()) { + continue; + } //skip . .. + $src = $folder . '/' . $f; + $dst = substr($f->getPathname(), strlen($root)); + if ($f->isDir()) { + if ($zip->addEmptyDir($dst)) { + if (zipDir($src, $zip, $root)) { + continue; + } else { + return false; + } + } else { + return false; + } + } + if ($f->isFile()) { + if (! $zip->addFile($src, $dst)) { + return false; + } + } + } + return true; +} + +/** + * main part of script + */ + +list($mod, $version) = detectModule(); +$outzip = sys_get_temp_dir() . "/module_" . $mod . "-" . $version . ".zip"; +if (file_exists($outzip)) { + secureUnlink($outzip); +} + +//copy all sources into system temp directory +$tmpdir = tempnam(sys_get_temp_dir(), $mod . "-module"); +secureUnlink($tmpdir); +mkdirAndCheck($tmpdir); +$dst = $tmpdir . "/" . $mod; +mkdirAndCheck($dst); + +foreach ($listOfModuleContent as $moduleContent) { + foreach (glob($moduleContent) as $entry) { + if (!rcopy($entry, $dst . '/' . $entry)) { + echo "[fail] Error on copy " . $entry . " to " . $dst . "/" . $entry . "\n"; + echo "Please take time to analyze the problem and fix the bug\n"; + exit(-8); + } + } +} + +$z = new ZipArchive(); +$z->open($outzip, ZIPARCHIVE::CREATE); +zipDir($tmpdir, $z, $tmpdir . '/'); +$z->close(); +delTree($tmpdir); +if (file_exists($outzip)) { + echo "[success] module archive is ready : $outzip ...\n"; +} else { + echo "[fail] build zip error\n"; + exit(-9); +} diff --git a/build/makepack-supplierlink3.conf b/build/makepack-supplierlink3.conf new file mode 100755 index 0000000..16dc1e7 --- /dev/null +++ b/build/makepack-supplierlink3.conf @@ -0,0 +1,11 @@ +# Your module name here +# +# Goal: Goal of module +# Version: +# Author: Copyright - +# License: GPLv3 +# Install: Just unpack content of module package in Dolibarr directory. +# Setup: Go on Dolibarr setup - modules to enable module. +# +# Files in module +mymodule/ \ No newline at end of file diff --git a/class/actions_supplierlink3.class.php b/class/actions_supplierlink3.class.php new file mode 100755 index 0000000..adcb7bd --- /dev/null +++ b/class/actions_supplierlink3.class.php @@ -0,0 +1,451 @@ + + * Copyright (C) 2025 Eduard Wisch + * + * 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 supplierlink3/class/actions_supplierlink3.class.php + * \ingroup supplierlink3 + * \brief Example hook overload. + * + * TODO: Write detailed description here. + */ + +require_once DOL_DOCUMENT_ROOT.'/core/class/commonhookactions.class.php'; + +/** + * Class ActionsSupplierLink3 + */ +class ActionsSupplierLink3 extends CommonHookActions +{ + /** + * @var DoliDB Database handler. + */ + public $db; + + /** + * @var string Error code (or message) + */ + public $error = ''; + + /** + * @var string[] Errors + */ + public $errors = array(); + + + /** + * @var mixed[] Hook results. Propagated to $hookmanager->resArray for later reuse + */ + public $results = array(); + + /** + * @var ?string String displayed by executeHook() immediately after return + */ + public $resprints; + + /** + * @var int Priority of hook (50 is used if value is not defined) + */ + public $priority; + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + $this->db = $db; + } + + + /** + * Execute action + * + * @param array $parameters Array of parameters + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param string $action 'add', 'update', 'view' + * @return int Return integer <0 if KO, + * =0 if OK but we want to process standard actions too, + * >0 if OK and we want to replace standard actions. + */ + public function getNomUrl($parameters, &$object, &$action) + { + global $db, $langs, $conf, $user; + $this->resprints = ''; + return 0; + } + + /** + * Overload the doActions function : replacing the parent's function with the one below + * + * @param array $parameters Hook metadata (context, etc...) + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param ?string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer < 0 on error, 0 on success, 1 to replace standard code + */ + public function doActions($parameters, &$object, &$action, $hookmanager) + { + $error = 0; // Error counter + + /* print_r($parameters); print_r($object); echo "action: " . $action; */ + if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { // do something only for the context 'somecontext1' or 'somecontext2' + // Do what you want here... + // You can for example load and use call global vars like $fieldstosearchall to overwrite them, or update the database depending on $action and GETPOST values. + + if (!$error) { + $this->results = array('myreturn' => 999); + $this->resprints = 'A text to show'; + return 0; // or return 1 to replace standard code + } else { + $this->errors[] = 'Error message'; + return -1; + } + } + + return 0; + } + + + /** + * Overload the doMassActions function : replacing the parent's function with the one below + * + * @param array $parameters Hook metadata (context, etc...) + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param ?string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer < 0 on error, 0 on success, 1 to replace standard code + */ + public function doMassActions($parameters, &$object, &$action, $hookmanager) + { + global $conf, $user, $langs; + + $error = 0; // Error counter + + /* print_r($parameters); print_r($object); echo "action: " . $action; */ + if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { // do something only for the context 'somecontext1' or 'somecontext2' + // @phan-suppress-next-line PhanPluginEmptyStatementForeachLoop + foreach ($parameters['toselect'] as $objectid) { + // Do action on each object id + } + + if (!$error) { + $this->results = array('myreturn' => 999); + $this->resprints = 'A text to show'; + return 0; // or return 1 to replace standard code + } else { + $this->errors[] = 'Error message'; + return -1; + } + } + + return 0; + } + + + /** + * Overload the addMoreMassActions function : replacing the parent's function with the one below + * + * @param array $parameters Hook metadata (context, etc...) + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param ?string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer < 0 on error, 0 on success, 1 to replace standard code + */ + public function addMoreMassActions($parameters, &$object, &$action, $hookmanager) + { + global $conf, $user, $langs; + + $error = 0; // Error counter + $disabled = 1; + + /* print_r($parameters); print_r($object); echo "action: " . $action; */ + if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { // do something only for the context 'somecontext1' or 'somecontext2' + $this->resprints = ''; + } + + if (!$error) { + return 0; // or return 1 to replace standard code + } else { + $this->errors[] = 'Error message'; + return -1; + } + } + + + + /** + * Execute action before PDF (document) creation + * + * @param array $parameters Array of parameters + * @param CommonObject $object Object output on PDF + * @param string $action 'add', 'update', 'view' + * @return int Return integer <0 if KO, + * =0 if OK but we want to process standard actions too, + * >0 if OK and we want to replace standard actions. + */ + public function beforePDFCreation($parameters, &$object, &$action) + { + global $conf, $user, $langs; + global $hookmanager; + + $outputlangs = $langs; + + $ret = 0; + $deltemp = array(); + dol_syslog(get_class($this).'::executeHooks action='.$action); + + /* print_r($parameters); print_r($object); echo "action: " . $action; */ + // @phan-suppress-next-line PhanPluginEmptyStatementIf + if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { + // do something only for the context 'somecontext1' or 'somecontext2' + } + + return $ret; + } + + /** + * Execute action after PDF (document) creation + * + * @param array $parameters Array of parameters + * @param CommonDocGenerator $pdfhandler PDF builder handler + * @param string $action 'add', 'update', 'view' + * @return int Return integer <0 if KO, + * =0 if OK but we want to process standard actions too, + * >0 if OK and we want to replace standard actions. + */ + public function afterPDFCreation($parameters, &$pdfhandler, &$action) + { + global $conf, $user, $langs; + global $hookmanager; + + $outputlangs = $langs; + + $ret = 0; + $deltemp = array(); + dol_syslog(get_class($this).'::executeHooks action='.$action); + + /* print_r($parameters); print_r($object); echo "action: " . $action; */ + // @phan-suppress-next-line PhanPluginEmptyStatementIf + if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { + // do something only for the context 'somecontext1' or 'somecontext2' + } + + return $ret; + } + + + + /** + * Overload the loadDataForCustomReports function : returns data to complete the customreport tool + * + * @param array $parameters Hook metadata (context, etc...) + * @param ?string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer < 0 on error, 0 on success, 1 to replace standard code + */ + public function loadDataForCustomReports($parameters, &$action, $hookmanager) + { + global $langs; + + $langs->load("supplierlink3@supplierlink3"); + + $this->results = array(); + + $head = array(); + $h = 0; + + if ($parameters['tabfamily'] == 'supplierlink3') { + $head[$h][0] = dol_buildpath('/module/index.php', 1); + $head[$h][1] = $langs->trans("Home"); + $head[$h][2] = 'home'; + $h++; + + $this->results['title'] = $langs->trans("SupplierLink3"); + $this->results['picto'] = 'supplierlink3@supplierlink3'; + } + + $head[$h][0] = 'customreports.php?objecttype='.$parameters['objecttype'].(empty($parameters['tabfamily']) ? '' : '&tabfamily='.$parameters['tabfamily']); + $head[$h][1] = $langs->trans("CustomReports"); + $head[$h][2] = 'customreports'; + + $this->results['head'] = $head; + + $arrayoftypes = array(); + //$arrayoftypes['supplierlink3_myobject'] = array('label' => 'MyObject', 'picto'=>'myobject@supplierlink3', 'ObjectClassName' => 'MyObject', 'enabled' => isModEnabled('supplierlink3'), 'ClassPath' => "/supplierlink3/class/myobject.class.php", 'langs'=>'supplierlink3@supplierlink3') + + $this->results['arrayoftype'] = $arrayoftypes; + + return 0; + } + + + + /** + * Overload the restrictedArea function : check permission on an object + * + * @param array $parameters Hook metadata (context, etc...) + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer <0 if KO, + * =0 if OK but we want to process standard actions too, + * >0 if OK and we want to replace standard actions. + */ + public function restrictedArea($parameters, $object, &$action, $hookmanager) + { + global $user; + + if ($parameters['features'] == 'myobject') { + if ($user->hasRight('supplierlink3', 'myobject', 'read')) { + $this->results['result'] = 1; + return 1; + } else { + $this->results['result'] = 0; + return 1; + } + } + + return 0; + } + + /** + * Execute action completeTabsHead + * + * @param array $parameters Array of parameters + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param string $action 'add', 'update', 'view' + * @param Hookmanager $hookmanager Hookmanager + * @return int Return integer <0 if KO, + * =0 if OK but we want to process standard actions too, + * >0 if OK and we want to replace standard actions. + */ + public function completeTabsHead(&$parameters, &$object, &$action, $hookmanager) + { + global $langs, $conf, $user; + + if (!isset($parameters['object']->element)) { + return 0; + } + if ($parameters['mode'] == 'remove') { + // used to make some tabs removed + return 0; + } elseif ($parameters['mode'] == 'add') { + $langs->load('supplierlink3@supplierlink3'); + // used when we want to add some tabs + $counter = count($parameters['head']); + $element = $parameters['object']->element; + $id = $parameters['object']->id; + // verifier le type d'onglet comme member_stats où ça ne doit pas apparaitre + // if (in_array($element, ['societe', 'member', 'contrat', 'fichinter', 'project', 'propal', 'commande', 'facture', 'order_supplier', 'invoice_supplier'])) { + if (in_array($element, ['context1', 'context2'])) { + $datacount = 0; + + $parameters['head'][$counter][0] = dol_buildpath('/supplierlink3/supplierlink3_tab.php', 1) . '?id=' . $id . '&module='.$element; + $parameters['head'][$counter][1] = $langs->trans('SupplierLink3Tab'); + if ($datacount > 0) { + $parameters['head'][$counter][1] .= '' . $datacount . ''; + } + $parameters['head'][$counter][2] = 'supplierlink3emails'; + $counter++; + } + if ($counter > 0 && (int) DOL_VERSION < 14) { // @phpstan-ignore-line + $this->results = $parameters['head']; + // return 1 to replace standard code + return 1; + } else { + // From V14 onwards, $parameters['head'] is modifiable by reference + return 0; + } + } else { + // Bad value for $parameters['mode'] + return -1; + } + } + + + /** + * Overload the showLinkToObjectBlock function : add or replace array of object linkable + * + * @param array $parameters Hook metadata (context, etc...) + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param ?string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer < 0 on error, 0 on success, 1 to replace standard code + */ + public function showLinkToObjectBlock($parameters, &$object, &$action, $hookmanager) + { + $myobject = new MyObject($object->db); + $this->results = array('myobject@supplierlink3' => array( + 'enabled' => isModEnabled('supplierlink3'), + 'perms' => 1, + 'label' => 'LinkToMyObject', + 'sql' => "SELECT t.rowid, t.ref, t.ref as 'name' FROM " . $this->db->prefix() . $myobject->table_element. " as t "),); + + return 1; + } + /* Add other hook methods here... */ + public function printObjectLine($parameters, &$object, &$action, $hookmanager) + { + if ($parameters['currentcontext'] != 'ordersuppliercard') { + return 0; + } + + $line = $parameters['line']; + + // Lieferanten-ID aus dem Bestell-Objekt holen, nicht aus der Zeile! + if (!empty($object->socid)) { + $fk_supplier = $object->socid; + + // Extrafeld-Wert vom Lieferanten laden + $sql = "SELECT shop_url"; + $sql .= " FROM ".MAIN_DB_PREFIX."societe_extrafields"; + $sql .= " WHERE fk_object = ".(int)$fk_supplier; + + $resql = $this->db->query($sql); + + if ($resql && $this->db->num_rows($resql) > 0) { + $obj = $this->db->fetch_object($resql); + + // Prüfen ob shop_url gesetzt ist + if (isset($obj->shop_url) && $obj->shop_url !== '' && $obj->shop_url !== null) { + + $shop_url = trim($obj->shop_url); + + // Nur wenn die URL nicht leer ist + if (!empty($shop_url)) { + + // Artikelnummer an die URL anhängen + $full_url = rtrim($shop_url, '/') . '/' . $line->ref_fourn; + + // Link erstellen + $newref = ''; + $newref .= $line->ref_fourn; + $newref .= ' '; + $newref .= ''; + + $line->ref_fourn = $newref; + } + } + } + } + + return 0; + } +} diff --git a/class/actions_supplierlink3.class.php.back b/class/actions_supplierlink3.class.php.back new file mode 100755 index 0000000..e9533a4 --- /dev/null +++ b/class/actions_supplierlink3.class.php.back @@ -0,0 +1,426 @@ + + * Copyright (C) 2025 Eduard Wisch + * + * 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 supplierlink3/class/actions_supplierlink3.class.php + * \ingroup supplierlink3 + * \brief Example hook overload. + * + * TODO: Write detailed description here. + */ + +require_once DOL_DOCUMENT_ROOT.'/core/class/commonhookactions.class.php'; + +/** + * Class ActionsSupplierLink3 + */ +class ActionsSupplierLink3 extends CommonHookActions +{ + /** + * @var DoliDB Database handler. + */ + public $db; + + /** + * @var string Error code (or message) + */ + public $error = ''; + + /** + * @var string[] Errors + */ + public $errors = array(); + + + /** + * @var mixed[] Hook results. Propagated to $hookmanager->resArray for later reuse + */ + public $results = array(); + + /** + * @var ?string String displayed by executeHook() immediately after return + */ + public $resprints; + + /** + * @var int Priority of hook (50 is used if value is not defined) + */ + public $priority; + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + $this->db = $db; + } + + + /** + * Execute action + * + * @param array $parameters Array of parameters + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param string $action 'add', 'update', 'view' + * @return int Return integer <0 if KO, + * =0 if OK but we want to process standard actions too, + * >0 if OK and we want to replace standard actions. + */ + public function getNomUrl($parameters, &$object, &$action) + { + global $db, $langs, $conf, $user; + $this->resprints = ''; + return 0; + } + + /** + * Overload the doActions function : replacing the parent's function with the one below + * + * @param array $parameters Hook metadata (context, etc...) + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param ?string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer < 0 on error, 0 on success, 1 to replace standard code + */ + public function doActions($parameters, &$object, &$action, $hookmanager) + { + $error = 0; // Error counter + + /* print_r($parameters); print_r($object); echo "action: " . $action; */ + if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { // do something only for the context 'somecontext1' or 'somecontext2' + // Do what you want here... + // You can for example load and use call global vars like $fieldstosearchall to overwrite them, or update the database depending on $action and GETPOST values. + + if (!$error) { + $this->results = array('myreturn' => 999); + $this->resprints = 'A text to show'; + return 0; // or return 1 to replace standard code + } else { + $this->errors[] = 'Error message'; + return -1; + } + } + + return 0; + } + + + /** + * Overload the doMassActions function : replacing the parent's function with the one below + * + * @param array $parameters Hook metadata (context, etc...) + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param ?string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer < 0 on error, 0 on success, 1 to replace standard code + */ + public function doMassActions($parameters, &$object, &$action, $hookmanager) + { + global $conf, $user, $langs; + + $error = 0; // Error counter + + /* print_r($parameters); print_r($object); echo "action: " . $action; */ + if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { // do something only for the context 'somecontext1' or 'somecontext2' + // @phan-suppress-next-line PhanPluginEmptyStatementForeachLoop + foreach ($parameters['toselect'] as $objectid) { + // Do action on each object id + } + + if (!$error) { + $this->results = array('myreturn' => 999); + $this->resprints = 'A text to show'; + return 0; // or return 1 to replace standard code + } else { + $this->errors[] = 'Error message'; + return -1; + } + } + + return 0; + } + + + /** + * Overload the addMoreMassActions function : replacing the parent's function with the one below + * + * @param array $parameters Hook metadata (context, etc...) + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param ?string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer < 0 on error, 0 on success, 1 to replace standard code + */ + public function addMoreMassActions($parameters, &$object, &$action, $hookmanager) + { + global $conf, $user, $langs; + + $error = 0; // Error counter + $disabled = 1; + + /* print_r($parameters); print_r($object); echo "action: " . $action; */ + if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { // do something only for the context 'somecontext1' or 'somecontext2' + $this->resprints = ''; + } + + if (!$error) { + return 0; // or return 1 to replace standard code + } else { + $this->errors[] = 'Error message'; + return -1; + } + } + + + + /** + * Execute action before PDF (document) creation + * + * @param array $parameters Array of parameters + * @param CommonObject $object Object output on PDF + * @param string $action 'add', 'update', 'view' + * @return int Return integer <0 if KO, + * =0 if OK but we want to process standard actions too, + * >0 if OK and we want to replace standard actions. + */ + public function beforePDFCreation($parameters, &$object, &$action) + { + global $conf, $user, $langs; + global $hookmanager; + + $outputlangs = $langs; + + $ret = 0; + $deltemp = array(); + dol_syslog(get_class($this).'::executeHooks action='.$action); + + /* print_r($parameters); print_r($object); echo "action: " . $action; */ + // @phan-suppress-next-line PhanPluginEmptyStatementIf + if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { + // do something only for the context 'somecontext1' or 'somecontext2' + } + + return $ret; + } + + /** + * Execute action after PDF (document) creation + * + * @param array $parameters Array of parameters + * @param CommonDocGenerator $pdfhandler PDF builder handler + * @param string $action 'add', 'update', 'view' + * @return int Return integer <0 if KO, + * =0 if OK but we want to process standard actions too, + * >0 if OK and we want to replace standard actions. + */ + public function afterPDFCreation($parameters, &$pdfhandler, &$action) + { + global $conf, $user, $langs; + global $hookmanager; + + $outputlangs = $langs; + + $ret = 0; + $deltemp = array(); + dol_syslog(get_class($this).'::executeHooks action='.$action); + + /* print_r($parameters); print_r($object); echo "action: " . $action; */ + // @phan-suppress-next-line PhanPluginEmptyStatementIf + if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { + // do something only for the context 'somecontext1' or 'somecontext2' + } + + return $ret; + } + + + + /** + * Overload the loadDataForCustomReports function : returns data to complete the customreport tool + * + * @param array $parameters Hook metadata (context, etc...) + * @param ?string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer < 0 on error, 0 on success, 1 to replace standard code + */ + public function loadDataForCustomReports($parameters, &$action, $hookmanager) + { + global $langs; + + $langs->load("supplierlink3@supplierlink3"); + + $this->results = array(); + + $head = array(); + $h = 0; + + if ($parameters['tabfamily'] == 'supplierlink3') { + $head[$h][0] = dol_buildpath('/module/index.php', 1); + $head[$h][1] = $langs->trans("Home"); + $head[$h][2] = 'home'; + $h++; + + $this->results['title'] = $langs->trans("SupplierLink3"); + $this->results['picto'] = 'supplierlink3@supplierlink3'; + } + + $head[$h][0] = 'customreports.php?objecttype='.$parameters['objecttype'].(empty($parameters['tabfamily']) ? '' : '&tabfamily='.$parameters['tabfamily']); + $head[$h][1] = $langs->trans("CustomReports"); + $head[$h][2] = 'customreports'; + + $this->results['head'] = $head; + + $arrayoftypes = array(); + //$arrayoftypes['supplierlink3_myobject'] = array('label' => 'MyObject', 'picto'=>'myobject@supplierlink3', 'ObjectClassName' => 'MyObject', 'enabled' => isModEnabled('supplierlink3'), 'ClassPath' => "/supplierlink3/class/myobject.class.php", 'langs'=>'supplierlink3@supplierlink3') + + $this->results['arrayoftype'] = $arrayoftypes; + + return 0; + } + + + + /** + * Overload the restrictedArea function : check permission on an object + * + * @param array $parameters Hook metadata (context, etc...) + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer <0 if KO, + * =0 if OK but we want to process standard actions too, + * >0 if OK and we want to replace standard actions. + */ + public function restrictedArea($parameters, $object, &$action, $hookmanager) + { + global $user; + + if ($parameters['features'] == 'myobject') { + if ($user->hasRight('supplierlink3', 'myobject', 'read')) { + $this->results['result'] = 1; + return 1; + } else { + $this->results['result'] = 0; + return 1; + } + } + + return 0; + } + + /** + * Execute action completeTabsHead + * + * @param array $parameters Array of parameters + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param string $action 'add', 'update', 'view' + * @param Hookmanager $hookmanager Hookmanager + * @return int Return integer <0 if KO, + * =0 if OK but we want to process standard actions too, + * >0 if OK and we want to replace standard actions. + */ + public function completeTabsHead(&$parameters, &$object, &$action, $hookmanager) + { + global $langs, $conf, $user; + + if (!isset($parameters['object']->element)) { + return 0; + } + if ($parameters['mode'] == 'remove') { + // used to make some tabs removed + return 0; + } elseif ($parameters['mode'] == 'add') { + $langs->load('supplierlink3@supplierlink3'); + // used when we want to add some tabs + $counter = count($parameters['head']); + $element = $parameters['object']->element; + $id = $parameters['object']->id; + // verifier le type d'onglet comme member_stats où ça ne doit pas apparaitre + // if (in_array($element, ['societe', 'member', 'contrat', 'fichinter', 'project', 'propal', 'commande', 'facture', 'order_supplier', 'invoice_supplier'])) { + if (in_array($element, ['context1', 'context2'])) { + $datacount = 0; + + $parameters['head'][$counter][0] = dol_buildpath('/supplierlink3/supplierlink3_tab.php', 1) . '?id=' . $id . '&module='.$element; + $parameters['head'][$counter][1] = $langs->trans('SupplierLink3Tab'); + if ($datacount > 0) { + $parameters['head'][$counter][1] .= '' . $datacount . ''; + } + $parameters['head'][$counter][2] = 'supplierlink3emails'; + $counter++; + } + if ($counter > 0 && (int) DOL_VERSION < 14) { // @phpstan-ignore-line + $this->results = $parameters['head']; + // return 1 to replace standard code + return 1; + } else { + // From V14 onwards, $parameters['head'] is modifiable by reference + return 0; + } + } else { + // Bad value for $parameters['mode'] + return -1; + } + } + + + /** + * Overload the showLinkToObjectBlock function : add or replace array of object linkable + * + * @param array $parameters Hook metadata (context, etc...) + * @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...) + * @param ?string $action Current action (if set). Generally create or edit or null + * @param HookManager $hookmanager Hook manager propagated to allow calling another hook + * @return int Return integer < 0 on error, 0 on success, 1 to replace standard code + */ + public function showLinkToObjectBlock($parameters, &$object, &$action, $hookmanager) + { + $myobject = new MyObject($object->db); + $this->results = array('myobject@supplierlink3' => array( + 'enabled' => isModEnabled('supplierlink3'), + 'perms' => 1, + 'label' => 'LinkToMyObject', + 'sql' => "SELECT t.rowid, t.ref, t.ref as 'name' FROM " . $this->db->prefix() . $myobject->table_element. " as t "),); + + return 1; + } + /* Add other hook methods here... */ + public function printObjectLine($parameters, &$object, &$action, $hookmanager) +{ + if ($parameters['currentcontext'] != 'ordersuppliercard') { + return 0; + } + + $line = $parameters['line']; + + // Lieferanten-Artikelnummer? + //if (!empty($line->product_fourn_ref)) { + + // Beispiel: als Link + $newref = ''.$line->object_ref_supplier.''; + + // Feld ersetzen + $line->object_ref_supplier = $newref; + //} + + return 0; +} +} diff --git a/core/modules/modSupplierLink3.class.php b/core/modules/modSupplierLink3.class.php new file mode 100755 index 0000000..6d8780e --- /dev/null +++ b/core/modules/modSupplierLink3.class.php @@ -0,0 +1,571 @@ + + * Copyright (C) 2018-2019 Nicolas ZABOURI + * Copyright (C) 2019-2024 Frédéric France + * Copyright (C) 2025 Eduard Wisch + * + * 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 . + */ + +/** + * \defgroup supplierlink3 Module SupplierLink3 + * \brief SupplierLink3 module descriptor. + * + * \file htdocs/supplierlink3/core/modules/modSupplierLink3.class.php + * \ingroup supplierlink3 + * \brief Description and activation file for module SupplierLink3 + */ +include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php'; + + +/** + * Description and activation class for module SupplierLink3 + */ +class modSupplierLink3 extends DolibarrModules +{ + /** + * Constructor. Define names, constants, directories, boxes, permissions + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + global $conf, $langs; + + $this->db = $db; + + // Id for module (must be unique). + // Use here a free id (See in Home -> System information -> Dolibarr for list of used modules id). + $this->numero = 500007; // TODO Go on page https://wiki.dolibarr.org/index.php/List_of_modules_id to reserve an id number for your module + + // Key text used to identify module (for permissions, menus, etc...) + $this->rights_class = 'supplierlink3'; + + // Family can be 'base' (core modules),'crm','financial','hr','projects','products','ecm','technic' (transverse modules),'interface' (link with external tools),'other','...' + // It is used to group modules by family in module setup page + $this->family = "other"; + + // Module position in the family on 2 digits ('01', '10', '20', ...) + $this->module_position = '90'; + + // Gives the possibility for the module, to provide his own family info and position of this family (Overwrite $this->family and $this->module_position. Avoid this) + //$this->familyinfo = array('myownfamily' => array('position' => '01', 'label' => $langs->trans("MyOwnFamily"))); + // Module label (no space allowed), used if translation string 'ModuleSupplierLink3Name' not found (SupplierLink3 is name of module). + $this->name = preg_replace('/^mod/i', '', get_class($this)); + + // DESCRIPTION_FLAG + // Module description, used if translation string 'ModuleSupplierLink3Desc' not found (SupplierLink3 is name of module). + $this->description = "SupplierLink3Description"; + // Used only if file README.md and README-LL.md not found. + $this->descriptionlong = "SupplierLink3Description"; + + // Author + $this->editor_name = 'Alles Watt läuft (Testsystem)'; + $this->editor_url = ''; // Must be an external online web site + $this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@supplierlink3' + + // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' + $this->version = '1.1'; + // Url to the file with your last numberversion of this module + //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; + + // Key used in llx_const table to save module status enabled/disabled (where SUPPLIERLINK3 is value of property name of module in uppercase) + $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); + + // Name of image file used for this module. + // If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue' + // If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module' + // To use a supported fa-xxx css style of font awesome, use this->picto='xxx' + $this->picto = 'fa-file'; + + // Define some features supported by module (triggers, login, substitutions, menus, css, etc...) + $this->module_parts = array( + // Set this to 1 if module has its own trigger directory (core/triggers) + 'triggers' => 0, + // Set this to 1 if module has its own login method file (core/login) + 'login' => 0, + // Set this to 1 if module has its own substitution function file (core/substitutions) + 'substitutions' => 0, + // Set this to 1 if module has its own menus handler directory (core/menus) + 'menus' => 0, + // Set this to 1 if module overwrite template dir (core/tpl) + 'tpl' => 0, + // Set this to 1 if module has its own barcode directory (core/modules/barcode) + 'barcode' => 0, + // Set this to 1 if module has its own models directory (core/modules/xxx) + 'models' => 0, + // Set this to 1 if module has its own printing directory (core/modules/printing) + 'printing' => 0, + // Set this to 1 if module has its own theme directory (theme) + 'theme' => 0, + // Set this to relative path of css file if module has its own css file + 'css' => array( + // '/supplierlink3/css/supplierlink3.css.php', + ), + // Set this to relative path of js file if module must load a js on all pages + 'js' => array( + // '/supplierlink3/js/supplierlink3.js.php', + ), + // Set here all hooks context managed by module. To find available hook context, make a "grep -r '>initHooks(' *" on source code. You can also set hook context to 'all' + /* BEGIN MODULEBUILDER HOOKSCONTEXTS */ + 'hooks' => array( + 'data' => array( + 'ordersuppliercard', + ), + 'entity' => '0', + ), + /* END MODULEBUILDER HOOKSCONTEXTS */ + // Set this to 1 if features of module are opened to external users + 'moduleforexternal' => 0, + // Set this to 1 if the module provides a website template into doctemplates/websites/website_template-mytemplate + 'websitetemplates' => 0, + // Set this to 1 if the module provides a captcha driver + 'captcha' => 0 + ); + + // Data directories to create when module is enabled. + // Example: this->dirs = array("/supplierlink3/temp","/supplierlink3/subdir"); + $this->dirs = array("/supplierlink3/temp"); + + // Config pages. Put here list of php page, stored into supplierlink3/admin directory, to use to setup module. + $this->config_page_url = array("setup.php@supplierlink3"); + + // Dependencies + // A condition to hide module + $this->hidden = getDolGlobalInt('MODULE_SUPPLIERLINK3_DISABLED'); // A condition to disable module; + // List of module class names that must be enabled if this module is enabled. Example: array('always'=>array('modModuleToEnable1','modModuleToEnable2'), 'FR'=>array('modModuleToEnableFR')...) + $this->depends = array(); + // List of module class names to disable if this one is disabled. Example: array('modModuleToDisable1', ...) + $this->requiredby = array(); + // List of module class names this module is in conflict with. Example: array('modModuleToDisable1', ...) + $this->conflictwith = array(); + + // The language file dedicated to your module + $this->langfiles = array("supplierlink3@supplierlink3"); + + // Prerequisites + $this->phpmin = array(7, 1); // Minimum version of PHP required by module + // $this->phpmax = array(8, 0); // Maximum version of PHP required by module + $this->need_dolibarr_version = array(19, -3); // Minimum version of Dolibarr required by module + // $this->max_dolibarr_version = array(19, -3); // Maximum version of Dolibarr required by module + $this->need_javascript_ajax = 0; + + // Messages at activation + $this->warnings_activation = array(); // Warning to show when we activate module. array('always'='text') or array('FR'='textfr','MX'='textmx'...) + $this->warnings_activation_ext = array(); // Warning to show when we activate an external module. array('always'='text') or array('FR'='textfr','MX'='textmx'...) + //$this->automatic_activation = array('FR'=>'SupplierLink3WasAutomaticallyActivatedBecauseOfYourCountryChoice'); + //$this->always_enabled = true; // If true, can't be disabled + + // Constants + // List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 'current' or 'allentities', deleteonunactive) + // Example: $this->const=array(1 => array('SUPPLIERLINK3_MYNEWCONST1', 'chaine', 'myvalue', 'This is a constant to add', 1), + // 2 => array('SUPPLIERLINK3_MYNEWCONST2', 'chaine', 'myvalue', 'This is another constant to add', 0, 'current', 1) + // ); + $this->const = array(); + + // Some keys to add into the overwriting translation tables + /*$this->overwrite_translation = array( + 'en_US:ParentCompany'=>'Parent company or reseller', + 'fr_FR:ParentCompany'=>'Maison mère ou revendeur' + )*/ + + if (!isModEnabled("supplierlink3")) { + $conf->supplierlink3 = new stdClass(); + $conf->supplierlink3->enabled = 0; + } + + // Array to add new pages in new tabs + /* BEGIN MODULEBUILDER TABS */ + $this->tabs = array(); + /* END MODULEBUILDER TABS */ + // Example: + // To add a new tab identified by code tabname1 + // $this->tabs[] = array('data' => 'objecttype:+tabname1:Title1:mylangfile@supplierlink3:$user->hasRight(\'supplierlink3\', \'read\'):/supplierlink3/mynewtab1.php?id=__ID__'); + // To add another new tab identified by code tabname2. Label will be result of calling all substitution functions on 'Title2' key. + // $this->tabs[] = array('data' => 'objecttype:+tabname2:SUBSTITUTION_Title2:mylangfile@supplierlink3:$user->hasRight(\'othermodule\', \'read\'):/supplierlink3/mynewtab2.php?id=__ID__', + // To remove an existing tab identified by code tabname + // $this->tabs[] = array('data' => 'objecttype:-tabname:NU:conditiontoremove'); + // + // Where objecttype can be + // 'categories_x' to add a tab in category view (replace 'x' by type of category (0=product, 1=supplier, 2=customer, 3=member) + // 'contact' to add a tab in contact view + // 'contract' to add a tab in contract view + // 'delivery' to add a tab in delivery view + // 'group' to add a tab in group view + // 'intervention' to add a tab in intervention view + // 'invoice' to add a tab in customer invoice view + // 'supplier_invoice' to add a tab in supplier invoice view + // 'member' to add a tab in foundation member view + // 'opensurveypoll' to add a tab in opensurvey poll view + // 'order' to add a tab in sale order view + // 'supplier_order' to add a tab in supplier order view + // 'payment' to add a tab in payment view + // 'supplier_payment' to add a tab in supplier payment view + // 'product' to add a tab in product view + // 'propal' to add a tab in propal view + // 'project' to add a tab in project view + // 'stock' to add a tab in stock view + // 'thirdparty' to add a tab in third party view + // 'user' to add a tab in user view + + + // Dictionaries + /* Example: + $this->dictionaries=array( + 'langs' => 'supplierlink3@supplierlink3', + // List of tables we want to see into dictionary editor + 'tabname' => array("table1", "table2", "table3"), + // Label of tables + 'tablib' => array("Table1", "Table2", "Table3"), + // Request to select fields + 'tabsql' => array('SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.$this->db->prefix().'table1 as f', 'SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.$this->db->prefix().'table2 as f', 'SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.$this->db->prefix().'table3 as f'), + // Sort order + 'tabsqlsort' => array("label ASC", "label ASC", "label ASC"), + // List of fields (result of select to show dictionary) + 'tabfield' => array("code,label", "code,label", "code,label"), + // List of fields (list of fields to edit a record) + 'tabfieldvalue' => array("code,label", "code,label", "code,label"), + // List of fields (list of fields for insert) + 'tabfieldinsert' => array("code,label", "code,label", "code,label"), + // Name of columns with primary key (try to always name it 'rowid') + 'tabrowid' => array("rowid", "rowid", "rowid"), + // Condition to show each dictionary + 'tabcond' => array(isModEnabled('supplierlink3'), isModEnabled('supplierlink3'), isModEnabled('supplierlink3')), + // Tooltip for every fields of dictionaries: DO NOT PUT AN EMPTY ARRAY + 'tabhelp' => array(array('code' => $langs->trans('CodeTooltipHelp'), 'field2' => 'field2tooltip'), array('code' => $langs->trans('CodeTooltipHelp'), 'field2' => 'field2tooltip'), ...), + ); + */ + /* BEGIN MODULEBUILDER DICTIONARIES */ + $this->dictionaries = array(); + /* END MODULEBUILDER DICTIONARIES */ + + // Boxes/Widgets + // Add here list of php file(s) stored in supplierlink3/core/boxes that contains a class to show a widget. + /* BEGIN MODULEBUILDER WIDGETS */ + $this->boxes = array( + // 0 => array( + // 'file' => 'supplierlink3widget1.php@supplierlink3', + // 'note' => 'Widget provided by SupplierLink3', + // 'enabledbydefaulton' => 'Home', + // ), + // ... + ); + /* END MODULEBUILDER WIDGETS */ + + // Cronjobs (List of cron jobs entries to add when module is enabled) + // unit_frequency must be 60 for minute, 3600 for hour, 86400 for day, 604800 for week + /* BEGIN MODULEBUILDER CRON */ + $this->cronjobs = array( + // 0 => array( + // 'label' => 'MyJob label', + // 'jobtype' => 'method', + // 'class' => '/supplierlink3/class/myobject.class.php', + // 'objectname' => 'MyObject', + // 'method' => 'doScheduledJob', + // 'parameters' => '', + // 'comment' => 'Comment', + // 'frequency' => 2, + // 'unitfrequency' => 3600, + // 'status' => 0, + // 'test' => 'isModEnabled("supplierlink3")', + // 'priority' => 50, + // ), + ); + /* END MODULEBUILDER CRON */ + // Example: $this->cronjobs=array( + // 0=>array('label'=>'My label', 'jobtype'=>'method', 'class'=>'/dir/class/file.class.php', 'objectname'=>'MyClass', 'method'=>'myMethod', 'parameters'=>'param1, param2', 'comment'=>'Comment', 'frequency'=>2, 'unitfrequency'=>3600, 'status'=>0, 'test'=>'isModEnabled("supplierlink3")', 'priority'=>50), + // 1=>array('label'=>'My label', 'jobtype'=>'command', 'command'=>'', 'parameters'=>'param1, param2', 'comment'=>'Comment', 'frequency'=>1, 'unitfrequency'=>3600*24, 'status'=>0, 'test'=>'isModEnabled("supplierlink3")', 'priority'=>50) + // ); + + // Permissions provided by this module + $this->rights = array(); + $r = 0; + // Add here entries to declare new permissions + /* BEGIN MODULEBUILDER PERMISSIONS */ + /* + $o = 1; + $this->rights[$r][0] = $this->numero . sprintf("%02d", ($o * 10) + 1); // Permission id (must not be already used) + $this->rights[$r][1] = 'Read objects of SupplierLink3'; // Permission label + $this->rights[$r][4] = 'myobject'; + $this->rights[$r][5] = 'read'; // In php code, permission will be checked by test if ($user->hasRight('supplierlink3', 'myobject', 'read')) + $r++; + $this->rights[$r][0] = $this->numero . sprintf("%02d", ($o * 10) + 2); // Permission id (must not be already used) + $this->rights[$r][1] = 'Create/Update objects of SupplierLink3'; // Permission label + $this->rights[$r][4] = 'myobject'; + $this->rights[$r][5] = 'write'; // In php code, permission will be checked by test if ($user->hasRight('supplierlink3', 'myobject', 'write')) + $r++; + $this->rights[$r][0] = $this->numero . sprintf("%02d", ($o * 10) + 3); // Permission id (must not be already used) + $this->rights[$r][1] = 'Delete objects of SupplierLink3'; // Permission label + $this->rights[$r][4] = 'myobject'; + $this->rights[$r][5] = 'delete'; // In php code, permission will be checked by test if ($user->hasRight('supplierlink3', 'myobject', 'delete')) + $r++; + */ + /* END MODULEBUILDER PERMISSIONS */ + + + // Main menu entries to add + //$this->menu = array(); + //$r = 0; + // Add here entries to declare new menus + /* BEGIN MODULEBUILDER TOPMENU */ + /*$this->menu[$r++] = array( + 'fk_menu' => '', // Will be stored into mainmenu + leftmenu. Use '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type' => 'top', // This is a Top menu entry + 'titre' => 'ModuleSupplierLink3Name', + 'prefix' => img_picto('', $this->picto, 'class="pictofixedwidth valignmiddle"'), + 'mainmenu' => 'supplierlink3', + 'leftmenu' => '', + 'url' => '/supplierlink3/supplierlink3index.php', + 'langs' => 'supplierlink3@supplierlink3', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("supplierlink3")', // Define condition to show or hide menu entry. Use 'isModEnabled("supplierlink3")' if entry must be visible if module is enabled. + 'perms' => '1', // Use 'perms'=>'$user->hasRight("supplierlink3", "myobject", "read")' if you want your menu with a permission rules + 'target' => '', + 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both + );*/ + /* END MODULEBUILDER TOPMENU */ + + /* BEGIN MODULEBUILDER LEFTMENU MYOBJECT */ + /* + $this->menu[$r++]=array( + 'fk_menu' => 'fk_mainmenu=supplierlink3', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type' => 'left', // This is a Left menu entry + 'titre' => 'MyObject', + 'prefix' => img_picto('', $this->picto, 'class="pictofixedwidth valignmiddle paddingright"'), + 'mainmenu' => 'supplierlink3', + 'leftmenu' => 'myobject', + 'url' => '/supplierlink3/supplierlink3index.php', + 'langs' => 'supplierlink3@supplierlink3', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("supplierlink3")', // Define condition to show or hide menu entry. Use 'isModEnabled("supplierlink3")' if entry must be visible if module is enabled. + 'perms' => '$user->hasRight("supplierlink3", "myobject", "read")', + 'target' => '', + 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both + 'object' => 'MyObject' + ); + $this->menu[$r++]=array( + 'fk_menu' => 'fk_mainmenu=supplierlink3,fk_leftmenu=myobject', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type' => 'left', // This is a Left menu entry + 'titre' => 'New_MyObject', + 'mainmenu' => 'supplierlink3', + 'leftmenu' => 'supplierlink3_myobject_new', + 'url' => '/supplierlink3/myobject_card.php?action=create', + 'langs' => 'supplierlink3@supplierlink3', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("supplierlink3")', // Define condition to show or hide menu entry. Use 'isModEnabled("supplierlink3")' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. + 'perms' => '$user->hasRight("supplierlink3", "myobject", "write")' + 'target' => '', + 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both + 'object' => 'MyObject' + ); + $this->menu[$r++]=array( + 'fk_menu' => 'fk_mainmenu=supplierlink3,fk_leftmenu=myobject', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type' => 'left', // This is a Left menu entry + 'titre' => 'List_MyObject', + 'mainmenu' => 'supplierlink3', + 'leftmenu' => 'supplierlink3_myobject_list', + 'url' => '/supplierlink3/myobject_list.php', + 'langs' => 'supplierlink3@supplierlink3', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("supplierlink3")', // Define condition to show or hide menu entry. Use 'isModEnabled("supplierlink3")' if entry must be visible if module is enabled. + 'perms' => '$user->hasRight("supplierlink3", "myobject", "read")' + 'target' => '', + 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both + 'object' => 'MyObject' + ); + */ + /* END MODULEBUILDER LEFTMENU MYOBJECT */ + + + // Exports profiles provided by this module + $r = 0; + /* BEGIN MODULEBUILDER EXPORT MYOBJECT */ + /* + $langs->load("supplierlink3@supplierlink3"); + $this->export_code[$r] = $this->rights_class.'_'.$r; + $this->export_label[$r] = 'MyObjectLines'; // Translation key (used only if key ExportDataset_xxx_z not found) + $this->export_icon[$r] = $this->picto; + // Define $this->export_fields_array, $this->export_TypeFields_array and $this->export_entities_array + $keyforclass = 'MyObject'; $keyforclassfile='/supplierlink3/class/myobject.class.php'; $keyforelement='myobject@supplierlink3'; + include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php'; + //$this->export_fields_array[$r]['t.fieldtoadd']='FieldToAdd'; $this->export_TypeFields_array[$r]['t.fieldtoadd']='Text'; + //unset($this->export_fields_array[$r]['t.fieldtoremove']); + //$keyforclass = 'MyObjectLine'; $keyforclassfile='/supplierlink3/class/myobject.class.php'; $keyforelement='myobjectline@supplierlink3'; $keyforalias='tl'; + //include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php'; + $keyforselect='myobject'; $keyforaliasextra='extra'; $keyforelement='myobject@supplierlink3'; + include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php'; + //$keyforselect='myobjectline'; $keyforaliasextra='extraline'; $keyforelement='myobjectline@supplierlink3'; + //include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php'; + //$this->export_dependencies_array[$r] = array('myobjectline' => array('tl.rowid','tl.ref')); // To force to activate one or several fields if we select some fields that need same (like to select a unique key if we ask a field of a child to avoid the DISTINCT to discard them, or for computed field than need several other fields) + //$this->export_special_array[$r] = array('t.field' => '...'); + //$this->export_examplevalues_array[$r] = array('t.field' => 'Example'); + //$this->export_help_array[$r] = array('t.field' => 'FieldDescHelp'); + $this->export_sql_start[$r]='SELECT DISTINCT '; + $this->export_sql_end[$r] =' FROM '.$this->db->prefix().'supplierlink3_myobject as t'; + //$this->export_sql_end[$r] .=' LEFT JOIN '.$this->db->prefix().'supplierlink3_myobject_line as tl ON tl.fk_myobject = t.rowid'; + $this->export_sql_end[$r] .=' WHERE 1 = 1'; + $this->export_sql_end[$r] .=' AND t.entity IN ('.getEntity('myobject').')'; + $r++; */ + /* END MODULEBUILDER EXPORT MYOBJECT */ + + // Imports profiles provided by this module + $r = 0; + /* BEGIN MODULEBUILDER IMPORT MYOBJECT */ + /* + $langs->load("supplierlink3@supplierlink3"); + $this->import_code[$r] = $this->rights_class.'_'.$r; + $this->import_label[$r] = 'MyObjectLines'; // Translation key (used only if key ExportDataset_xxx_z not found) + $this->import_icon[$r] = $this->picto; + $this->import_tables_array[$r] = array('t' => $this->db->prefix().'supplierlink3_myobject', 'extra' => $this->db->prefix().'supplierlink3_myobject_extrafields'); + $this->import_tables_creator_array[$r] = array('t' => 'fk_user_author'); // Fields to store import user id + $import_sample = array(); + $keyforclass = 'MyObject'; $keyforclassfile='/supplierlink3/class/myobject.class.php'; $keyforelement='myobject@supplierlink3'; + include DOL_DOCUMENT_ROOT.'/core/commonfieldsinimport.inc.php'; + $import_extrafield_sample = array(); + $keyforselect='myobject'; $keyforaliasextra='extra'; $keyforelement='myobject@supplierlink3'; + include DOL_DOCUMENT_ROOT.'/core/extrafieldsinimport.inc.php'; + $this->import_fieldshidden_array[$r] = array('extra.fk_object' => 'lastrowid-'.$this->db->prefix().'supplierlink3_myobject'); + $this->import_regex_array[$r] = array(); + $this->import_examplevalues_array[$r] = array_merge($import_sample, $import_extrafield_sample); + $this->import_updatekeys_array[$r] = array('t.ref' => 'Ref'); + $this->import_convertvalue_array[$r] = array( + 't.ref' => array( + 'rule'=>'getrefifauto', + 'class'=>(!getDolGlobalString('SUPPLIERLINK3_MYOBJECT_ADDON') ? 'mod_myobject_standard' : getDolGlobalString('SUPPLIERLINK3_MYOBJECT_ADDON')), + 'path'=>"/core/modules/supplierlink3/".(!getDolGlobalString('SUPPLIERLINK3_MYOBJECT_ADDON') ? 'mod_myobject_standard' : getDolGlobalString('SUPPLIERLINK3_MYOBJECT_ADDON')).'.php', + 'classobject'=>'MyObject', + 'pathobject'=>'/supplierlink3/class/myobject.class.php', + ), + 't.fk_soc' => array('rule' => 'fetchidfromref', 'file' => '/societe/class/societe.class.php', 'class' => 'Societe', 'method' => 'fetch', 'element' => 'ThirdParty'), + 't.fk_user_valid' => array('rule' => 'fetchidfromref', 'file' => '/user/class/user.class.php', 'class' => 'User', 'method' => 'fetch', 'element' => 'user'), + 't.fk_mode_reglement' => array('rule' => 'fetchidfromcodeorlabel', 'file' => '/compta/paiement/class/cpaiement.class.php', 'class' => 'Cpaiement', 'method' => 'fetch', 'element' => 'cpayment'), + ); + $this->import_run_sql_after_array[$r] = array(); + $r++; */ + /* END MODULEBUILDER IMPORT MYOBJECT */ + } + + /** + * Function called when module is enabled. + * The init function add constants, boxes, permissions and menus (defined in constructor) into Dolibarr database. + * It also creates data directories + * + * @param string $options Options when enabling module ('', 'noboxes') + * @return int<-1,1> 1 if OK, <=0 if KO + */ + public function init($options = '') + { + global $conf, $langs; + + // Create tables of module at module activation + //$result = $this->_load_tables('/install/mysql/', 'supplierlink3'); + $result = $this->_load_tables('/supplierlink3/sql/'); + if ($result < 0) { + return -1; // Do not activate module if error 'not allowed' returned when loading module SQL queries (the _load_table run sql with run_sql with the error allowed parameter set to 'default') + } + + // Create extrafields during init + include_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; + $extrafields = new ExtraFields($this->db); + //$result0=$extrafields->addExtraField('supplierlink3_separator1', "Separator 1", 'separator', 1, 0, 'thirdparty', 0, 0, '', array('options'=>array(1=>1)), 1, '', 1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + //$result1=$extrafields->addExtraField('supplierlink3_myattr1', "New Attr 1 label", 'boolean', 1, 3, 'thirdparty', 0, 0, '', '', 1, '', -1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + //$result2=$extrafields->addExtraField('supplierlink3_myattr2', "New Attr 2 label", 'varchar', 1, 10, 'project', 0, 0, '', '', 1, '', -1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + //$result3=$extrafields->addExtraField('supplierlink3_myattr3', "New Attr 3 label", 'varchar', 1, 10, 'bank_account', 0, 0, '', '', 1, '', -1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + //$result4=$extrafields->addExtraField('supplierlink3_myattr4', "New Attr 4 label", 'select', 1, 3, 'thirdparty', 0, 1, '', array('options'=>array('code1'=>'Val1','code2'=>'Val2','code3'=>'Val3')), 1,'', -1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + //$result5=$extrafields->addExtraField('supplierlink3_myattr5', "New Attr 5 label", 'text', 1, 10, 'user', 0, 0, '', '', 1, '', -1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + + $result0 = $extrafields->addExtraField( + 'shop_url', // Name (mit Modul-Präfix!) + "Shop URL", // Label + 'varchar', // Typ + 100, // Position/Reihenfolge + 255, // Größe + 'thirdparty', // Element (societe = thirdparty) + 0, // Unique (0=nein, 1=ja) + 0, // Required (0=nein, 1=ja) + '', // Default value + '', // Param (zusätzliche Optionen) + 1, // Always editable (1=ja) + '', // Perms (leer = alle dürfen) + 1, // List (0=nicht in Listen, 1=in Listen zeigen) + 0, // Computed value + '', // Entity + '', // Help text + 'supplierlink3@supplierlink3', // Language file + 'isModEnabled("supplierlink3")' // Enabled condition + ); + + print_r($result0); + + // Permissions + $this->remove($options); + + $sql = array(); + + // Document templates + $moduledir = dol_sanitizeFileName('supplierlink3'); + $myTmpObjects = array(); + $myTmpObjects['MyObject'] = array('includerefgeneration' => 0, 'includedocgeneration' => 0); + + foreach ($myTmpObjects as $myTmpObjectKey => $myTmpObjectArray) { + if ($myTmpObjectArray['includerefgeneration']) { + $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/'.$moduledir.'/template_myobjects.odt'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/'.$moduledir; + $dest = $dirodt.'/template_myobjects.odt'; + + if (file_exists($src) && !file_exists($dest)) { + require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + dol_mkdir($dirodt); + $result = dol_copy($src, $dest, '0', 0); + if ($result < 0) { + $langs->load("errors"); + $this->error = $langs->trans('ErrorFailToCopyFile', $src, $dest); + return 0; + } + } + + $sql = array_merge($sql, array( + "DELETE FROM ".$this->db->prefix()."document_model WHERE nom = 'standard_".strtolower($myTmpObjectKey)."' AND type = '".$this->db->escape(strtolower($myTmpObjectKey))."' AND entity = ".((int) $conf->entity), + "INSERT INTO ".$this->db->prefix()."document_model (nom, type, entity) VALUES('standard_".strtolower($myTmpObjectKey)."', '".$this->db->escape(strtolower($myTmpObjectKey))."', ".((int) $conf->entity).")", + "DELETE FROM ".$this->db->prefix()."document_model WHERE nom = 'generic_".strtolower($myTmpObjectKey)."_odt' AND type = '".$this->db->escape(strtolower($myTmpObjectKey))."' AND entity = ".((int) $conf->entity), + "INSERT INTO ".$this->db->prefix()."document_model (nom, type, entity) VALUES('generic_".strtolower($myTmpObjectKey)."_odt', '".$this->db->escape(strtolower($myTmpObjectKey))."', ".((int) $conf->entity).")" + )); + } + } + + return $this->_init($sql, $options); + } + + /** + * Function called when module is disabled. + * Remove from database constants, boxes and permissions from Dolibarr database. + * Data directories are not deleted + * + * @param string $options Options when enabling module ('', 'noboxes') + * @return int<-1,1> 1 if OK, <=0 if KO + */ + public function remove($options = '') + { + $sql = array(); + + // Nur löschen wenn wirklich deinstalliert wird, nicht bei init() + if (!empty($options) && $options == 'uninstall') { + include_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; + $extrafields = new ExtraFields($this->db); + $extrafields->delete('shop_url', 'thirdparty'); + } + + return $this->_remove($sql, $options); + } +} diff --git a/core/modules/modSupplierLink3.class.php.back b/core/modules/modSupplierLink3.class.php.back new file mode 100755 index 0000000..57f25d9 --- /dev/null +++ b/core/modules/modSupplierLink3.class.php.back @@ -0,0 +1,540 @@ + + * Copyright (C) 2018-2019 Nicolas ZABOURI + * Copyright (C) 2019-2024 Frédéric France + * Copyright (C) 2025 Eduard Wisch + * + * 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 . + */ + +/** + * \defgroup supplierlink3 Module SupplierLink3 + * \brief SupplierLink3 module descriptor. + * + * \file htdocs/supplierlink3/core/modules/modSupplierLink3.class.php + * \ingroup supplierlink3 + * \brief Description and activation file for module SupplierLink3 + */ +include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php'; + + +/** + * Description and activation class for module SupplierLink3 + */ +class modSupplierLink3 extends DolibarrModules +{ + /** + * Constructor. Define names, constants, directories, boxes, permissions + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + global $conf, $langs; + + $this->db = $db; + + // Id for module (must be unique). + // Use here a free id (See in Home -> System information -> Dolibarr for list of used modules id). + $this->numero = 500007; // TODO Go on page https://wiki.dolibarr.org/index.php/List_of_modules_id to reserve an id number for your module + + // Key text used to identify module (for permissions, menus, etc...) + $this->rights_class = 'supplierlink3'; + + // Family can be 'base' (core modules),'crm','financial','hr','projects','products','ecm','technic' (transverse modules),'interface' (link with external tools),'other','...' + // It is used to group modules by family in module setup page + $this->family = "other"; + + // Module position in the family on 2 digits ('01', '10', '20', ...) + $this->module_position = '90'; + + // Gives the possibility for the module, to provide his own family info and position of this family (Overwrite $this->family and $this->module_position. Avoid this) + //$this->familyinfo = array('myownfamily' => array('position' => '01', 'label' => $langs->trans("MyOwnFamily"))); + // Module label (no space allowed), used if translation string 'ModuleSupplierLink3Name' not found (SupplierLink3 is name of module). + $this->name = preg_replace('/^mod/i', '', get_class($this)); + + // DESCRIPTION_FLAG + // Module description, used if translation string 'ModuleSupplierLink3Desc' not found (SupplierLink3 is name of module). + $this->description = "SupplierLink3Description"; + // Used only if file README.md and README-LL.md not found. + $this->descriptionlong = "SupplierLink3Description"; + + // Author + $this->editor_name = 'Alles Watt läuft (Testsystem)'; + $this->editor_url = ''; // Must be an external online web site + $this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@supplierlink3' + + // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' + $this->version = '1.0'; + // Url to the file with your last numberversion of this module + //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; + + // Key used in llx_const table to save module status enabled/disabled (where SUPPLIERLINK3 is value of property name of module in uppercase) + $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); + + // Name of image file used for this module. + // If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue' + // If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module' + // To use a supported fa-xxx css style of font awesome, use this->picto='xxx' + $this->picto = 'fa-file'; + + // Define some features supported by module (triggers, login, substitutions, menus, css, etc...) + $this->module_parts = array( + // Set this to 1 if module has its own trigger directory (core/triggers) + 'triggers' => 0, + // Set this to 1 if module has its own login method file (core/login) + 'login' => 0, + // Set this to 1 if module has its own substitution function file (core/substitutions) + 'substitutions' => 0, + // Set this to 1 if module has its own menus handler directory (core/menus) + 'menus' => 0, + // Set this to 1 if module overwrite template dir (core/tpl) + 'tpl' => 0, + // Set this to 1 if module has its own barcode directory (core/modules/barcode) + 'barcode' => 0, + // Set this to 1 if module has its own models directory (core/modules/xxx) + 'models' => 0, + // Set this to 1 if module has its own printing directory (core/modules/printing) + 'printing' => 0, + // Set this to 1 if module has its own theme directory (theme) + 'theme' => 0, + // Set this to relative path of css file if module has its own css file + 'css' => array( + // '/supplierlink3/css/supplierlink3.css.php', + ), + // Set this to relative path of js file if module must load a js on all pages + 'js' => array( + // '/supplierlink3/js/supplierlink3.js.php', + ), + // Set here all hooks context managed by module. To find available hook context, make a "grep -r '>initHooks(' *" on source code. You can also set hook context to 'all' + /* BEGIN MODULEBUILDER HOOKSCONTEXTS */ + 'hooks' => array( + 'data' => array( + 'ordersuppliercard', + ), + 'entity' => '0', + ), + /* END MODULEBUILDER HOOKSCONTEXTS */ + // Set this to 1 if features of module are opened to external users + 'moduleforexternal' => 0, + // Set this to 1 if the module provides a website template into doctemplates/websites/website_template-mytemplate + 'websitetemplates' => 0, + // Set this to 1 if the module provides a captcha driver + 'captcha' => 0 + ); + + // Data directories to create when module is enabled. + // Example: this->dirs = array("/supplierlink3/temp","/supplierlink3/subdir"); + $this->dirs = array("/supplierlink3/temp"); + + // Config pages. Put here list of php page, stored into supplierlink3/admin directory, to use to setup module. + $this->config_page_url = array("setup.php@supplierlink3"); + + // Dependencies + // A condition to hide module + $this->hidden = getDolGlobalInt('MODULE_SUPPLIERLINK3_DISABLED'); // A condition to disable module; + // List of module class names that must be enabled if this module is enabled. Example: array('always'=>array('modModuleToEnable1','modModuleToEnable2'), 'FR'=>array('modModuleToEnableFR')...) + $this->depends = array(); + // List of module class names to disable if this one is disabled. Example: array('modModuleToDisable1', ...) + $this->requiredby = array(); + // List of module class names this module is in conflict with. Example: array('modModuleToDisable1', ...) + $this->conflictwith = array(); + + // The language file dedicated to your module + $this->langfiles = array("supplierlink3@supplierlink3"); + + // Prerequisites + $this->phpmin = array(7, 1); // Minimum version of PHP required by module + // $this->phpmax = array(8, 0); // Maximum version of PHP required by module + $this->need_dolibarr_version = array(19, -3); // Minimum version of Dolibarr required by module + // $this->max_dolibarr_version = array(19, -3); // Maximum version of Dolibarr required by module + $this->need_javascript_ajax = 0; + + // Messages at activation + $this->warnings_activation = array(); // Warning to show when we activate module. array('always'='text') or array('FR'='textfr','MX'='textmx'...) + $this->warnings_activation_ext = array(); // Warning to show when we activate an external module. array('always'='text') or array('FR'='textfr','MX'='textmx'...) + //$this->automatic_activation = array('FR'=>'SupplierLink3WasAutomaticallyActivatedBecauseOfYourCountryChoice'); + //$this->always_enabled = true; // If true, can't be disabled + + // Constants + // List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 'current' or 'allentities', deleteonunactive) + // Example: $this->const=array(1 => array('SUPPLIERLINK3_MYNEWCONST1', 'chaine', 'myvalue', 'This is a constant to add', 1), + // 2 => array('SUPPLIERLINK3_MYNEWCONST2', 'chaine', 'myvalue', 'This is another constant to add', 0, 'current', 1) + // ); + $this->const = array(); + + // Some keys to add into the overwriting translation tables + /*$this->overwrite_translation = array( + 'en_US:ParentCompany'=>'Parent company or reseller', + 'fr_FR:ParentCompany'=>'Maison mère ou revendeur' + )*/ + + if (!isModEnabled("supplierlink3")) { + $conf->supplierlink3 = new stdClass(); + $conf->supplierlink3->enabled = 0; + } + + // Array to add new pages in new tabs + /* BEGIN MODULEBUILDER TABS */ + $this->tabs = array(); + /* END MODULEBUILDER TABS */ + // Example: + // To add a new tab identified by code tabname1 + // $this->tabs[] = array('data' => 'objecttype:+tabname1:Title1:mylangfile@supplierlink3:$user->hasRight(\'supplierlink3\', \'read\'):/supplierlink3/mynewtab1.php?id=__ID__'); + // To add another new tab identified by code tabname2. Label will be result of calling all substitution functions on 'Title2' key. + // $this->tabs[] = array('data' => 'objecttype:+tabname2:SUBSTITUTION_Title2:mylangfile@supplierlink3:$user->hasRight(\'othermodule\', \'read\'):/supplierlink3/mynewtab2.php?id=__ID__', + // To remove an existing tab identified by code tabname + // $this->tabs[] = array('data' => 'objecttype:-tabname:NU:conditiontoremove'); + // + // Where objecttype can be + // 'categories_x' to add a tab in category view (replace 'x' by type of category (0=product, 1=supplier, 2=customer, 3=member) + // 'contact' to add a tab in contact view + // 'contract' to add a tab in contract view + // 'delivery' to add a tab in delivery view + // 'group' to add a tab in group view + // 'intervention' to add a tab in intervention view + // 'invoice' to add a tab in customer invoice view + // 'supplier_invoice' to add a tab in supplier invoice view + // 'member' to add a tab in foundation member view + // 'opensurveypoll' to add a tab in opensurvey poll view + // 'order' to add a tab in sale order view + // 'supplier_order' to add a tab in supplier order view + // 'payment' to add a tab in payment view + // 'supplier_payment' to add a tab in supplier payment view + // 'product' to add a tab in product view + // 'propal' to add a tab in propal view + // 'project' to add a tab in project view + // 'stock' to add a tab in stock view + // 'thirdparty' to add a tab in third party view + // 'user' to add a tab in user view + + + // Dictionaries + /* Example: + $this->dictionaries=array( + 'langs' => 'supplierlink3@supplierlink3', + // List of tables we want to see into dictionary editor + 'tabname' => array("table1", "table2", "table3"), + // Label of tables + 'tablib' => array("Table1", "Table2", "Table3"), + // Request to select fields + 'tabsql' => array('SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.$this->db->prefix().'table1 as f', 'SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.$this->db->prefix().'table2 as f', 'SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.$this->db->prefix().'table3 as f'), + // Sort order + 'tabsqlsort' => array("label ASC", "label ASC", "label ASC"), + // List of fields (result of select to show dictionary) + 'tabfield' => array("code,label", "code,label", "code,label"), + // List of fields (list of fields to edit a record) + 'tabfieldvalue' => array("code,label", "code,label", "code,label"), + // List of fields (list of fields for insert) + 'tabfieldinsert' => array("code,label", "code,label", "code,label"), + // Name of columns with primary key (try to always name it 'rowid') + 'tabrowid' => array("rowid", "rowid", "rowid"), + // Condition to show each dictionary + 'tabcond' => array(isModEnabled('supplierlink3'), isModEnabled('supplierlink3'), isModEnabled('supplierlink3')), + // Tooltip for every fields of dictionaries: DO NOT PUT AN EMPTY ARRAY + 'tabhelp' => array(array('code' => $langs->trans('CodeTooltipHelp'), 'field2' => 'field2tooltip'), array('code' => $langs->trans('CodeTooltipHelp'), 'field2' => 'field2tooltip'), ...), + ); + */ + /* BEGIN MODULEBUILDER DICTIONARIES */ + $this->dictionaries = array(); + /* END MODULEBUILDER DICTIONARIES */ + + // Boxes/Widgets + // Add here list of php file(s) stored in supplierlink3/core/boxes that contains a class to show a widget. + /* BEGIN MODULEBUILDER WIDGETS */ + $this->boxes = array( + // 0 => array( + // 'file' => 'supplierlink3widget1.php@supplierlink3', + // 'note' => 'Widget provided by SupplierLink3', + // 'enabledbydefaulton' => 'Home', + // ), + // ... + ); + /* END MODULEBUILDER WIDGETS */ + + // Cronjobs (List of cron jobs entries to add when module is enabled) + // unit_frequency must be 60 for minute, 3600 for hour, 86400 for day, 604800 for week + /* BEGIN MODULEBUILDER CRON */ + $this->cronjobs = array( + // 0 => array( + // 'label' => 'MyJob label', + // 'jobtype' => 'method', + // 'class' => '/supplierlink3/class/myobject.class.php', + // 'objectname' => 'MyObject', + // 'method' => 'doScheduledJob', + // 'parameters' => '', + // 'comment' => 'Comment', + // 'frequency' => 2, + // 'unitfrequency' => 3600, + // 'status' => 0, + // 'test' => 'isModEnabled("supplierlink3")', + // 'priority' => 50, + // ), + ); + /* END MODULEBUILDER CRON */ + // Example: $this->cronjobs=array( + // 0=>array('label'=>'My label', 'jobtype'=>'method', 'class'=>'/dir/class/file.class.php', 'objectname'=>'MyClass', 'method'=>'myMethod', 'parameters'=>'param1, param2', 'comment'=>'Comment', 'frequency'=>2, 'unitfrequency'=>3600, 'status'=>0, 'test'=>'isModEnabled("supplierlink3")', 'priority'=>50), + // 1=>array('label'=>'My label', 'jobtype'=>'command', 'command'=>'', 'parameters'=>'param1, param2', 'comment'=>'Comment', 'frequency'=>1, 'unitfrequency'=>3600*24, 'status'=>0, 'test'=>'isModEnabled("supplierlink3")', 'priority'=>50) + // ); + + // Permissions provided by this module + $this->rights = array(); + $r = 0; + // Add here entries to declare new permissions + /* BEGIN MODULEBUILDER PERMISSIONS */ + /* + $o = 1; + $this->rights[$r][0] = $this->numero . sprintf("%02d", ($o * 10) + 1); // Permission id (must not be already used) + $this->rights[$r][1] = 'Read objects of SupplierLink3'; // Permission label + $this->rights[$r][4] = 'myobject'; + $this->rights[$r][5] = 'read'; // In php code, permission will be checked by test if ($user->hasRight('supplierlink3', 'myobject', 'read')) + $r++; + $this->rights[$r][0] = $this->numero . sprintf("%02d", ($o * 10) + 2); // Permission id (must not be already used) + $this->rights[$r][1] = 'Create/Update objects of SupplierLink3'; // Permission label + $this->rights[$r][4] = 'myobject'; + $this->rights[$r][5] = 'write'; // In php code, permission will be checked by test if ($user->hasRight('supplierlink3', 'myobject', 'write')) + $r++; + $this->rights[$r][0] = $this->numero . sprintf("%02d", ($o * 10) + 3); // Permission id (must not be already used) + $this->rights[$r][1] = 'Delete objects of SupplierLink3'; // Permission label + $this->rights[$r][4] = 'myobject'; + $this->rights[$r][5] = 'delete'; // In php code, permission will be checked by test if ($user->hasRight('supplierlink3', 'myobject', 'delete')) + $r++; + */ + /* END MODULEBUILDER PERMISSIONS */ + + + // Main menu entries to add + //$this->menu = array(); + //$r = 0; + // Add here entries to declare new menus + /* BEGIN MODULEBUILDER TOPMENU */ + /*$this->menu[$r++] = array( + 'fk_menu' => '', // Will be stored into mainmenu + leftmenu. Use '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type' => 'top', // This is a Top menu entry + 'titre' => 'ModuleSupplierLink3Name', + 'prefix' => img_picto('', $this->picto, 'class="pictofixedwidth valignmiddle"'), + 'mainmenu' => 'supplierlink3', + 'leftmenu' => '', + 'url' => '/supplierlink3/supplierlink3index.php', + 'langs' => 'supplierlink3@supplierlink3', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("supplierlink3")', // Define condition to show or hide menu entry. Use 'isModEnabled("supplierlink3")' if entry must be visible if module is enabled. + 'perms' => '1', // Use 'perms'=>'$user->hasRight("supplierlink3", "myobject", "read")' if you want your menu with a permission rules + 'target' => '', + 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both + );*/ + /* END MODULEBUILDER TOPMENU */ + + /* BEGIN MODULEBUILDER LEFTMENU MYOBJECT */ + /* + $this->menu[$r++]=array( + 'fk_menu' => 'fk_mainmenu=supplierlink3', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type' => 'left', // This is a Left menu entry + 'titre' => 'MyObject', + 'prefix' => img_picto('', $this->picto, 'class="pictofixedwidth valignmiddle paddingright"'), + 'mainmenu' => 'supplierlink3', + 'leftmenu' => 'myobject', + 'url' => '/supplierlink3/supplierlink3index.php', + 'langs' => 'supplierlink3@supplierlink3', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("supplierlink3")', // Define condition to show or hide menu entry. Use 'isModEnabled("supplierlink3")' if entry must be visible if module is enabled. + 'perms' => '$user->hasRight("supplierlink3", "myobject", "read")', + 'target' => '', + 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both + 'object' => 'MyObject' + ); + $this->menu[$r++]=array( + 'fk_menu' => 'fk_mainmenu=supplierlink3,fk_leftmenu=myobject', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type' => 'left', // This is a Left menu entry + 'titre' => 'New_MyObject', + 'mainmenu' => 'supplierlink3', + 'leftmenu' => 'supplierlink3_myobject_new', + 'url' => '/supplierlink3/myobject_card.php?action=create', + 'langs' => 'supplierlink3@supplierlink3', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("supplierlink3")', // Define condition to show or hide menu entry. Use 'isModEnabled("supplierlink3")' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected. + 'perms' => '$user->hasRight("supplierlink3", "myobject", "write")' + 'target' => '', + 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both + 'object' => 'MyObject' + ); + $this->menu[$r++]=array( + 'fk_menu' => 'fk_mainmenu=supplierlink3,fk_leftmenu=myobject', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type' => 'left', // This is a Left menu entry + 'titre' => 'List_MyObject', + 'mainmenu' => 'supplierlink3', + 'leftmenu' => 'supplierlink3_myobject_list', + 'url' => '/supplierlink3/myobject_list.php', + 'langs' => 'supplierlink3@supplierlink3', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("supplierlink3")', // Define condition to show or hide menu entry. Use 'isModEnabled("supplierlink3")' if entry must be visible if module is enabled. + 'perms' => '$user->hasRight("supplierlink3", "myobject", "read")' + 'target' => '', + 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both + 'object' => 'MyObject' + ); + */ + /* END MODULEBUILDER LEFTMENU MYOBJECT */ + + + // Exports profiles provided by this module + $r = 0; + /* BEGIN MODULEBUILDER EXPORT MYOBJECT */ + /* + $langs->load("supplierlink3@supplierlink3"); + $this->export_code[$r] = $this->rights_class.'_'.$r; + $this->export_label[$r] = 'MyObjectLines'; // Translation key (used only if key ExportDataset_xxx_z not found) + $this->export_icon[$r] = $this->picto; + // Define $this->export_fields_array, $this->export_TypeFields_array and $this->export_entities_array + $keyforclass = 'MyObject'; $keyforclassfile='/supplierlink3/class/myobject.class.php'; $keyforelement='myobject@supplierlink3'; + include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php'; + //$this->export_fields_array[$r]['t.fieldtoadd']='FieldToAdd'; $this->export_TypeFields_array[$r]['t.fieldtoadd']='Text'; + //unset($this->export_fields_array[$r]['t.fieldtoremove']); + //$keyforclass = 'MyObjectLine'; $keyforclassfile='/supplierlink3/class/myobject.class.php'; $keyforelement='myobjectline@supplierlink3'; $keyforalias='tl'; + //include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php'; + $keyforselect='myobject'; $keyforaliasextra='extra'; $keyforelement='myobject@supplierlink3'; + include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php'; + //$keyforselect='myobjectline'; $keyforaliasextra='extraline'; $keyforelement='myobjectline@supplierlink3'; + //include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php'; + //$this->export_dependencies_array[$r] = array('myobjectline' => array('tl.rowid','tl.ref')); // To force to activate one or several fields if we select some fields that need same (like to select a unique key if we ask a field of a child to avoid the DISTINCT to discard them, or for computed field than need several other fields) + //$this->export_special_array[$r] = array('t.field' => '...'); + //$this->export_examplevalues_array[$r] = array('t.field' => 'Example'); + //$this->export_help_array[$r] = array('t.field' => 'FieldDescHelp'); + $this->export_sql_start[$r]='SELECT DISTINCT '; + $this->export_sql_end[$r] =' FROM '.$this->db->prefix().'supplierlink3_myobject as t'; + //$this->export_sql_end[$r] .=' LEFT JOIN '.$this->db->prefix().'supplierlink3_myobject_line as tl ON tl.fk_myobject = t.rowid'; + $this->export_sql_end[$r] .=' WHERE 1 = 1'; + $this->export_sql_end[$r] .=' AND t.entity IN ('.getEntity('myobject').')'; + $r++; */ + /* END MODULEBUILDER EXPORT MYOBJECT */ + + // Imports profiles provided by this module + $r = 0; + /* BEGIN MODULEBUILDER IMPORT MYOBJECT */ + /* + $langs->load("supplierlink3@supplierlink3"); + $this->import_code[$r] = $this->rights_class.'_'.$r; + $this->import_label[$r] = 'MyObjectLines'; // Translation key (used only if key ExportDataset_xxx_z not found) + $this->import_icon[$r] = $this->picto; + $this->import_tables_array[$r] = array('t' => $this->db->prefix().'supplierlink3_myobject', 'extra' => $this->db->prefix().'supplierlink3_myobject_extrafields'); + $this->import_tables_creator_array[$r] = array('t' => 'fk_user_author'); // Fields to store import user id + $import_sample = array(); + $keyforclass = 'MyObject'; $keyforclassfile='/supplierlink3/class/myobject.class.php'; $keyforelement='myobject@supplierlink3'; + include DOL_DOCUMENT_ROOT.'/core/commonfieldsinimport.inc.php'; + $import_extrafield_sample = array(); + $keyforselect='myobject'; $keyforaliasextra='extra'; $keyforelement='myobject@supplierlink3'; + include DOL_DOCUMENT_ROOT.'/core/extrafieldsinimport.inc.php'; + $this->import_fieldshidden_array[$r] = array('extra.fk_object' => 'lastrowid-'.$this->db->prefix().'supplierlink3_myobject'); + $this->import_regex_array[$r] = array(); + $this->import_examplevalues_array[$r] = array_merge($import_sample, $import_extrafield_sample); + $this->import_updatekeys_array[$r] = array('t.ref' => 'Ref'); + $this->import_convertvalue_array[$r] = array( + 't.ref' => array( + 'rule'=>'getrefifauto', + 'class'=>(!getDolGlobalString('SUPPLIERLINK3_MYOBJECT_ADDON') ? 'mod_myobject_standard' : getDolGlobalString('SUPPLIERLINK3_MYOBJECT_ADDON')), + 'path'=>"/core/modules/supplierlink3/".(!getDolGlobalString('SUPPLIERLINK3_MYOBJECT_ADDON') ? 'mod_myobject_standard' : getDolGlobalString('SUPPLIERLINK3_MYOBJECT_ADDON')).'.php', + 'classobject'=>'MyObject', + 'pathobject'=>'/supplierlink3/class/myobject.class.php', + ), + 't.fk_soc' => array('rule' => 'fetchidfromref', 'file' => '/societe/class/societe.class.php', 'class' => 'Societe', 'method' => 'fetch', 'element' => 'ThirdParty'), + 't.fk_user_valid' => array('rule' => 'fetchidfromref', 'file' => '/user/class/user.class.php', 'class' => 'User', 'method' => 'fetch', 'element' => 'user'), + 't.fk_mode_reglement' => array('rule' => 'fetchidfromcodeorlabel', 'file' => '/compta/paiement/class/cpaiement.class.php', 'class' => 'Cpaiement', 'method' => 'fetch', 'element' => 'cpayment'), + ); + $this->import_run_sql_after_array[$r] = array(); + $r++; */ + /* END MODULEBUILDER IMPORT MYOBJECT */ + } + + /** + * Function called when module is enabled. + * The init function add constants, boxes, permissions and menus (defined in constructor) into Dolibarr database. + * It also creates data directories + * + * @param string $options Options when enabling module ('', 'noboxes') + * @return int<-1,1> 1 if OK, <=0 if KO + */ + public function init($options = '') + { + global $conf, $langs; + + // Create tables of module at module activation + //$result = $this->_load_tables('/install/mysql/', 'supplierlink3'); + $result = $this->_load_tables('/supplierlink3/sql/'); + if ($result < 0) { + return -1; // Do not activate module if error 'not allowed' returned when loading module SQL queries (the _load_table run sql with run_sql with the error allowed parameter set to 'default') + } + + // Create extrafields during init + //include_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; + //$extrafields = new ExtraFields($this->db); + //$result0=$extrafields->addExtraField('supplierlink3_separator1', "Separator 1", 'separator', 1, 0, 'thirdparty', 0, 0, '', array('options'=>array(1=>1)), 1, '', 1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + //$result1=$extrafields->addExtraField('supplierlink3_myattr1', "New Attr 1 label", 'boolean', 1, 3, 'thirdparty', 0, 0, '', '', 1, '', -1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + //$result2=$extrafields->addExtraField('supplierlink3_myattr2', "New Attr 2 label", 'varchar', 1, 10, 'project', 0, 0, '', '', 1, '', -1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + //$result3=$extrafields->addExtraField('supplierlink3_myattr3', "New Attr 3 label", 'varchar', 1, 10, 'bank_account', 0, 0, '', '', 1, '', -1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + //$result4=$extrafields->addExtraField('supplierlink3_myattr4', "New Attr 4 label", 'select', 1, 3, 'thirdparty', 0, 1, '', array('options'=>array('code1'=>'Val1','code2'=>'Val2','code3'=>'Val3')), 1,'', -1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + //$result5=$extrafields->addExtraField('supplierlink3_myattr5', "New Attr 5 label", 'text', 1, 10, 'user', 0, 0, '', '', 1, '', -1, 0, '', '', 'supplierlink3@supplierlink3', 'isModEnabled("supplierlink3")'); + + // Permissions + $this->remove($options); + + $sql = array(); + + // Document templates + $moduledir = dol_sanitizeFileName('supplierlink3'); + $myTmpObjects = array(); + $myTmpObjects['MyObject'] = array('includerefgeneration' => 0, 'includedocgeneration' => 0); + + foreach ($myTmpObjects as $myTmpObjectKey => $myTmpObjectArray) { + if ($myTmpObjectArray['includerefgeneration']) { + $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/'.$moduledir.'/template_myobjects.odt'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/'.$moduledir; + $dest = $dirodt.'/template_myobjects.odt'; + + if (file_exists($src) && !file_exists($dest)) { + require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + dol_mkdir($dirodt); + $result = dol_copy($src, $dest, '0', 0); + if ($result < 0) { + $langs->load("errors"); + $this->error = $langs->trans('ErrorFailToCopyFile', $src, $dest); + return 0; + } + } + + $sql = array_merge($sql, array( + "DELETE FROM ".$this->db->prefix()."document_model WHERE nom = 'standard_".strtolower($myTmpObjectKey)."' AND type = '".$this->db->escape(strtolower($myTmpObjectKey))."' AND entity = ".((int) $conf->entity), + "INSERT INTO ".$this->db->prefix()."document_model (nom, type, entity) VALUES('standard_".strtolower($myTmpObjectKey)."', '".$this->db->escape(strtolower($myTmpObjectKey))."', ".((int) $conf->entity).")", + "DELETE FROM ".$this->db->prefix()."document_model WHERE nom = 'generic_".strtolower($myTmpObjectKey)."_odt' AND type = '".$this->db->escape(strtolower($myTmpObjectKey))."' AND entity = ".((int) $conf->entity), + "INSERT INTO ".$this->db->prefix()."document_model (nom, type, entity) VALUES('generic_".strtolower($myTmpObjectKey)."_odt', '".$this->db->escape(strtolower($myTmpObjectKey))."', ".((int) $conf->entity).")" + )); + } + } + + return $this->_init($sql, $options); + } + + /** + * Function called when module is disabled. + * Remove from database constants, boxes and permissions from Dolibarr database. + * Data directories are not deleted + * + * @param string $options Options when enabling module ('', 'noboxes') + * @return int<-1,1> 1 if OK, <=0 if KO + */ + public function remove($options = '') + { + $sql = array(); + return $this->_remove($sql, $options); + } +} diff --git a/img/README.md b/img/README.md new file mode 100755 index 0000000..612f8b4 --- /dev/null +++ b/img/README.md @@ -0,0 +1,14 @@ + +Directory for module image files +-------------------------------- + +You can put here the .png files of your module: + + +If the picto of your module is an image (property $picto has been set to 'supplierlink3.png@supplierlink3', you can put into this +directory a .png file called *object_supplierlink3.png* (16x16 or 32x32 pixels) + + +If the picto of an object is an image (property $picto of the object.class.php has been set to 'myobject.png@supplierlink3', then you can put into this +directory a .png file called *object_myobject.png* (16x16 or 32x32 pixels) + diff --git a/langs/en_US/supplierlink3.lang b/langs/en_US/supplierlink3.lang new file mode 100755 index 0000000..78db5f2 --- /dev/null +++ b/langs/en_US/supplierlink3.lang @@ -0,0 +1,42 @@ +# Translation file + +# +# Generic +# + +# Module label 'ModuleSupplierLink3Name' +ModuleSupplierLink3Name = SupplierLink3 +# Module description 'ModuleSupplierLink3Desc' +ModuleSupplierLink3Desc = SupplierLink3 description + +# +# Admin page +# +SupplierLink3Setup = SupplierLink3 setup +Settings = Settings +SupplierLink3SetupPage = SupplierLink3 setup page +NewSection=New section +SUPPLIERLINK3_MYPARAM1 = My param 1 +SUPPLIERLINK3_MYPARAM1Tooltip = My param 1 tooltip +SUPPLIERLINK3_MYPARAM2=My param 2 +SUPPLIERLINK3_MYPARAM2Tooltip=My param 2 tooltip + + +# +# About page +# +About = About +SupplierLink3About = About SupplierLink3 +SupplierLink3AboutPage = SupplierLink3 about page + +# +# Sample page +# +SupplierLink3Area = Home SupplierLink3 +MyPageName = My page name + +# +# Sample widget +# +MyWidget = My widget +MyWidgetDescription = My widget description diff --git a/lib/supplierlink3.lib.php b/lib/supplierlink3.lib.php new file mode 100755 index 0000000..6f752a4 --- /dev/null +++ b/lib/supplierlink3.lib.php @@ -0,0 +1,85 @@ + + * + * 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 supplierlink3/lib/supplierlink3.lib.php + * \ingroup supplierlink3 + * \brief Library files with common functions for SupplierLink3 + */ + +/** + * Prepare admin pages header + * + * @return array + */ +function supplierlink3AdminPrepareHead() +{ + global $langs, $conf; + + // global $db; + // $extrafields = new ExtraFields($db); + // $extrafields->fetch_name_optionals_label('myobject'); + + $langs->load("supplierlink3@supplierlink3"); + + $h = 0; + $head = array(); + + $head[$h][0] = dol_buildpath("/supplierlink3/admin/setup.php", 1); + $head[$h][1] = $langs->trans("Settings"); + $head[$h][2] = 'settings'; + $h++; + + /* + $head[$h][0] = dol_buildpath("/supplierlink3/admin/myobject_extrafields.php", 1); + $head[$h][1] = $langs->trans("ExtraFields"); + $nbExtrafields = (isset($extrafields->attributes['myobject']['label']) && is_countable($extrafields->attributes['myobject']['label'])) ? count($extrafields->attributes['myobject']['label']) : 0; + if ($nbExtrafields > 0) { + $head[$h][1] .= '' . $nbExtrafields . ''; + } + $head[$h][2] = 'myobject_extrafields'; + $h++; + + $head[$h][0] = dol_buildpath("/supplierlink3/admin/myobjectline_extrafields.php", 1); + $head[$h][1] = $langs->trans("ExtraFieldsLines"); + $nbExtrafields = (isset($extrafields->attributes['myobjectline']['label']) && is_countable($extrafields->attributes['myobjectline']['label'])) ? count($extrafields->attributes['myobject']['label']) : 0; + if ($nbExtrafields > 0) { + $head[$h][1] .= '' . $nbExtrafields . ''; + } + $head[$h][2] = 'myobject_extrafieldsline'; + $h++; + */ + + $head[$h][0] = dol_buildpath("/supplierlink3/admin/about.php", 1); + $head[$h][1] = $langs->trans("About"); + $head[$h][2] = 'about'; + $h++; + + // Show more tabs from modules + // Entries must be declared in modules descriptor with line + //$this->tabs = array( + // 'entity:+tabname:Title:@supplierlink3:/supplierlink3/mypage.php?id=__ID__' + //); // to add new tab + //$this->tabs = array( + // 'entity:-tabname:Title:@supplierlink3:/supplierlink3/mypage.php?id=__ID__' + //); // to remove a tab + complete_head_from_modules($conf, $langs, null, $head, $h, 'supplierlink3@supplierlink3'); + + complete_head_from_modules($conf, $langs, null, $head, $h, 'supplierlink3@supplierlink3', 'remove'); + + return $head; +} diff --git a/modulebuilder.txt b/modulebuilder.txt new file mode 100755 index 0000000..670a177 --- /dev/null +++ b/modulebuilder.txt @@ -0,0 +1,3 @@ +# DO NOT DELETE THIS FILE MANUALLY +# File to flag module built using official module template. +# When this file is present into a module directory, you can edit it with the module builder tool. \ No newline at end of file diff --git a/sql/dolibarr_allversions.sql b/sql/dolibarr_allversions.sql new file mode 100755 index 0000000..5026bb4 --- /dev/null +++ b/sql/dolibarr_allversions.sql @@ -0,0 +1,3 @@ +-- +-- Script run when an upgrade of Dolibarr is done. Whatever is the Dolibarr version. +-- diff --git a/supplierlink3index.php b/supplierlink3index.php new file mode 100755 index 0000000..bb46425 --- /dev/null +++ b/supplierlink3index.php @@ -0,0 +1,259 @@ + + * Copyright (C) 2004-2015 Laurent Destailleur + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2015 Jean-François Ferry + * Copyright (C) 2024 Frédéric France + * Copyright (C) 2025 Eduard Wisch + * + * 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 supplierlink3/supplierlink3index.php + * \ingroup supplierlink3 + * \brief Home page of supplierlink3 top menu + */ + +// Load Dolibarr environment +$res = 0; +// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) +if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) { + $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; +} +// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME +$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; +$tmp2 = realpath(__FILE__); +$i = strlen($tmp) - 1; +$j = strlen($tmp2) - 1; +while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { + $i--; + $j--; +} +if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) { + $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php"; +} +if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) { + $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; +} +// Try main.inc.php using relative path +if (!$res && file_exists("../main.inc.php")) { + $res = @include "../main.inc.php"; +} +if (!$res && file_exists("../../main.inc.php")) { + $res = @include "../../main.inc.php"; +} +if (!$res && file_exists("../../../main.inc.php")) { + $res = @include "../../../main.inc.php"; +} +if (!$res) { + die("Include of main fails"); +} + +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ + +// Load translation files required by the page +$langs->loadLangs(array("supplierlink3@supplierlink3")); + +$action = GETPOST('action', 'aZ09'); + +$now = dol_now(); +$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); + +// Security check - Protection if external user +$socid = GETPOSTINT('socid'); +if (!empty($user->socid) && $user->socid > 0) { + $action = ''; + $socid = $user->socid; +} + +// Initialize a technical object to manage hooks. Note that conf->hooks_modules contains array +//$hookmanager->initHooks(array($object->element.'index')); + +// Security check (enable the most restrictive one) +//if ($user->socid > 0) accessforbidden(); +//if ($user->socid > 0) $socid = $user->socid; +//if (!isModEnabled('supplierlink3')) { +// accessforbidden('Module not enabled'); +//} +//if (! $user->hasRight('supplierlink3', 'myobject', 'read')) { +// accessforbidden(); +//} +//restrictedArea($user, 'supplierlink3', 0, 'supplierlink3_myobject', 'myobject', '', 'rowid'); +//if (empty($user->admin)) { +// accessforbidden('Must be admin'); +//} + + +/* + * Actions + */ + +// None + + +/* + * View + */ + +$form = new Form($db); +$formfile = new FormFile($db); + +llxHeader("", $langs->trans("SupplierLink3Area"), '', '', 0, 0, '', '', '', 'mod-supplierlink3 page-index'); + +print load_fiche_titre($langs->trans("SupplierLink3Area"), '', 'supplierlink3.png@supplierlink3'); + +print '
'; + + +/* BEGIN MODULEBUILDER DRAFT MYOBJECT +// Draft MyObject +if (isModEnabled('supplierlink3') && $user->hasRight('supplierlink3', 'read')) { + $langs->load("orders"); + + $sql = "SELECT c.rowid, c.ref, c.ref_client, c.total_ht, c.tva as total_tva, c.total_ttc, s.rowid as socid, s.nom as name, s.client, s.canvas"; + $sql.= ", s.code_client"; + $sql.= " FROM ".$db->prefix()."commande as c"; + $sql.= ", ".$db->prefix()."societe as s"; + $sql.= " WHERE c.fk_soc = s.rowid"; + $sql.= " AND c.fk_statut = 0"; + $sql.= " AND c.entity IN (".getEntity('commande').")"; + if ($socid) $sql.= " AND c.fk_soc = ".((int) $socid); + + $resql = $db->query($sql); + if ($resql) + { + $total = 0; + $num = $db->num_rows($resql); + + print ''; + print ''; + print ''; + + $var = true; + if ($num > 0) + { + $i = 0; + while ($i < $num) + { + + $obj = $db->fetch_object($resql); + print ''; + print ''; + print ''; + $i++; + $total += $obj->total_ttc; + } + if ($total>0) + { + + print '"; + } + } + else + { + + print ''; + } + print "
'.$langs->trans("DraftMyObjects").($num?''.$num.'':'').'
'; + + $myobjectstatic->id=$obj->rowid; + $myobjectstatic->ref=$obj->ref; + $myobjectstatic->ref_client=$obj->ref_client; + $myobjectstatic->total_ht = $obj->total_ht; + $myobjectstatic->total_tva = $obj->total_tva; + $myobjectstatic->total_ttc = $obj->total_ttc; + + print $myobjectstatic->getNomUrl(1); + print ''; + print ''.price($obj->total_ttc).'
'.$langs->trans("Total").''.price($total)."
'.$langs->trans("NoOrder").'

"; + + $db->free($resql); + } + else + { + dol_print_error($db); + } +} +END MODULEBUILDER DRAFT MYOBJECT */ + + +print '
'; + + +/* BEGIN MODULEBUILDER LASTMODIFIED MYOBJECT +// Last modified myobject +if (isModEnabled('supplierlink3') && $user->hasRight('supplierlink3', 'read')) { + $sql = "SELECT s.rowid, s.ref, s.label, s.date_creation, s.tms"; + $sql.= " FROM ".$db->prefix()."supplierlink3_myobject as s"; + $sql.= " WHERE s.entity IN (".getEntity($myobjectstatic->element).")"; + //if ($socid) $sql.= " AND s.rowid = $socid"; + $sql .= " ORDER BY s.tms DESC"; + $sql .= $db->plimit($max, 0); + + $resql = $db->query($sql); + if ($resql) + { + $num = $db->num_rows($resql); + $i = 0; + + print ''; + print ''; + print ''; + print ''; + print ''; + if ($num) + { + while ($i < $num) + { + $objp = $db->fetch_object($resql); + + $myobjectstatic->id=$objp->rowid; + $myobjectstatic->ref=$objp->ref; + $myobjectstatic->label=$objp->label; + $myobjectstatic->status = $objp->status; + + print ''; + print ''; + print '"; + print '"; + print ''; + $i++; + } + + $db->free($resql); + } else { + print ''; + } + print "
'; + print $langs->trans("BoxTitleLatestModifiedMyObjects", $max); + print ''.$langs->trans("DateModificationShort").'
'.$myobjectstatic->getNomUrl(1).''; + print "'.dol_print_date($db->jdate($objp->tms), 'day')."
'.$langs->trans("None").'

"; + } +} +*/ + +print '
'; + +// End of page +llxFooter(); +$db->close(); From aee5056361bafe1a71278535113f663b8e43ceee Mon Sep 17 00:00:00 2001 From: data Date: Tue, 9 Dec 2025 14:28:22 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Langebestand=20in=20Lieferantenbestellung?= =?UTF-8?q?=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ChangeLog.md | 2 +- class/actions_supplierlink3.class.php | 39 +++++++++++++++++++------ core/modules/modSupplierLink3.class.php | 2 +- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index ede6a79..db2787f 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,5 @@ # CHANGELOG MODULE SUPPLIERLINK3 FOR [DOLIBARR ERP CRM](https://www.dolibarr.org) -1.1 +## 1.0 Initial version diff --git a/class/actions_supplierlink3.class.php b/class/actions_supplierlink3.class.php index adcb7bd..c65a3d8 100755 --- a/class/actions_supplierlink3.class.php +++ b/class/actions_supplierlink3.class.php @@ -409,13 +409,12 @@ class ActionsSupplierLink3 extends CommonHookActions $line = $parameters['line']; - // Lieferanten-ID aus dem Bestell-Objekt holen, nicht aus der Zeile! + // Lieferanten-ID aus dem Bestell-Objekt holen if (!empty($object->socid)) { $fk_supplier = $object->socid; // Extrafeld-Wert vom Lieferanten laden - $sql = "SELECT shop_url"; - $sql .= " FROM ".MAIN_DB_PREFIX."societe_extrafields"; + $sql = "SELECT shop_url FROM ".MAIN_DB_PREFIX."societe_extrafields"; $sql .= " WHERE fk_object = ".(int)$fk_supplier; $resql = $this->db->query($sql); @@ -423,24 +422,45 @@ class ActionsSupplierLink3 extends CommonHookActions if ($resql && $this->db->num_rows($resql) > 0) { $obj = $this->db->fetch_object($resql); - // Prüfen ob shop_url gesetzt ist if (isset($obj->shop_url) && $obj->shop_url !== '' && $obj->shop_url !== null) { $shop_url = trim($obj->shop_url); - // Nur wenn die URL nicht leer ist if (!empty($shop_url)) { - // Artikelnummer an die URL anhängen - $full_url = rtrim($shop_url, '/') . '/' . $line->ref_fourn; + // Lagerbestand und Wunschbestand abfragen + $sqlStock = "SELECT stock, desiredstock + FROM ".MAIN_DB_PREFIX."product + WHERE rowid = ".(int)$line->fk_product; - // Link erstellen + $resStock = $this->db->query($sqlStock); + $qtyStock = 0; + $desiredQty = 0; + if ($resStock && $this->db->num_rows($resStock) > 0) { + $objStock = $this->db->fetch_object($resStock); + $qtyStock = (float) $objStock->stock; + $desiredQty = (float) $objStock->desiredstock; + } + + // Farbe setzen + $stockColor = 'ffffff'; + if ($qtyStock < 1) { + $stockColor = 'red'; // ausverkauft + } elseif ($qtyStock < $desiredQty) { + $stockColor = 'orange'; // kleiner als Wunschbestand + } + + // Artikel-Link + $full_url = rtrim($shop_url, '/') . '/' . $line->ref_fourn; $newref = ''; $newref .= $line->ref_fourn; - $newref .= ' '; $newref .= ''; + // Lagerbestand daneben mit Farbe + $newref .= '       '.$qtyStock.''; + $line->ref_fourn = $newref; + } } } @@ -448,4 +468,5 @@ class ActionsSupplierLink3 extends CommonHookActions return 0; } + } diff --git a/core/modules/modSupplierLink3.class.php b/core/modules/modSupplierLink3.class.php index 6d8780e..db06d78 100755 --- a/core/modules/modSupplierLink3.class.php +++ b/core/modules/modSupplierLink3.class.php @@ -76,7 +76,7 @@ class modSupplierLink3 extends DolibarrModules $this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@supplierlink3' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' - $this->version = '1.1'; + $this->version = '1.0'; // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt';