Initial Drupal 11 with DDEV setup
This commit is contained in:
		
							
								
								
									
										339
									
								
								web/core/lib/Drupal/Component/Gettext/LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								web/core/lib/Drupal/Component/Gettext/LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,339 @@
 | 
			
		||||
        GNU GENERAL PUBLIC LICENSE
 | 
			
		||||
           Version 2, June 1991
 | 
			
		||||
 | 
			
		||||
 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 | 
			
		||||
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 | 
			
		||||
 Everyone is permitted to copy and distribute verbatim copies
 | 
			
		||||
 of this license document, but changing it is not allowed.
 | 
			
		||||
 | 
			
		||||
          Preamble
 | 
			
		||||
 | 
			
		||||
  The licenses for most software are designed to take away your
 | 
			
		||||
freedom to share and change it.  By contrast, the GNU General Public
 | 
			
		||||
License is intended to guarantee your freedom to share and change free
 | 
			
		||||
software--to make sure the software is free for all its users.  This
 | 
			
		||||
General Public License applies to most of the Free Software
 | 
			
		||||
Foundation's software and to any other program whose authors commit to
 | 
			
		||||
using it.  (Some other Free Software Foundation software is covered by
 | 
			
		||||
the GNU Lesser General Public License instead.)  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
 | 
			
		||||
this service 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 make restrictions that forbid
 | 
			
		||||
anyone to deny you these rights or to ask you to surrender the rights.
 | 
			
		||||
These restrictions translate to certain responsibilities for you if you
 | 
			
		||||
distribute copies of the software, or if you modify it.
 | 
			
		||||
 | 
			
		||||
  For example, if you distribute copies of such a program, whether
 | 
			
		||||
gratis or for a fee, you must give the recipients all the rights that
 | 
			
		||||
you have.  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.
 | 
			
		||||
 | 
			
		||||
  We protect your rights with two steps: (1) copyright the software, and
 | 
			
		||||
(2) offer you this license which gives you legal permission to copy,
 | 
			
		||||
distribute and/or modify the software.
 | 
			
		||||
 | 
			
		||||
  Also, for each author's protection and ours, we want to make certain
 | 
			
		||||
that everyone understands that there is no warranty for this free
 | 
			
		||||
software.  If the software is modified by someone else and passed on, we
 | 
			
		||||
want its recipients to know that what they have is not the original, so
 | 
			
		||||
that any problems introduced by others will not reflect on the original
 | 
			
		||||
authors' reputations.
 | 
			
		||||
 | 
			
		||||
  Finally, any free program is threatened constantly by software
 | 
			
		||||
patents.  We wish to avoid the danger that redistributors of a free
 | 
			
		||||
program will individually obtain patent licenses, in effect making the
 | 
			
		||||
program proprietary.  To prevent this, we have made it clear that any
 | 
			
		||||
patent must be licensed for everyone's free use or not licensed at all.
 | 
			
		||||
 | 
			
		||||
  The precise terms and conditions for copying, distribution and
 | 
			
		||||
modification follow.
 | 
			
		||||
 | 
			
		||||
        GNU GENERAL PUBLIC LICENSE
 | 
			
		||||
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 | 
			
		||||
 | 
			
		||||
  0. This License applies to any program or other work which contains
 | 
			
		||||
a notice placed by the copyright holder saying it may be distributed
 | 
			
		||||
under the terms of this General Public License.  The "Program", below,
 | 
			
		||||
refers to any such program or work, and a "work based on the Program"
 | 
			
		||||
means either the Program or any derivative work under copyright law:
 | 
			
		||||
that is to say, a work containing the Program or a portion of it,
 | 
			
		||||
either verbatim or with modifications and/or translated into another
 | 
			
		||||
language.  (Hereinafter, translation is included without limitation in
 | 
			
		||||
the term "modification".)  Each licensee is addressed as "you".
 | 
			
		||||
 | 
			
		||||
Activities other than copying, distribution and modification are not
 | 
			
		||||
covered by this License; they are outside its scope.  The act of
 | 
			
		||||
running the Program is not restricted, and the output from the Program
 | 
			
		||||
is covered only if its contents constitute a work based on the
 | 
			
		||||
Program (independent of having been made by running the Program).
 | 
			
		||||
Whether that is true depends on what the Program does.
 | 
			
		||||
 | 
			
		||||
  1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
 | 
			
		||||
notices that refer to this License and to the absence of any warranty;
 | 
			
		||||
and give any other recipients of the Program a copy of this License
 | 
			
		||||
along with the Program.
 | 
			
		||||
 | 
			
		||||
You may charge a fee for the physical act of transferring a copy, and
 | 
			
		||||
you may at your option offer warranty protection in exchange for a fee.
 | 
			
		||||
 | 
			
		||||
  2. You may modify your copy or copies of the Program or any portion
 | 
			
		||||
of it, thus forming a work based on the Program, and copy and
 | 
			
		||||
distribute such modifications or work under the terms of Section 1
 | 
			
		||||
above, provided that you also meet all of these conditions:
 | 
			
		||||
 | 
			
		||||
    a) You must cause the modified files to carry prominent notices
 | 
			
		||||
    stating that you changed the files and the date of any change.
 | 
			
		||||
 | 
			
		||||
    b) You must cause any work that you distribute or publish, that in
 | 
			
		||||
    whole or in part contains or is derived from the Program or any
 | 
			
		||||
    part thereof, to be licensed as a whole at no charge to all third
 | 
			
		||||
    parties under the terms of this License.
 | 
			
		||||
 | 
			
		||||
    c) If the modified program normally reads commands interactively
 | 
			
		||||
    when run, you must cause it, when started running for such
 | 
			
		||||
    interactive use in the most ordinary way, to print or display an
 | 
			
		||||
    announcement including an appropriate copyright notice and a
 | 
			
		||||
    notice that there is no warranty (or else, saying that you provide
 | 
			
		||||
    a warranty) and that users may redistribute the program under
 | 
			
		||||
    these conditions, and telling the user how to view a copy of this
 | 
			
		||||
    License.  (Exception: if the Program itself is interactive but
 | 
			
		||||
    does not normally print such an announcement, your work based on
 | 
			
		||||
    the Program is not required to print an announcement.)
 | 
			
		||||
 | 
			
		||||
These requirements apply to the modified work as a whole.  If
 | 
			
		||||
identifiable sections of that work are not derived from the Program,
 | 
			
		||||
and can be reasonably considered independent and separate works in
 | 
			
		||||
themselves, then this License, and its terms, do not apply to those
 | 
			
		||||
sections when you distribute them as separate works.  But when you
 | 
			
		||||
distribute the same sections as part of a whole which is a work based
 | 
			
		||||
on the Program, the distribution of the whole must be on the terms of
 | 
			
		||||
this License, whose permissions for other licensees extend to the
 | 
			
		||||
entire whole, and thus to each and every part regardless of who wrote it.
 | 
			
		||||
 | 
			
		||||
Thus, it is not the intent of this section to claim rights or contest
 | 
			
		||||
your rights to work written entirely by you; rather, the intent is to
 | 
			
		||||
exercise the right to control the distribution of derivative or
 | 
			
		||||
collective works based on the Program.
 | 
			
		||||
 | 
			
		||||
In addition, mere aggregation of another work not based on the Program
 | 
			
		||||
with the Program (or with a work based on the Program) on a volume of
 | 
			
		||||
a storage or distribution medium does not bring the other work under
 | 
			
		||||
the scope of this License.
 | 
			
		||||
 | 
			
		||||
  3. You may copy and distribute the Program (or a work based on it,
 | 
			
		||||
under Section 2) in object code or executable form under the terms of
 | 
			
		||||
Sections 1 and 2 above provided that you also do one of the following:
 | 
			
		||||
 | 
			
		||||
    a) Accompany it with the complete corresponding machine-readable
 | 
			
		||||
    source code, which must be distributed under the terms of Sections
 | 
			
		||||
    1 and 2 above on a medium customarily used for software interchange; or,
 | 
			
		||||
 | 
			
		||||
    b) Accompany it with a written offer, valid for at least three
 | 
			
		||||
    years, to give any third party, for a charge no more than your
 | 
			
		||||
    cost of physically performing source distribution, a complete
 | 
			
		||||
    machine-readable copy of the corresponding source code, to be
 | 
			
		||||
    distributed under the terms of Sections 1 and 2 above on a medium
 | 
			
		||||
    customarily used for software interchange; or,
 | 
			
		||||
 | 
			
		||||
    c) Accompany it with the information you received as to the offer
 | 
			
		||||
    to distribute corresponding source code.  (This alternative is
 | 
			
		||||
    allowed only for noncommercial distribution and only if you
 | 
			
		||||
    received the program in object code or executable form with such
 | 
			
		||||
    an offer, in accord with Subsection b above.)
 | 
			
		||||
 | 
			
		||||
The source code for a work means the preferred form of the work for
 | 
			
		||||
making modifications to it.  For an executable work, complete source
 | 
			
		||||
code means all the source code for all modules it contains, plus any
 | 
			
		||||
associated interface definition files, plus the scripts used to
 | 
			
		||||
control compilation and installation of the executable.  However, as a
 | 
			
		||||
special exception, the source code distributed need not include
 | 
			
		||||
anything that is normally distributed (in either source or binary
 | 
			
		||||
form) with the major components (compiler, kernel, and so on) of the
 | 
			
		||||
operating system on which the executable runs, unless that component
 | 
			
		||||
itself accompanies the executable.
 | 
			
		||||
 | 
			
		||||
If distribution of executable or object code is made by offering
 | 
			
		||||
access to copy from a designated place, then offering equivalent
 | 
			
		||||
access to copy the source code from the same place counts as
 | 
			
		||||
distribution of the source code, even though third parties are not
 | 
			
		||||
compelled to copy the source along with the object code.
 | 
			
		||||
 | 
			
		||||
  4. You may not copy, modify, sublicense, or distribute the Program
 | 
			
		||||
except as expressly provided under this License.  Any attempt
 | 
			
		||||
otherwise to copy, modify, sublicense or distribute the Program is
 | 
			
		||||
void, and will automatically terminate your rights under this License.
 | 
			
		||||
However, parties who have received copies, or rights, from you under
 | 
			
		||||
this License will not have their licenses terminated so long as such
 | 
			
		||||
parties remain in full compliance.
 | 
			
		||||
 | 
			
		||||
  5. You are not required to accept this License, since you have not
 | 
			
		||||
signed it.  However, nothing else grants you permission to modify or
 | 
			
		||||
distribute the Program or its derivative works.  These actions are
 | 
			
		||||
prohibited by law if you do not accept this License.  Therefore, by
 | 
			
		||||
modifying or distributing the Program (or any work based on the
 | 
			
		||||
Program), you indicate your acceptance of this License to do so, and
 | 
			
		||||
all its terms and conditions for copying, distributing or modifying
 | 
			
		||||
the Program or works based on it.
 | 
			
		||||
 | 
			
		||||
  6. Each time you redistribute the Program (or any work based on the
 | 
			
		||||
Program), the recipient automatically receives a license from the
 | 
			
		||||
original licensor to copy, distribute or modify the Program subject to
 | 
			
		||||
these terms and conditions.  You may not impose any further
 | 
			
		||||
restrictions on the recipients' exercise of the rights granted herein.
 | 
			
		||||
You are not responsible for enforcing compliance by third parties to
 | 
			
		||||
this License.
 | 
			
		||||
 | 
			
		||||
  7. If, as a consequence of a court judgment or allegation of patent
 | 
			
		||||
infringement or for any other reason (not limited to patent issues),
 | 
			
		||||
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
 | 
			
		||||
distribute so as to satisfy simultaneously your obligations under this
 | 
			
		||||
License and any other pertinent obligations, then as a consequence you
 | 
			
		||||
may not distribute the Program at all.  For example, if a patent
 | 
			
		||||
license would not permit royalty-free redistribution of the Program by
 | 
			
		||||
all those who receive copies directly or indirectly through you, then
 | 
			
		||||
the only way you could satisfy both it and this License would be to
 | 
			
		||||
refrain entirely from distribution of the Program.
 | 
			
		||||
 | 
			
		||||
If any portion of this section is held invalid or unenforceable under
 | 
			
		||||
any particular circumstance, the balance of the section is intended to
 | 
			
		||||
apply and the section as a whole is intended to apply in other
 | 
			
		||||
circumstances.
 | 
			
		||||
 | 
			
		||||
It is not the purpose of this section to induce you to infringe any
 | 
			
		||||
patents or other property right claims or to contest validity of any
 | 
			
		||||
such claims; this section has the sole purpose of protecting the
 | 
			
		||||
integrity of the free software distribution system, which is
 | 
			
		||||
implemented by public license practices.  Many people have made
 | 
			
		||||
generous contributions to the wide range of software distributed
 | 
			
		||||
through that system in reliance on consistent application of that
 | 
			
		||||
system; it is up to the author/donor to decide if he or she is willing
 | 
			
		||||
to distribute software through any other system and a licensee cannot
 | 
			
		||||
impose that choice.
 | 
			
		||||
 | 
			
		||||
This section is intended to make thoroughly clear what is believed to
 | 
			
		||||
be a consequence of the rest of this License.
 | 
			
		||||
 | 
			
		||||
  8. If the distribution and/or use of the Program is restricted in
 | 
			
		||||
certain countries either by patents or by copyrighted interfaces, the
 | 
			
		||||
original copyright holder who places the Program under this License
 | 
			
		||||
may add an explicit geographical distribution limitation excluding
 | 
			
		||||
those countries, so that distribution is permitted only in or among
 | 
			
		||||
countries not thus excluded.  In such case, this License incorporates
 | 
			
		||||
the limitation as if written in the body of this License.
 | 
			
		||||
 | 
			
		||||
  9. The Free Software Foundation may publish revised and/or new versions
 | 
			
		||||
of the 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 a version number of this License which applies to it and "any
 | 
			
		||||
later version", you have the option of following the terms and conditions
 | 
			
		||||
either of that version or of any later version published by the Free
 | 
			
		||||
Software Foundation.  If the Program does not specify a version number of
 | 
			
		||||
this License, you may choose any version ever published by the Free Software
 | 
			
		||||
Foundation.
 | 
			
		||||
 | 
			
		||||
  10. If you wish to incorporate parts of the Program into other free
 | 
			
		||||
programs whose distribution conditions are different, write to the author
 | 
			
		||||
to ask for permission.  For software which is copyrighted by the Free
 | 
			
		||||
Software Foundation, write to the Free Software Foundation; we sometimes
 | 
			
		||||
make exceptions for this.  Our decision will be guided by the two goals
 | 
			
		||||
of preserving the free status of all derivatives of our free software and
 | 
			
		||||
of promoting the sharing and reuse of software generally.
 | 
			
		||||
 | 
			
		||||
          NO WARRANTY
 | 
			
		||||
 | 
			
		||||
  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
 | 
			
		||||
 | 
			
		||||
  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 | 
			
		||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
 | 
			
		||||
REDISTRIBUTE 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.
 | 
			
		||||
 | 
			
		||||
         END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
      How to Apply These Terms to Your New Programs
 | 
			
		||||
 | 
			
		||||
  If you develop a new program, and you want it to be of the greatest
 | 
			
		||||
possible use to the public, the best way to achieve this is to make it
 | 
			
		||||
free software which everyone can redistribute and change under these terms.
 | 
			
		||||
 | 
			
		||||
  To do so, attach the following notices to the program.  It is safest
 | 
			
		||||
to attach them to the start of each source file to most effectively
 | 
			
		||||
convey the exclusion of warranty; and each file should have at least
 | 
			
		||||
the "copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
    <one line to give the program's name and a brief idea of what it does.>
 | 
			
		||||
    Copyright (C) <year>  <name of author>
 | 
			
		||||
 | 
			
		||||
    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 2 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, write to the Free Software Foundation, Inc.,
 | 
			
		||||
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 | 
			
		||||
Also add information on how to contact you by electronic and paper mail.
 | 
			
		||||
 | 
			
		||||
If the program is interactive, make it output a short notice like this
 | 
			
		||||
when it starts in an interactive mode:
 | 
			
		||||
 | 
			
		||||
    Gnomovision version 69, Copyright (C) year name of author
 | 
			
		||||
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
 | 
			
		||||
    This is free software, and you are welcome to redistribute it
 | 
			
		||||
    under certain conditions; type `show c' for details.
 | 
			
		||||
 | 
			
		||||
The hypothetical commands `show w' and `show c' should show the appropriate
 | 
			
		||||
parts of the General Public License.  Of course, the commands you use may
 | 
			
		||||
be called something other than `show w' and `show c'; they could even be
 | 
			
		||||
mouse-clicks or menu items--whatever suits your program.
 | 
			
		||||
 | 
			
		||||
You should also get your employer (if you work as a programmer) or your
 | 
			
		||||
school, if any, to sign a "copyright disclaimer" for the program, if
 | 
			
		||||
necessary.  Here is a sample; alter the names:
 | 
			
		||||
 | 
			
		||||
  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
 | 
			
		||||
  `Gnomovision' (which makes passes at compilers) written by James Hacker.
 | 
			
		||||
 | 
			
		||||
  <signature of Ty Coon>, 1 April 1989
 | 
			
		||||
  Ty Coon, President of Vice
 | 
			
		||||
 | 
			
		||||
This General Public License does not permit incorporating your program into
 | 
			
		||||
proprietary programs.  If your program is a subroutine library, you may
 | 
			
		||||
consider it more useful to permit linking proprietary applications with the
 | 
			
		||||
library.  If this is what you want to do, use the GNU Lesser General
 | 
			
		||||
Public License instead of this License.
 | 
			
		||||
							
								
								
									
										605
									
								
								web/core/lib/Drupal/Component/Gettext/PoHeader.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										605
									
								
								web/core/lib/Drupal/Component/Gettext/PoHeader.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,605 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Gettext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gettext PO header handler.
 | 
			
		||||
 *
 | 
			
		||||
 * Possible Gettext PO header elements are explained in
 | 
			
		||||
 * http://www.gnu.org/software/gettext/manual/gettext.html#Header-Entry,
 | 
			
		||||
 * but we only support a subset of these directly.
 | 
			
		||||
 *
 | 
			
		||||
 * Example header:
 | 
			
		||||
 *
 | 
			
		||||
 * "Project-Id-Version: Drupal core (7.11)\n"
 | 
			
		||||
 * "POT-Creation-Date: 2012-02-12 22:59+0000\n"
 | 
			
		||||
 * "PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
 | 
			
		||||
 * "Language-Team: Catalan\n"
 | 
			
		||||
 * "MIME-Version: 1.0\n"
 | 
			
		||||
 * "Content-Type: text/plain; charset=utf-8\n"
 | 
			
		||||
 * "Content-Transfer-Encoding: 8bit\n"
 | 
			
		||||
 * "Plural-Forms: nplurals=2; plural=(n>1);\n"
 | 
			
		||||
 */
 | 
			
		||||
class PoHeader {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Language code.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $langcode;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Formula for the plural form.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $pluralForms;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Author(s) of the file.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $authors;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Date the po file got created.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $poDate;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Human readable language name.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageName;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Name of the project the translation belongs to.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $projectName;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor, creates a PoHeader with default values.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   Language code.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($langcode = NULL) {
 | 
			
		||||
    $this->langcode = $langcode;
 | 
			
		||||
    // Ignore errors when run during site installation before
 | 
			
		||||
    // date_default_timezone_set() is called.
 | 
			
		||||
    $this->poDate = @date("Y-m-d H:iO");
 | 
			
		||||
    $this->pluralForms = 'nplurals=2; plural=(n > 1);';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the plural form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Plural form component from the header, for example:
 | 
			
		||||
   *   'nplurals=2; plural=(n > 1);'.
 | 
			
		||||
   */
 | 
			
		||||
  public function getPluralForms() {
 | 
			
		||||
    return $this->pluralForms;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the human readable language name.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $languageName
 | 
			
		||||
   *   Human readable language name.
 | 
			
		||||
   */
 | 
			
		||||
  public function setLanguageName($languageName) {
 | 
			
		||||
    $this->languageName = $languageName;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the human readable language name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The human readable language name.
 | 
			
		||||
   */
 | 
			
		||||
  public function getLanguageName() {
 | 
			
		||||
    return $this->languageName;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the project name.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $projectName
 | 
			
		||||
   *   Human readable project name.
 | 
			
		||||
   */
 | 
			
		||||
  public function setProjectName($projectName) {
 | 
			
		||||
    $this->projectName = $projectName;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the project name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The human readable project name.
 | 
			
		||||
   */
 | 
			
		||||
  public function getProjectName() {
 | 
			
		||||
    return $this->projectName;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Populate internal values from a string.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $header
 | 
			
		||||
   *   Full header string with key-value pairs.
 | 
			
		||||
   */
 | 
			
		||||
  public function setFromString($header) {
 | 
			
		||||
    // Get an array of all header values for processing.
 | 
			
		||||
    $values = $this->parseHeader($header);
 | 
			
		||||
 | 
			
		||||
    // There is only one value relevant for our header implementation when
 | 
			
		||||
    // reading, and that is the plural formula.
 | 
			
		||||
    if (!empty($values['Plural-Forms'])) {
 | 
			
		||||
      $this->pluralForms = $values['Plural-Forms'];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generate a Gettext PO formatted header string based on data set earlier.
 | 
			
		||||
   */
 | 
			
		||||
  public function __toString() {
 | 
			
		||||
    $output = '';
 | 
			
		||||
 | 
			
		||||
    $isTemplate = empty($this->languageName);
 | 
			
		||||
 | 
			
		||||
    $output .= '# ' . ($isTemplate ? 'LANGUAGE' : $this->languageName) . ' translation of ' . ($isTemplate ? 'PROJECT' : $this->projectName) . "\n";
 | 
			
		||||
    if (!empty($this->authors)) {
 | 
			
		||||
      $output .= '# Generated by ' . implode("\n# ", $this->authors) . "\n";
 | 
			
		||||
    }
 | 
			
		||||
    $output .= "#\n";
 | 
			
		||||
 | 
			
		||||
    // Add the actual header information.
 | 
			
		||||
    $output .= "msgid \"\"\n";
 | 
			
		||||
    $output .= "msgstr \"\"\n";
 | 
			
		||||
    $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
 | 
			
		||||
    $output .= "\"POT-Creation-Date: " . $this->poDate . "\\n\"\n";
 | 
			
		||||
    $output .= "\"PO-Revision-Date: " . $this->poDate . "\\n\"\n";
 | 
			
		||||
    $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
 | 
			
		||||
    $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
 | 
			
		||||
    $output .= "\"MIME-Version: 1.0\\n\"\n";
 | 
			
		||||
    $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
 | 
			
		||||
    $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
 | 
			
		||||
    $output .= "\"Plural-Forms: " . $this->pluralForms . "\\n\"\n";
 | 
			
		||||
    $output .= "\n";
 | 
			
		||||
 | 
			
		||||
    return $output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Parses a Plural-Forms entry from a Gettext Portable Object file header.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $plural_forms
 | 
			
		||||
   *   The Plural-Forms entry value.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array|bool
 | 
			
		||||
   *   An indexed array of parsed plural formula data. Containing:
 | 
			
		||||
   *   - 'nplurals': The number of plural forms defined by the plural formula.
 | 
			
		||||
   *   - 'plurals': Array of plural positions keyed by plural value.
 | 
			
		||||
   *   False when there is no plural string.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   */
 | 
			
		||||
  public function parsePluralForms($plural_forms) {
 | 
			
		||||
    $plurals = [];
 | 
			
		||||
    // First, delete all whitespace.
 | 
			
		||||
    $plural_forms = strtr($plural_forms, [" " => "", "\t" => ""]);
 | 
			
		||||
 | 
			
		||||
    // Select the parts that define nplurals and plural.
 | 
			
		||||
    $nplurals = strstr($plural_forms, "nplurals=");
 | 
			
		||||
    if (strpos($nplurals, ";")) {
 | 
			
		||||
      // We want the string from the 10th char, because "nplurals=" length is 9.
 | 
			
		||||
      $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
    $plural = strstr($plural_forms, "plural=");
 | 
			
		||||
    if (strpos($plural, ";")) {
 | 
			
		||||
      // We want the string from the 8th char, because "plural=" length is 7.
 | 
			
		||||
      $plural = substr($plural, 7, strpos($plural, ";") - 7);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the number of plurals is zero, we return a default result.
 | 
			
		||||
    if ($nplurals == 0) {
 | 
			
		||||
      return [$nplurals, ['default' => 0]];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate possible plural positions of different plural values. All known
 | 
			
		||||
    // plural formula's are repetitive above 100.
 | 
			
		||||
    // For data compression we store the last position the array value
 | 
			
		||||
    // changes and store it as default.
 | 
			
		||||
    $element_stack = $this->parseArithmetic($plural);
 | 
			
		||||
    if ($element_stack !== FALSE) {
 | 
			
		||||
      for ($i = 0; $i <= 199; $i++) {
 | 
			
		||||
        $plurals[$i] = $this->evaluatePlural($element_stack, $i);
 | 
			
		||||
      }
 | 
			
		||||
      $default = $plurals[$i - 1];
 | 
			
		||||
      $plurals = array_filter($plurals, function ($value) use ($default) {
 | 
			
		||||
        return ($value != $default);
 | 
			
		||||
      });
 | 
			
		||||
      $plurals['default'] = $default;
 | 
			
		||||
 | 
			
		||||
      return [$nplurals, $plurals];
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      throw new \Exception('The plural formula could not be parsed.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Parses a Gettext Portable Object file header.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $header
 | 
			
		||||
   *   A string containing the complete header.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array of key-value pairs.
 | 
			
		||||
   */
 | 
			
		||||
  private function parseHeader($header) {
 | 
			
		||||
    $header_parsed = [];
 | 
			
		||||
    $lines = array_map('trim', explode("\n", $header));
 | 
			
		||||
    foreach ($lines as $line) {
 | 
			
		||||
      if ($line) {
 | 
			
		||||
        [$tag, $contents] = explode(":", $line, 2);
 | 
			
		||||
        $header_parsed[trim($tag)] = trim($contents);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $header_parsed;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Parses and sanitizes an arithmetic formula into a plural element stack.
 | 
			
		||||
   *
 | 
			
		||||
   * While parsing, we ensure, that the operators have the right
 | 
			
		||||
   * precedence and associativity.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   A string containing the arithmetic formula.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array|bool
 | 
			
		||||
   *   A stack of values and operations to be evaluated. False if the formula
 | 
			
		||||
   *   could not be parsed.
 | 
			
		||||
   */
 | 
			
		||||
  private function parseArithmetic($string) {
 | 
			
		||||
    // Operator precedence table.
 | 
			
		||||
    $precedence = [
 | 
			
		||||
      "(" => -1,
 | 
			
		||||
      ")" => -1,
 | 
			
		||||
      "?" => 1,
 | 
			
		||||
      ":" => 1,
 | 
			
		||||
      "||" => 3,
 | 
			
		||||
      "&&" => 4,
 | 
			
		||||
      "==" => 5,
 | 
			
		||||
      "!=" => 5,
 | 
			
		||||
      "<" => 6,
 | 
			
		||||
      ">" => 6,
 | 
			
		||||
      "<=" => 6,
 | 
			
		||||
      ">=" => 6,
 | 
			
		||||
      "+" => 7,
 | 
			
		||||
      "-" => 7,
 | 
			
		||||
      "*" => 8,
 | 
			
		||||
      "/" => 8,
 | 
			
		||||
      "%" => 8,
 | 
			
		||||
    ];
 | 
			
		||||
    // Right associativity.
 | 
			
		||||
    $right_associativity = ["?" => 1, ":" => 1];
 | 
			
		||||
 | 
			
		||||
    $tokens = $this->tokenizeFormula($string);
 | 
			
		||||
 | 
			
		||||
    // Parse by converting into infix notation then back into postfix
 | 
			
		||||
    // Operator stack - holds math operators and symbols.
 | 
			
		||||
    $operator_stack = [];
 | 
			
		||||
    // Element Stack - holds data to be operated on.
 | 
			
		||||
    $element_stack = [];
 | 
			
		||||
 | 
			
		||||
    foreach ($tokens as $token) {
 | 
			
		||||
      $current_token = $token;
 | 
			
		||||
 | 
			
		||||
      // Numbers and the $n variable are simply pushed into $element_stack.
 | 
			
		||||
      if (is_numeric($token)) {
 | 
			
		||||
        $element_stack[] = $current_token;
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($current_token == "n") {
 | 
			
		||||
        $element_stack[] = '$n';
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($current_token == "(") {
 | 
			
		||||
        $operator_stack[] = $current_token;
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($current_token == ")") {
 | 
			
		||||
        $top_op = array_pop($operator_stack);
 | 
			
		||||
        while (isset($top_op) && ($top_op != "(")) {
 | 
			
		||||
          $element_stack[] = $top_op;
 | 
			
		||||
          $top_op = array_pop($operator_stack);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!empty($precedence[$current_token])) {
 | 
			
		||||
        // If it's an operator, then pop from $operator_stack into
 | 
			
		||||
        // $element_stack until the precedence in $operator_stack is less
 | 
			
		||||
        // than current, then push into $operator_stack.
 | 
			
		||||
        $top_op = array_pop($operator_stack);
 | 
			
		||||
        while (isset($top_op) && ($precedence[$top_op] >= $precedence[$current_token]) && !(($precedence[$top_op] == $precedence[$current_token]) && !empty($right_associativity[$top_op]) && !empty($right_associativity[$current_token]))) {
 | 
			
		||||
          $element_stack[] = $top_op;
 | 
			
		||||
          $top_op = array_pop($operator_stack);
 | 
			
		||||
        }
 | 
			
		||||
        if ($top_op) {
 | 
			
		||||
          // Return element to top.
 | 
			
		||||
          $operator_stack[] = $top_op;
 | 
			
		||||
        }
 | 
			
		||||
        // Parentheses are not needed.
 | 
			
		||||
        $operator_stack[] = $current_token;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        return FALSE;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Flush operator stack.
 | 
			
		||||
    $top_op = array_pop($operator_stack);
 | 
			
		||||
    while ($top_op != NULL) {
 | 
			
		||||
      $element_stack[] = $top_op;
 | 
			
		||||
      $top_op = array_pop($operator_stack);
 | 
			
		||||
    }
 | 
			
		||||
    $return = $element_stack;
 | 
			
		||||
 | 
			
		||||
    // Now validate stack.
 | 
			
		||||
    $previous_size = count($element_stack) + 1;
 | 
			
		||||
    while (count($element_stack) < $previous_size) {
 | 
			
		||||
      $previous_size = count($element_stack);
 | 
			
		||||
      for ($i = 2; $i < count($element_stack); $i++) {
 | 
			
		||||
        $op = $element_stack[$i];
 | 
			
		||||
        if (!empty($precedence[$op])) {
 | 
			
		||||
          if ($op == ":") {
 | 
			
		||||
            $f = $element_stack[$i - 2] . "):" . $element_stack[$i - 1] . ")";
 | 
			
		||||
          }
 | 
			
		||||
          elseif ($op == "?") {
 | 
			
		||||
            $f = "(" . $element_stack[$i - 2] . "?(" . $element_stack[$i - 1];
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            $f = "(" . $element_stack[$i - 2] . $op . $element_stack[$i - 1] . ")";
 | 
			
		||||
          }
 | 
			
		||||
          array_splice($element_stack, $i - 2, 3, $f);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If only one element is left, the number of operators is appropriate.
 | 
			
		||||
    return count($element_stack) == 1 ? $return : FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tokenize the formula.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $formula
 | 
			
		||||
   *   A string containing the arithmetic formula.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   List of arithmetic tokens identified in the formula.
 | 
			
		||||
   */
 | 
			
		||||
  private function tokenizeFormula($formula) {
 | 
			
		||||
    $formula = str_replace(" ", "", $formula);
 | 
			
		||||
    $tokens = [];
 | 
			
		||||
    for ($i = 0; $i < strlen($formula); $i++) {
 | 
			
		||||
      if (is_numeric($formula[$i])) {
 | 
			
		||||
        $num = $formula[$i];
 | 
			
		||||
        $j = $i + 1;
 | 
			
		||||
        while ($j < strlen($formula) && is_numeric($formula[$j])) {
 | 
			
		||||
          $num .= $formula[$j];
 | 
			
		||||
          $j++;
 | 
			
		||||
        }
 | 
			
		||||
        $i = $j - 1;
 | 
			
		||||
        $tokens[] = $num;
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($pos = strpos(" =<>!&|", $formula[$i])) {
 | 
			
		||||
        $next = $formula[$i + 1];
 | 
			
		||||
        switch ($pos) {
 | 
			
		||||
          case 1:
 | 
			
		||||
          case 2:
 | 
			
		||||
          case 3:
 | 
			
		||||
          case 4:
 | 
			
		||||
            if ($next == '=') {
 | 
			
		||||
              $tokens[] = $formula[$i] . '=';
 | 
			
		||||
              $i++;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
              $tokens[] = $formula[$i];
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case 5:
 | 
			
		||||
            if ($next == '&') {
 | 
			
		||||
              $tokens[] = '&&';
 | 
			
		||||
              $i++;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
              $tokens[] = $formula[$i];
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case 6:
 | 
			
		||||
            if ($next == '|') {
 | 
			
		||||
              $tokens[] = '||';
 | 
			
		||||
              $i++;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
              $tokens[] = $formula[$i];
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $tokens[] = $formula[$i];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $tokens;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Evaluate the plural element stack using a plural value.
 | 
			
		||||
   *
 | 
			
		||||
   * Using an element stack, which represents a plural formula, we calculate
 | 
			
		||||
   * which plural string should be used for a given plural value.
 | 
			
		||||
   *
 | 
			
		||||
   * An example of plural formula parting and evaluation:
 | 
			
		||||
   *   Plural formula: 'n!=1'
 | 
			
		||||
   * This formula is parsed by parseArithmetic() to a stack (array) of elements:
 | 
			
		||||
   * @code
 | 
			
		||||
   *   [
 | 
			
		||||
   *     0 => '$n',
 | 
			
		||||
   *     1 => '1',
 | 
			
		||||
   *     2 => '!=',
 | 
			
		||||
   *   ];
 | 
			
		||||
   * @endcode
 | 
			
		||||
   * The evaluatePlural() method evaluates the $element_stack using the plural
 | 
			
		||||
   * value $n. Before the actual evaluation, the '$n' in the array is replaced
 | 
			
		||||
   * by the value of $n.
 | 
			
		||||
   *   For example: $n = 2 results in:
 | 
			
		||||
   * @code
 | 
			
		||||
   *   [
 | 
			
		||||
   *     0 => '2',
 | 
			
		||||
   *     1 => '1',
 | 
			
		||||
   *     2 => '!=',
 | 
			
		||||
   *   ]
 | 
			
		||||
   * @endcode
 | 
			
		||||
   * The stack is processed until only one element is (the result) is left. In
 | 
			
		||||
   * every iteration the top elements of the stack, up until the first operator,
 | 
			
		||||
   * are evaluated. After evaluation the arguments and the operator itself are
 | 
			
		||||
   * removed and replaced by the evaluation result. This is typically 2
 | 
			
		||||
   * arguments and 1 element for the operator.
 | 
			
		||||
   *   Because the operator is '!=' the example stack is evaluated as:
 | 
			
		||||
   *   $f = (int) 2 != 1;
 | 
			
		||||
   *   The resulting stack is:
 | 
			
		||||
   * @code
 | 
			
		||||
   *   [
 | 
			
		||||
   *     0 => 1,
 | 
			
		||||
   *   ]
 | 
			
		||||
   * @endcode
 | 
			
		||||
   * With only one element left in the stack (the final result) the loop is
 | 
			
		||||
   * terminated and the result is returned.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $element_stack
 | 
			
		||||
   *   Array of plural formula values and operators create by parseArithmetic().
 | 
			
		||||
   * @param int $n
 | 
			
		||||
   *   The @count number for which we are determining the right plural position.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   Number of the plural string to be used for the given plural value.
 | 
			
		||||
   *
 | 
			
		||||
   * @see parseArithmetic()
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   */
 | 
			
		||||
  protected function evaluatePlural($element_stack, $n) {
 | 
			
		||||
    $count = count($element_stack);
 | 
			
		||||
    $limit = $count;
 | 
			
		||||
    // Replace the '$n' value in the formula by the plural value.
 | 
			
		||||
    for ($i = 0; $i < $count; $i++) {
 | 
			
		||||
      if ($element_stack[$i] === '$n') {
 | 
			
		||||
        $element_stack[$i] = $n;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // We process the stack until only one element is (the result) is left.
 | 
			
		||||
    // We limit the number of evaluation cycles to prevent an endless loop in
 | 
			
		||||
    // case the stack contains an error.
 | 
			
		||||
    while (isset($element_stack[1])) {
 | 
			
		||||
      for ($i = 2; $i < $count; $i++) {
 | 
			
		||||
        // There's no point in checking non-symbols. Also, switch(TRUE) would
 | 
			
		||||
        // match any case and so it would break.
 | 
			
		||||
        if (is_bool($element_stack[$i]) || is_numeric($element_stack[$i])) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        $f = NULL;
 | 
			
		||||
        $length = 3;
 | 
			
		||||
        $delta = 2;
 | 
			
		||||
        switch ($element_stack[$i]) {
 | 
			
		||||
          case '==':
 | 
			
		||||
            $f = $element_stack[$i - 2] == $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '!=':
 | 
			
		||||
            $f = $element_stack[$i - 2] != $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '<=':
 | 
			
		||||
            $f = $element_stack[$i - 2] <= $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '>=':
 | 
			
		||||
            $f = $element_stack[$i - 2] >= $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '<':
 | 
			
		||||
            $f = $element_stack[$i - 2] < $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '>':
 | 
			
		||||
            $f = $element_stack[$i - 2] > $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '+':
 | 
			
		||||
            $f = $element_stack[$i - 2] + $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '-':
 | 
			
		||||
            $f = $element_stack[$i - 2] - $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '*':
 | 
			
		||||
            $f = $element_stack[$i - 2] * $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '/':
 | 
			
		||||
            $f = $element_stack[$i - 2] / $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '%':
 | 
			
		||||
            $f = $element_stack[$i - 2] % $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '&&':
 | 
			
		||||
            $f = $element_stack[$i - 2] && $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case '||':
 | 
			
		||||
            $f = $element_stack[$i - 2] || $element_stack[$i - 1];
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case ':':
 | 
			
		||||
            $f = $element_stack[$i - 3] ? $element_stack[$i - 2] : $element_stack[$i - 1];
 | 
			
		||||
            // This operator has 3 preceding elements, instead of the default 2.
 | 
			
		||||
            $length = 5;
 | 
			
		||||
            $delta = 3;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If the element is an operator we remove the processed elements and
 | 
			
		||||
        // store the result.
 | 
			
		||||
        if (isset($f)) {
 | 
			
		||||
          array_splice($element_stack, $i - $delta, $length, $f);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (!$limit) {
 | 
			
		||||
      throw new \Exception('The plural formula could not be evaluated.');
 | 
			
		||||
    }
 | 
			
		||||
    return (int) $element_stack[0];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										301
									
								
								web/core/lib/Drupal/Component/Gettext/PoItem.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								web/core/lib/Drupal/Component/Gettext/PoItem.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,301 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Gettext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * PoItem handles one translation.
 | 
			
		||||
 *
 | 
			
		||||
 * @todo This class contains some really old legacy code.
 | 
			
		||||
 * @see https://www.drupal.org/node/1637662
 | 
			
		||||
 */
 | 
			
		||||
class PoItem {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The delimiter used to split plural strings.
 | 
			
		||||
   *
 | 
			
		||||
   * This is the ETX (End of text) character and is used as a minimal means to
 | 
			
		||||
   * separate singular and plural variants in source and translation text. It
 | 
			
		||||
   * was found to be the most compatible delimiter for the supported databases.
 | 
			
		||||
   */
 | 
			
		||||
  const DELIMITER = "\03";
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The language code this translation is in.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $langcode;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The context this translation belongs to.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $context = '';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The source string or array of strings if it has plurals.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string|array
 | 
			
		||||
   *
 | 
			
		||||
   * @see $plural
 | 
			
		||||
   */
 | 
			
		||||
  protected $source;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Flag indicating if this translation has plurals.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected $plural;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The comment of this translation.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $comment;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The translation string or array of strings if it has plurals.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string|array
 | 
			
		||||
   * @see $plural
 | 
			
		||||
   */
 | 
			
		||||
  protected $translation;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the language code of the currently used language.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The language code for this item.
 | 
			
		||||
   */
 | 
			
		||||
  public function getLangcode() {
 | 
			
		||||
    return $this->langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the language code of the current language.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   The language code of the current language.
 | 
			
		||||
   */
 | 
			
		||||
  public function setLangcode($langcode) {
 | 
			
		||||
    $this->langcode = $langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the context this translation belongs to.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The context for this translation.
 | 
			
		||||
   */
 | 
			
		||||
  public function getContext() {
 | 
			
		||||
    return $this->context;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the context this translation belongs to.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $context
 | 
			
		||||
   *   The context this translation belongs to.
 | 
			
		||||
   */
 | 
			
		||||
  public function setContext($context) {
 | 
			
		||||
    $this->context = $context;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the source string(s) if the translation has plurals.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string|array
 | 
			
		||||
   *   The source string or array of strings if it has plurals.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSource() {
 | 
			
		||||
    return $this->source;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the source string(s) if the translation has plurals.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string|array $source
 | 
			
		||||
   *   The source string or the array of strings if the translation has plurals.
 | 
			
		||||
   */
 | 
			
		||||
  public function setSource($source) {
 | 
			
		||||
    $this->source = $source;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the translation string(s) if the translation has plurals.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string|array
 | 
			
		||||
   *   The translation string or array of strings if it has plurals.
 | 
			
		||||
   */
 | 
			
		||||
  public function getTranslation() {
 | 
			
		||||
    return $this->translation;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the translation string(s) if the translation has plurals.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string|array $translation
 | 
			
		||||
   *   The translation string or the array of strings if the translation has
 | 
			
		||||
   *   plurals.
 | 
			
		||||
   */
 | 
			
		||||
  public function setTranslation($translation) {
 | 
			
		||||
    $this->translation = $translation;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set if the translation has plural values.
 | 
			
		||||
   *
 | 
			
		||||
   * @param bool $plural
 | 
			
		||||
   *   TRUE, if the translation has plural values. FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function setPlural($plural) {
 | 
			
		||||
    $this->plural = $plural;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get if the translation has plural values.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the translation has plurals, otherwise FALSE.
 | 
			
		||||
   */
 | 
			
		||||
  public function isPlural() {
 | 
			
		||||
    return $this->plural;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the comment of this translation.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The comment of this translation.
 | 
			
		||||
   */
 | 
			
		||||
  public function getComment() {
 | 
			
		||||
    return $this->comment;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the comment of this translation.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $comment
 | 
			
		||||
   *   The comment of this translation.
 | 
			
		||||
   */
 | 
			
		||||
  public function setComment($comment) {
 | 
			
		||||
    $this->comment = $comment;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create the PoItem from a structured array.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   A structured array to create the PoItem from.
 | 
			
		||||
   */
 | 
			
		||||
  public function setFromArray(array $values = []) {
 | 
			
		||||
    if (isset($values['context'])) {
 | 
			
		||||
      $this->setContext($values['context']);
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($values['source'])) {
 | 
			
		||||
      $this->setSource($values['source']);
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($values['translation'])) {
 | 
			
		||||
      $this->setTranslation($values['translation']);
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($values['comment'])) {
 | 
			
		||||
      $this->setComment($values['comment']);
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($this->source) && str_contains($this->source, self::DELIMITER)) {
 | 
			
		||||
      $this->setSource(explode(self::DELIMITER, $this->source));
 | 
			
		||||
      $this->setTranslation(explode(self::DELIMITER, $this->translation ?? ''));
 | 
			
		||||
      $this->setPlural(count($this->source) > 1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Output the PoItem as a string.
 | 
			
		||||
   */
 | 
			
		||||
  public function __toString() {
 | 
			
		||||
    return $this->formatItem();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Format the POItem as a string.
 | 
			
		||||
   */
 | 
			
		||||
  private function formatItem() {
 | 
			
		||||
    $output = '';
 | 
			
		||||
 | 
			
		||||
    // Format string context.
 | 
			
		||||
    if (!empty($this->context)) {
 | 
			
		||||
      $output .= 'msgctxt ' . $this->formatString($this->context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Format translation.
 | 
			
		||||
    if ($this->plural) {
 | 
			
		||||
      $output .= $this->formatPlural();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $output .= $this->formatSingular();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add one empty line to separate the translations.
 | 
			
		||||
    $output .= "\n";
 | 
			
		||||
 | 
			
		||||
    return $output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Formats a plural translation.
 | 
			
		||||
   */
 | 
			
		||||
  private function formatPlural() {
 | 
			
		||||
    $output = '';
 | 
			
		||||
 | 
			
		||||
    // Format source strings.
 | 
			
		||||
    $output .= 'msgid ' . $this->formatString($this->source[0]);
 | 
			
		||||
    $output .= 'msgid_plural ' . $this->formatString($this->source[1]);
 | 
			
		||||
 | 
			
		||||
    foreach ($this->translation as $i => $trans) {
 | 
			
		||||
      if (isset($this->translation[$i])) {
 | 
			
		||||
        $output .= 'msgstr[' . $i . '] ' . $this->formatString($trans);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $output .= 'msgstr[' . $i . '] ""' . "\n";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Formats a singular translation.
 | 
			
		||||
   */
 | 
			
		||||
  private function formatSingular() {
 | 
			
		||||
    $output = '';
 | 
			
		||||
    $output .= 'msgid ' . $this->formatString($this->source);
 | 
			
		||||
    $output .= 'msgstr ' . (isset($this->translation) ? $this->formatString($this->translation) : '""');
 | 
			
		||||
    return $output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Formats a string for output on multiple lines.
 | 
			
		||||
   */
 | 
			
		||||
  private function formatString($string) {
 | 
			
		||||
    // Escape characters for processing.
 | 
			
		||||
    $string = addcslashes($string, "\0..\37\\\"");
 | 
			
		||||
 | 
			
		||||
    // Always include a line break after the explicit \n line breaks from
 | 
			
		||||
    // the source string. Otherwise wrap at 70 chars to accommodate the extra
 | 
			
		||||
    // format overhead too.
 | 
			
		||||
    $parts = explode("\n", wordwrap(str_replace('\n', "\\n\n", $string), 70, " \n"));
 | 
			
		||||
 | 
			
		||||
    // Multiline string should be exported starting with a "" and newline to
 | 
			
		||||
    // have all lines aligned on the same column.
 | 
			
		||||
    if (count($parts) > 1) {
 | 
			
		||||
      return "\"\"\n\"" . implode("\"\n\"", $parts) . "\"\n";
 | 
			
		||||
    }
 | 
			
		||||
    // Single line strings are output on the same line.
 | 
			
		||||
    else {
 | 
			
		||||
      return "\"$parts[0]\"\n";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								web/core/lib/Drupal/Component/Gettext/PoMemoryWriter.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								web/core/lib/Drupal/Component/Gettext/PoMemoryWriter.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Gettext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a Gettext PO memory writer, to be used by the installer.
 | 
			
		||||
 */
 | 
			
		||||
class PoMemoryWriter implements PoWriterInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Array to hold all PoItem elements.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $items;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor, initialize empty items.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct() {
 | 
			
		||||
    $this->items = [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function writeItem(PoItem $item) {
 | 
			
		||||
    if (is_array($item->getSource())) {
 | 
			
		||||
      $item->setSource(implode(PoItem::DELIMITER, $item->getSource()));
 | 
			
		||||
      $item->setTranslation(implode(PoItem::DELIMITER, $item->getTranslation()));
 | 
			
		||||
    }
 | 
			
		||||
    $context = $item->getContext();
 | 
			
		||||
    $this->items[$context != NULL ? $context : ''][$item->getSource()] = $item->getTranslation();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function writeItems(PoReaderInterface $reader, $count = -1) {
 | 
			
		||||
    $forever = $count == -1;
 | 
			
		||||
    while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
 | 
			
		||||
      $this->writeItem($item);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get all stored PoItem's.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Array of all PoItem elements.
 | 
			
		||||
   */
 | 
			
		||||
  public function getData() {
 | 
			
		||||
    return $this->items;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoMetadataInterface:setLangcode().
 | 
			
		||||
   *
 | 
			
		||||
   * Not implemented. Not relevant for the MemoryWriter.
 | 
			
		||||
   */
 | 
			
		||||
  public function setLangcode($langcode) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoMetadataInterface:getLangcode().
 | 
			
		||||
   *
 | 
			
		||||
   * Not implemented. Not relevant for the MemoryWriter.
 | 
			
		||||
   */
 | 
			
		||||
  public function getLangcode() {
 | 
			
		||||
    throw new \LogicException(__METHOD__ . '() not implemented. Not relevant for the MemoryWriter');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoMetadataInterface:getHeader().
 | 
			
		||||
   *
 | 
			
		||||
   * Not implemented. Not relevant for the MemoryWriter.
 | 
			
		||||
   */
 | 
			
		||||
  public function getHeader() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoMetadataInterface:setHeader().
 | 
			
		||||
   *
 | 
			
		||||
   * Not implemented. Not relevant for the MemoryWriter.
 | 
			
		||||
   */
 | 
			
		||||
  public function setHeader(PoHeader $header) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Gettext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Methods required for both reader and writer implementations.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\Component\Gettext\PoReaderInterface
 | 
			
		||||
 * @see \Drupal\Component\Gettext\PoWriterInterface
 | 
			
		||||
 */
 | 
			
		||||
interface PoMetadataInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set language code.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   Language code string.
 | 
			
		||||
   */
 | 
			
		||||
  public function setLangcode($langcode);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get language code.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Language code string.
 | 
			
		||||
   */
 | 
			
		||||
  public function getLangcode();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set header metadata.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Component\Gettext\PoHeader $header
 | 
			
		||||
   *   Header object representing metadata in a PO header.
 | 
			
		||||
   */
 | 
			
		||||
  public function setHeader(PoHeader $header);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get header metadata.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Component\Gettext\PoHeader
 | 
			
		||||
   *   Header instance representing metadata in a PO header.
 | 
			
		||||
   */
 | 
			
		||||
  public function getHeader();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								web/core/lib/Drupal/Component/Gettext/PoReaderInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								web/core/lib/Drupal/Component/Gettext/PoReaderInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Gettext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Shared interface definition for all Gettext PO Readers.
 | 
			
		||||
 */
 | 
			
		||||
interface PoReaderInterface extends PoMetadataInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reads and returns a PoItem (source/translation pair).
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Component\Gettext\PoItem
 | 
			
		||||
   *   Wrapper for item data instance.
 | 
			
		||||
   */
 | 
			
		||||
  public function readItem();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								web/core/lib/Drupal/Component/Gettext/PoStreamInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								web/core/lib/Drupal/Component/Gettext/PoStreamInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Gettext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Common functions for file/stream based PO readers/writers.
 | 
			
		||||
 *
 | 
			
		||||
 * @see PoReaderInterface
 | 
			
		||||
 * @see PoWriterInterface
 | 
			
		||||
 */
 | 
			
		||||
interface PoStreamInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Open the stream. Set the URI for the stream earlier with setURI().
 | 
			
		||||
   */
 | 
			
		||||
  public function open();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Close the stream.
 | 
			
		||||
   */
 | 
			
		||||
  public function close();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the URI of the PO stream that is being read or written.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   URI string for this stream.
 | 
			
		||||
   */
 | 
			
		||||
  public function getURI();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the URI of the PO stream that is going to be read or written.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $uri
 | 
			
		||||
   *   URI string to set for this stream.
 | 
			
		||||
   */
 | 
			
		||||
  public function setURI($uri);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										597
									
								
								web/core/lib/Drupal/Component/Gettext/PoStreamReader.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										597
									
								
								web/core/lib/Drupal/Component/Gettext/PoStreamReader.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,597 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Gettext;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Render\FormattableMarkup;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements Gettext PO stream reader.
 | 
			
		||||
 *
 | 
			
		||||
 * The PO file format parsing is implemented according to the documentation at
 | 
			
		||||
 * http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
 | 
			
		||||
 */
 | 
			
		||||
class PoStreamReader implements PoStreamInterface, PoReaderInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Source line number of the stream being parsed.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $lineNumber = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Parser context for the stream reader state machine.
 | 
			
		||||
   *
 | 
			
		||||
   * Possible contexts are:
 | 
			
		||||
   *  - 'COMMENT' (#)
 | 
			
		||||
   *  - 'MSGID' (msgid)
 | 
			
		||||
   *  - 'MSGID_PLURAL' (msgid_plural)
 | 
			
		||||
   *  - 'MSGCTXT' (msgctxt)
 | 
			
		||||
   *  - 'MSGSTR' (msgstr or msgstr[])
 | 
			
		||||
   *  - 'MSGSTR_ARR' (msgstr_arg)
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $context = 'COMMENT';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Current entry being read. Incomplete.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $currentItem = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Current plural index for plural translations.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $currentPluralIndex = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * URI of the PO stream that is being read.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $uri = '';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Language code for the PO stream being read.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $langcode = NULL;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * File handle of the current PO stream.
 | 
			
		||||
   *
 | 
			
		||||
   * @var resource
 | 
			
		||||
   */
 | 
			
		||||
  protected $fd;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The PO stream header.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Component\Gettext\PoHeader
 | 
			
		||||
   */
 | 
			
		||||
  protected $header;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Object wrapper for the last read source/translation pair.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Component\Gettext\PoItem
 | 
			
		||||
   */
 | 
			
		||||
  protected $lastItem;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicator of whether the stream reading is finished.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected $finished;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Array of translated error strings recorded on reading this stream so far.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $errors;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getLangcode() {
 | 
			
		||||
    return $this->langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setLangcode($langcode) {
 | 
			
		||||
    $this->langcode = $langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getHeader() {
 | 
			
		||||
    return $this->header;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoMetadataInterface::setHeader().
 | 
			
		||||
   *
 | 
			
		||||
   * Not applicable to stream reading and therefore not implemented.
 | 
			
		||||
   */
 | 
			
		||||
  public function setHeader(PoHeader $header) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getURI() {
 | 
			
		||||
    return $this->uri;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setURI($uri) {
 | 
			
		||||
    $this->uri = $uri;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoStreamInterface::open().
 | 
			
		||||
   *
 | 
			
		||||
   * Opens the stream and reads the header. The stream is ready for reading
 | 
			
		||||
   * items after.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   *   If the URI is not yet set.
 | 
			
		||||
   */
 | 
			
		||||
  public function open() {
 | 
			
		||||
    if (!empty($this->uri)) {
 | 
			
		||||
      $this->fd = fopen($this->uri, 'rb');
 | 
			
		||||
      $this->readHeader();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      throw new \Exception('Cannot open stream without URI set.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoStreamInterface::close().
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   *   If the stream is not open.
 | 
			
		||||
   */
 | 
			
		||||
  public function close() {
 | 
			
		||||
    if ($this->fd) {
 | 
			
		||||
      fclose($this->fd);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      throw new \Exception('Cannot close stream that is not open.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function readItem() {
 | 
			
		||||
    // Clear out the last item.
 | 
			
		||||
    $this->lastItem = NULL;
 | 
			
		||||
 | 
			
		||||
    // Read until finished with the stream or a complete item was identified.
 | 
			
		||||
    while (!$this->finished && is_null($this->lastItem)) {
 | 
			
		||||
      $this->readLine();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this->lastItem;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the seek position for the current PO stream.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $seek
 | 
			
		||||
   *   The new seek position to set.
 | 
			
		||||
   */
 | 
			
		||||
  public function setSeek($seek) {
 | 
			
		||||
    fseek($this->fd, $seek);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the pointer position of the current PO stream.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSeek() {
 | 
			
		||||
    return ftell($this->fd);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Read the header from the PO stream.
 | 
			
		||||
   *
 | 
			
		||||
   * The header is a special case PoItem, using the empty string as source and
 | 
			
		||||
   * key-value pairs as translation. We just reuse the item reader logic to
 | 
			
		||||
   * read the header.
 | 
			
		||||
   */
 | 
			
		||||
  private function readHeader() {
 | 
			
		||||
    $item = $this->readItem();
 | 
			
		||||
    // Handle the case properly when the .po file is empty (0 bytes).
 | 
			
		||||
    if (!$item) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $header = new PoHeader();
 | 
			
		||||
    $header->setFromString(trim($item->getTranslation()));
 | 
			
		||||
    $this->header = $header;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reads a line from the PO stream and stores data internally.
 | 
			
		||||
   *
 | 
			
		||||
   * Expands $this->current_item based on new data for the current item. If
 | 
			
		||||
   * this line ends the current item, it is saved with setItemFromArray() with
 | 
			
		||||
   * data from $this->current_item.
 | 
			
		||||
   *
 | 
			
		||||
   * An internal state machine is maintained in this reader using
 | 
			
		||||
   * $this->context as the reading state. PO items are in between COMMENT
 | 
			
		||||
   * states (when items have at least one line or comment in between them) or
 | 
			
		||||
   * indicated by MSGSTR or MSGSTR_ARR followed immediately by an MSGID or
 | 
			
		||||
   * MSGCTXT (when items closely follow each other).
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool|null
 | 
			
		||||
   *   FALSE if an error was logged, NULL otherwise. The errors are considered
 | 
			
		||||
   *   non-blocking, so reading can continue, while the errors are collected
 | 
			
		||||
   *   for later presentation.
 | 
			
		||||
   */
 | 
			
		||||
  private function readLine() {
 | 
			
		||||
    // Read a line and set the stream finished indicator if it was not
 | 
			
		||||
    // possible anymore.
 | 
			
		||||
    $line = fgets($this->fd);
 | 
			
		||||
    $this->finished = ($line === FALSE);
 | 
			
		||||
 | 
			
		||||
    if (!$this->finished) {
 | 
			
		||||
 | 
			
		||||
      if ($this->lineNumber == 0) {
 | 
			
		||||
        // The first line might come with a UTF-8 BOM, which should be removed.
 | 
			
		||||
        $line = str_replace("\xEF\xBB\xBF", '', $line);
 | 
			
		||||
        // Current plurality for 'msgstr[]'.
 | 
			
		||||
        $this->currentPluralIndex = 0;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Track the line number for error reporting.
 | 
			
		||||
      $this->lineNumber++;
 | 
			
		||||
 | 
			
		||||
      // Initialize common values for error logging.
 | 
			
		||||
      $log_vars = [
 | 
			
		||||
        '%uri' => $this->getURI(),
 | 
			
		||||
        '%line' => $this->lineNumber,
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      // Trim away the linefeed. \\n might appear at the end of the string if
 | 
			
		||||
      // another line continuing the same string follows. We can remove that.
 | 
			
		||||
      $line = trim(strtr($line, ["\\\n" => ""]));
 | 
			
		||||
 | 
			
		||||
      if (!strncmp('#', $line, 1)) {
 | 
			
		||||
        // Lines starting with '#' are comments.
 | 
			
		||||
 | 
			
		||||
        if ($this->context == 'COMMENT') {
 | 
			
		||||
          // Already in comment context, add to current comment.
 | 
			
		||||
          $this->currentItem['#'][] = substr($line, 1);
 | 
			
		||||
        }
 | 
			
		||||
        elseif (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
 | 
			
		||||
          // We are currently in string context, save current item.
 | 
			
		||||
          $this->setItemFromArray($this->currentItem);
 | 
			
		||||
 | 
			
		||||
          // Start a new entry for the comment.
 | 
			
		||||
          $this->currentItem = [];
 | 
			
		||||
          $this->currentItem['#'][] = substr($line, 1);
 | 
			
		||||
 | 
			
		||||
          $this->context = 'COMMENT';
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          // A comment following any other context is a syntax error.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr" was expected but not found on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!strncmp('msgid_plural', $line, 12)) {
 | 
			
		||||
        // A plural form for the current source string.
 | 
			
		||||
 | 
			
		||||
        if ($this->context != 'MSGID') {
 | 
			
		||||
          // A plural form can only be added to an msgid directly.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgid_plural" was expected but not found on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Remove 'msgid_plural' and trim away whitespace.
 | 
			
		||||
        $line = trim(substr($line, 12));
 | 
			
		||||
 | 
			
		||||
        // Only the plural source string is left, parse it.
 | 
			
		||||
        $quoted = $this->parseQuoted($line);
 | 
			
		||||
        if ($quoted === FALSE) {
 | 
			
		||||
          // The plural form must be wrapped in quotes.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains a syntax error on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Append the plural source to the current entry.
 | 
			
		||||
        if (is_string($this->currentItem['msgid'])) {
 | 
			
		||||
          // The first value was stored as string. Now we know the context is
 | 
			
		||||
          // plural, it is converted to array.
 | 
			
		||||
          $this->currentItem['msgid'] = [$this->currentItem['msgid']];
 | 
			
		||||
        }
 | 
			
		||||
        $this->currentItem['msgid'][] = $quoted;
 | 
			
		||||
 | 
			
		||||
        $this->context = 'MSGID_PLURAL';
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!strncmp('msgid', $line, 5)) {
 | 
			
		||||
        // Starting a new message.
 | 
			
		||||
 | 
			
		||||
        if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
 | 
			
		||||
          // We are currently in string context, save current item.
 | 
			
		||||
          $this->setItemFromArray($this->currentItem);
 | 
			
		||||
 | 
			
		||||
          // Start a new context for the msgid.
 | 
			
		||||
          $this->currentItem = [];
 | 
			
		||||
        }
 | 
			
		||||
        elseif ($this->context == 'MSGID') {
 | 
			
		||||
          // We are currently already in the context, meaning we passed an id
 | 
			
		||||
          // with no data.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgid" is unexpected on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Remove 'msgid' and trim away whitespace.
 | 
			
		||||
        $line = trim(substr($line, 5));
 | 
			
		||||
 | 
			
		||||
        // Only the message id string is left, parse it.
 | 
			
		||||
        $quoted = $this->parseQuoted($line);
 | 
			
		||||
        if ($quoted === FALSE) {
 | 
			
		||||
          // The message id must be wrapped in quotes.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->currentItem['msgid'] = $quoted;
 | 
			
		||||
        $this->context = 'MSGID';
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!strncmp('msgctxt', $line, 7)) {
 | 
			
		||||
        // Starting a new context.
 | 
			
		||||
 | 
			
		||||
        if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
 | 
			
		||||
          // We are currently in string context, save current item.
 | 
			
		||||
          $this->setItemFromArray($this->currentItem);
 | 
			
		||||
          $this->currentItem = [];
 | 
			
		||||
        }
 | 
			
		||||
        elseif (!empty($this->currentItem['msgctxt'])) {
 | 
			
		||||
          // A context cannot apply to another context.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgctxt" is unexpected on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Remove 'msgctxt' and trim away whitespaces.
 | 
			
		||||
        $line = trim(substr($line, 7));
 | 
			
		||||
 | 
			
		||||
        // Only the msgctxt string is left, parse it.
 | 
			
		||||
        $quoted = $this->parseQuoted($line);
 | 
			
		||||
        if ($quoted === FALSE) {
 | 
			
		||||
          // The context string must be quoted.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->currentItem['msgctxt'] = $quoted;
 | 
			
		||||
 | 
			
		||||
        $this->context = 'MSGCTXT';
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!strncmp('msgstr[', $line, 7)) {
 | 
			
		||||
        // A message string for a specific plurality.
 | 
			
		||||
 | 
			
		||||
        if (($this->context != 'MSGID') &&
 | 
			
		||||
            ($this->context != 'MSGCTXT') &&
 | 
			
		||||
            ($this->context != 'MSGID_PLURAL') &&
 | 
			
		||||
            ($this->context != 'MSGSTR_ARR')) {
 | 
			
		||||
          // Plural message strings must come after msgid, msgctxt,
 | 
			
		||||
          // msgid_plural, or other msgstr[] entries.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Ensure the plurality is terminated.
 | 
			
		||||
        if (!str_contains($line, ']')) {
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Extract the plurality.
 | 
			
		||||
        $from_bracket = strstr($line, '[');
 | 
			
		||||
        $this->currentPluralIndex = substr($from_bracket, 1, strpos($from_bracket, ']') - 1);
 | 
			
		||||
 | 
			
		||||
        // Skip to the next whitespace and trim away any further whitespace,
 | 
			
		||||
        // bringing $line to the message text only.
 | 
			
		||||
        $line = trim(strstr($line, " "));
 | 
			
		||||
 | 
			
		||||
        $quoted = $this->parseQuoted($line);
 | 
			
		||||
        if ($quoted === FALSE) {
 | 
			
		||||
          // The string must be quoted.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($this->currentItem['msgstr']) || !is_array($this->currentItem['msgstr'])) {
 | 
			
		||||
          $this->currentItem['msgstr'] = [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->currentItem['msgstr'][$this->currentPluralIndex] = $quoted;
 | 
			
		||||
 | 
			
		||||
        $this->context = 'MSGSTR_ARR';
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!strncmp("msgstr", $line, 6)) {
 | 
			
		||||
        // A string pair for an msgid (with optional context).
 | 
			
		||||
 | 
			
		||||
        if (($this->context != 'MSGID') && ($this->context != 'MSGCTXT')) {
 | 
			
		||||
          // Strings are only valid within an id or context scope.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Remove 'msgstr' and trim away whitespaces.
 | 
			
		||||
        $line = trim(substr($line, 6));
 | 
			
		||||
 | 
			
		||||
        // Only the msgstr string is left, parse it.
 | 
			
		||||
        $quoted = $this->parseQuoted($line);
 | 
			
		||||
        if ($quoted === FALSE) {
 | 
			
		||||
          // The string must be quoted.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->currentItem['msgstr'] = $quoted;
 | 
			
		||||
 | 
			
		||||
        $this->context = 'MSGSTR';
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($line != '') {
 | 
			
		||||
        // Anything that is not a token may be a continuation of a previous
 | 
			
		||||
        // token.
 | 
			
		||||
 | 
			
		||||
        $quoted = $this->parseQuoted($line);
 | 
			
		||||
        if ($quoted === FALSE) {
 | 
			
		||||
          // This string must be quoted.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: string continuation expected on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Append the string to the current item.
 | 
			
		||||
        if (($this->context == 'MSGID') || ($this->context == 'MSGID_PLURAL')) {
 | 
			
		||||
          if (is_array($this->currentItem['msgid'])) {
 | 
			
		||||
            // Add string to last array element for plural sources.
 | 
			
		||||
            $last_index = count($this->currentItem['msgid']) - 1;
 | 
			
		||||
            $this->currentItem['msgid'][$last_index] .= $quoted;
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            // Singular source, just append the string.
 | 
			
		||||
            $this->currentItem['msgid'] .= $quoted;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        elseif ($this->context == 'MSGCTXT') {
 | 
			
		||||
          // Multiline context name.
 | 
			
		||||
          $this->currentItem['msgctxt'] .= $quoted;
 | 
			
		||||
        }
 | 
			
		||||
        elseif ($this->context == 'MSGSTR') {
 | 
			
		||||
          // Multiline translation string.
 | 
			
		||||
          $this->currentItem['msgstr'] .= $quoted;
 | 
			
		||||
        }
 | 
			
		||||
        elseif ($this->context == 'MSGSTR_ARR') {
 | 
			
		||||
          // Multiline plural translation string.
 | 
			
		||||
          $this->currentItem['msgstr'][$this->currentPluralIndex] .= $quoted;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          // No valid context to append to.
 | 
			
		||||
          $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: unexpected string on line %line.', $log_vars);
 | 
			
		||||
          return FALSE;
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Empty line read or EOF of PO stream, close out the last entry.
 | 
			
		||||
    if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
 | 
			
		||||
      $this->setItemFromArray($this->currentItem);
 | 
			
		||||
      $this->currentItem = [];
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($this->context != 'COMMENT') {
 | 
			
		||||
      $this->errors[] = new FormattableMarkup('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Store the parsed values as a PoItem object.
 | 
			
		||||
   */
 | 
			
		||||
  public function setItemFromArray($value) {
 | 
			
		||||
    $plural = FALSE;
 | 
			
		||||
 | 
			
		||||
    $comments = '';
 | 
			
		||||
    if (isset($value['#'])) {
 | 
			
		||||
      $comments = $this->shortenComments($value['#']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (is_array($value['msgstr'])) {
 | 
			
		||||
      // Sort plural variants by their form index.
 | 
			
		||||
      ksort($value['msgstr']);
 | 
			
		||||
      $plural = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $item = new PoItem();
 | 
			
		||||
    $item->setContext($value['msgctxt'] ?? '');
 | 
			
		||||
    $item->setSource($value['msgid']);
 | 
			
		||||
    $item->setTranslation($value['msgstr']);
 | 
			
		||||
    $item->setPlural($plural);
 | 
			
		||||
    $item->setComment($comments);
 | 
			
		||||
    $item->setLangcode($this->langcode);
 | 
			
		||||
 | 
			
		||||
    $this->lastItem = $item;
 | 
			
		||||
 | 
			
		||||
    $this->context = 'COMMENT';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Parses a string in quotes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   A string specified with enclosing quotes.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool|string
 | 
			
		||||
   *   The string parsed from inside the quotes. False when the syntax is
 | 
			
		||||
   *   invalid.
 | 
			
		||||
   */
 | 
			
		||||
  public function parseQuoted($string) {
 | 
			
		||||
    if (substr($string, 0, 1) != substr($string, -1, 1)) {
 | 
			
		||||
      // Start and end quotes must be the same.
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
    $quote = substr($string, 0, 1);
 | 
			
		||||
    $string = substr($string, 1, -1);
 | 
			
		||||
    if ($quote == '"') {
 | 
			
		||||
      // Double quotes: strip slashes.
 | 
			
		||||
      return stripcslashes($string);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($quote == "'") {
 | 
			
		||||
      // Simple quote: return as-is.
 | 
			
		||||
      return $string;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // Unrecognized quote.
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates a short, one-string version of the passed comment array.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string[] $comment
 | 
			
		||||
   *   An array of strings containing a comment.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Short one-string version of the comment.
 | 
			
		||||
   */
 | 
			
		||||
  private function shortenComments($comment) {
 | 
			
		||||
    $comm = '';
 | 
			
		||||
    while (count($comment)) {
 | 
			
		||||
      $test = $comm . substr(array_shift($comment), 1) . ', ';
 | 
			
		||||
      if (strlen($comm) < 130) {
 | 
			
		||||
        $comm = $test;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return trim(substr($comm, 0, -2));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										164
									
								
								web/core/lib/Drupal/Component/Gettext/PoStreamWriter.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								web/core/lib/Drupal/Component/Gettext/PoStreamWriter.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,164 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Gettext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a Gettext PO stream writer.
 | 
			
		||||
 */
 | 
			
		||||
class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * URI of the PO stream that is being written.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $uri;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The Gettext PO header.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Component\Gettext\PoHeader
 | 
			
		||||
   */
 | 
			
		||||
  protected $header;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * File handle of the current PO stream.
 | 
			
		||||
   *
 | 
			
		||||
   * @var resource
 | 
			
		||||
   */
 | 
			
		||||
  protected $fd;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The language code of this writer.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $langcode;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the PO header of the current stream.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Component\Gettext\PoHeader
 | 
			
		||||
   *   The Gettext PO header.
 | 
			
		||||
   */
 | 
			
		||||
  public function getHeader() {
 | 
			
		||||
    return $this->header;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the PO header for the current stream.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Component\Gettext\PoHeader $header
 | 
			
		||||
   *   The Gettext PO header to set.
 | 
			
		||||
   */
 | 
			
		||||
  public function setHeader(PoHeader $header) {
 | 
			
		||||
    $this->header = $header;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the current language code used.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The language code.
 | 
			
		||||
   */
 | 
			
		||||
  public function getLangcode() {
 | 
			
		||||
    return $this->langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the language code.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   The language code.
 | 
			
		||||
   */
 | 
			
		||||
  public function setLangcode($langcode) {
 | 
			
		||||
    $this->langcode = $langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function open() {
 | 
			
		||||
    // Open in write mode. Will overwrite the stream if it already exists.
 | 
			
		||||
    $this->fd = fopen($this->getURI(), 'w');
 | 
			
		||||
    // Write the header at the start.
 | 
			
		||||
    $this->writeHeader();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoStreamInterface::close().
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   *   If the stream is not open.
 | 
			
		||||
   */
 | 
			
		||||
  public function close() {
 | 
			
		||||
    if ($this->fd) {
 | 
			
		||||
      fclose($this->fd);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      throw new \Exception('Cannot close stream that is not open.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Write data to the stream.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $data
 | 
			
		||||
   *   Piece of string to write to the stream. If the value is not directly a
 | 
			
		||||
   *   string, casting will happen in writing.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   *   If writing the data is not possible.
 | 
			
		||||
   */
 | 
			
		||||
  private function write($data) {
 | 
			
		||||
    $result = fwrite($this->fd, $data);
 | 
			
		||||
    if ($result === FALSE || $result != strlen($data)) {
 | 
			
		||||
      throw new \Exception('Unable to write data: ' . substr($data, 0, 20));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Write the PO header to the stream.
 | 
			
		||||
   */
 | 
			
		||||
  private function writeHeader() {
 | 
			
		||||
    $this->write($this->header);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function writeItem(PoItem $item) {
 | 
			
		||||
    $this->write($item);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function writeItems(PoReaderInterface $reader, $count = -1) {
 | 
			
		||||
    $forever = $count == -1;
 | 
			
		||||
    while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
 | 
			
		||||
      $this->writeItem($item);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoStreamInterface::getURI().
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   *   If the URI is not set.
 | 
			
		||||
   */
 | 
			
		||||
  public function getURI() {
 | 
			
		||||
    if (empty($this->uri)) {
 | 
			
		||||
      throw new \Exception('No URI set.');
 | 
			
		||||
    }
 | 
			
		||||
    return $this->uri;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setURI($uri) {
 | 
			
		||||
    $this->uri = $uri;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								web/core/lib/Drupal/Component/Gettext/PoWriterInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								web/core/lib/Drupal/Component/Gettext/PoWriterInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Gettext;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Shared interface definition for all Gettext PO Writers.
 | 
			
		||||
 */
 | 
			
		||||
interface PoWriterInterface extends PoMetadataInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Writes the given item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param PoItem $item
 | 
			
		||||
   *   One specific item to write.
 | 
			
		||||
   */
 | 
			
		||||
  public function writeItem(PoItem $item);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Writes all or the given amount of items.
 | 
			
		||||
   *
 | 
			
		||||
   * @param PoReaderInterface $reader
 | 
			
		||||
   *   Reader to read PoItems from.
 | 
			
		||||
   * @param int $count
 | 
			
		||||
   *   Amount of items to read from $reader to write. If -1, all items are
 | 
			
		||||
   *   read from $reader.
 | 
			
		||||
   */
 | 
			
		||||
  public function writeItems(PoReaderInterface $reader, $count = -1);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								web/core/lib/Drupal/Component/Gettext/README.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								web/core/lib/Drupal/Component/Gettext/README.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
The Drupal Gettext Component
 | 
			
		||||
 | 
			
		||||
Thanks for using this Drupal component.
 | 
			
		||||
 | 
			
		||||
You can participate in its development on Drupal.org, through our issue system:
 | 
			
		||||
https://www.drupal.org/project/issues/drupal
 | 
			
		||||
 | 
			
		||||
You can get the full Drupal repo here:
 | 
			
		||||
https://www.drupal.org/project/drupal/git-instructions
 | 
			
		||||
 | 
			
		||||
You can browse the full Drupal repo here:
 | 
			
		||||
https://git.drupalcode.org/project/drupal
 | 
			
		||||
							
								
								
									
										18
									
								
								web/core/lib/Drupal/Component/Gettext/TESTING.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								web/core/lib/Drupal/Component/Gettext/TESTING.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
HOW-TO: Test this Drupal component
 | 
			
		||||
 | 
			
		||||
In order to test this component, you'll need to get the entire Drupal repo and
 | 
			
		||||
run the tests there.
 | 
			
		||||
 | 
			
		||||
You'll find the tests under core/tests/Drupal/Tests/Component.
 | 
			
		||||
 | 
			
		||||
You can get the full Drupal repo here:
 | 
			
		||||
https://www.drupal.org/project/drupal/git-instructions
 | 
			
		||||
 | 
			
		||||
You can find more information about running PHPUnit tests with Drupal here:
 | 
			
		||||
https://www.drupal.org/node/2116263
 | 
			
		||||
 | 
			
		||||
Each component in the Drupal\Component namespace has its own annotated test
 | 
			
		||||
group. You can use this group to run only the tests for this component. Like
 | 
			
		||||
this:
 | 
			
		||||
 | 
			
		||||
$ ./vendor/bin/phpunit -c core --group Gettext
 | 
			
		||||
							
								
								
									
										25
									
								
								web/core/lib/Drupal/Component/Gettext/composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								web/core/lib/Drupal/Component/Gettext/composer.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "drupal/core-gettext",
 | 
			
		||||
    "description": "PHP library for reading PO files.",
 | 
			
		||||
    "type": "library",
 | 
			
		||||
    "license": "GPL-2.0-or-later",
 | 
			
		||||
    "support": {
 | 
			
		||||
        "issues": "https://www.drupal.org/project/issues/drupal",
 | 
			
		||||
        "source": "https://www.drupal.org/project/drupal/git-instructions"
 | 
			
		||||
    },
 | 
			
		||||
    "require": {
 | 
			
		||||
        "php": ">=8.3.0",
 | 
			
		||||
        "drupal/core-render": "^11.2"
 | 
			
		||||
    },
 | 
			
		||||
    "autoload": {
 | 
			
		||||
        "psr-4": {
 | 
			
		||||
            "Drupal\\Component\\Gettext\\": ""
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "extra": {
 | 
			
		||||
        "_readme": [
 | 
			
		||||
            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    "minimum-stability": "dev"
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user