1
0
mirror of https://github.com/vichan-devel/vichan.git synced 2025-02-16 11:02:40 +01:00
This commit is contained in:
8n-tech 2015-04-09 10:22:24 +10:00
commit 46cd77e813
2244 changed files with 229952 additions and 73107 deletions

17
.gitignore vendored
View File

@ -40,8 +40,25 @@ Thumbs.db
*.orig
*~
# tmp filesystem
/tmp/cache/*
/tmp/locks/*
!/tmp/cache/.gitkeep
!/tmp/locks/.gitkeep
#vichan custom
favicon.ico
/static/spoiler.png
/inc/locale/ja_JP/*
#json
/boards.json
/boards-top20.json
/8archive.json
/tags.json
#board specific files
/*/main.js
/*/rules.txt
/stylesheets/board/*.css

8
.gitmodules vendored
View File

@ -1,3 +1,9 @@
[submodule "js/wPaint"]
path = js/wPaint
url = https://github.com/vichan-devel/wPaint.git
url = https://github.com/ctrlcctrlv/wPaint.git
branch = master
[submodule "inc/lib/parsedown"]
path = inc/lib/parsedown
url = https://github.com/vichan-devel/parsedown
branch = master

19
404.php
View File

@ -1,6 +1,9 @@
<?php
include "inc/functions.php";
require_once "inc/functions.php";
header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
global $config;
$dir = "static/404/";
@ -25,9 +28,19 @@ if (count($files) == 0) {
$errorimage = $files[array_rand($files)];
}
if (preg_match('!'.$config['board_regex'].'/'.$config['dir']['res'].'\d+\.html!u', $_SERVER['REQUEST_URI'])) {
$return_link = '<a href="../index.html">[ Return ]</a>';
} else {
$return_link = '';
}
$ad = Element("ad_top.html", array());
$page = <<<EOT
<div style="text-align:center">$ad</div>
<div class="ban">
<p style="text-align:center"><img src="/static/404/{$errorimage}" style="width:100%"></p>
<p style="text-align:center"><a href="/index.html">[ Home ]</a>{$return_link}</p>
</div>
<script type="text/javascript">
@ -35,8 +48,8 @@ $page = <<<EOT
var faves = JSON.parse(localStorage.favorites);
$.each(faves, function(k, v) {
if (window.location.pathname === '/' + v + '/') {
faves.pop(v);
if ((window.location.pathname === '/' + v + '/') || (window.location.pathname === '/' + v)) {
faves.splice(k, 1);
localStorage.favorites = JSON.stringify(faves);
alert('As /' + v + '/ no longer exists, it has been removed from your favorites.');

View File

@ -0,0 +1,15 @@
<?php
// We are using a custom path here to connect to the database.
// Why? Performance reasons.
$pdo = new PDO("mysql:dbname=8chan;host=localhost", "user", "pass", array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
// Captcha expiration:
$expires_in = 120; // 120 seconds
// Captcha dimensions:
$width = 300;
$height = 80;
// Captcha length:
$length = 6;

View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
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
state 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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program 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, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU 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. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@ -0,0 +1,477 @@
<?php
/**
* Script para la generación de CAPTCHAS
*
* @author Jose Rodriguez <jose.rodriguez@exec.cl>
* @license GPLv3
* @link http://code.google.com/p/cool-php-captcha
* @package captcha
* @version 0.3
*
*/
/**
* SimpleCaptcha class
*
*/
class SimpleCaptcha {
/** Width of the image */
public $width = 200;
/** Height of the image */
public $height = 80;
/** Dictionary word file (empty for random text) */
public $wordsFile = 'words/en.php';
/**
* Path for resource files (fonts, words, etc.)
*
* "resources" by default. For security reasons, is better move this
* directory to another location outise the web server
*
*/
public $resourcesPath = 'cool-php-captcha-0.3.1/resources';
/** Min word length (for non-dictionary random text generation) */
public $minWordLength = 5;
/**
* Max word length (for non-dictionary random text generation)
*
* Used for dictionary words indicating the word-length
* for font-size modification purposes
*/
public $maxWordLength = 8;
/** Sessionname to store the original text */
public $session_var = 'captcha';
/** Background color in RGB-array */
public $backgroundColor = array(255, 255, 255);
/** Foreground colors in RGB-array */
public $colors = array(
array(27,78,181), // blue
array(22,163,35), // green
array(214,36,7), // red
);
/** Shadow color in RGB-array or null */
public $shadowColor = null; //array(0, 0, 0);
/** Horizontal line through the text */
public $lineWidth = 2;
/**
* Font configuration
*
* - font: TTF file
* - spacing: relative pixel space between character
* - minSize: min font size
* - maxSize: max font size
*/
public $fonts = array(
'Antykwa' => array('spacing' => -3, 'minSize' => 27, 'maxSize' => 30, 'font' => 'AntykwaBold.ttf'),
'Candice' => array('spacing' =>-1.5,'minSize' => 28, 'maxSize' => 31, 'font' => 'Candice.ttf'),
'DingDong' => array('spacing' => -2, 'minSize' => 24, 'maxSize' => 30, 'font' => 'Ding-DongDaddyO.ttf'),
'Duality' => array('spacing' => -2, 'minSize' => 30, 'maxSize' => 38, 'font' => 'Duality.ttf'),
'Heineken' => array('spacing' => -2, 'minSize' => 24, 'maxSize' => 34, 'font' => 'Heineken.ttf'),
'Jura' => array('spacing' => -2, 'minSize' => 28, 'maxSize' => 32, 'font' => 'Jura.ttf'),
'StayPuft' => array('spacing' =>-1.5,'minSize' => 28, 'maxSize' => 32, 'font' => 'StayPuft.ttf'),
//'Times' => array('spacing' => -2, 'minSize' => 28, 'maxSize' => 34, 'font' => 'TimesNewRomanBold.ttf'),
//'VeraSans' => array('spacing' => -1, 'minSize' => 20, 'maxSize' => 28, 'font' => 'VeraSansBold.ttf'),
);
/** Wave configuracion in X and Y axes */
public $Yperiod = 12;
public $Yamplitude = 14;
public $Xperiod = 11;
public $Xamplitude = 5;
/** letter rotation clockwise */
public $maxRotation = 8;
/**
* Internal image size factor (for better image quality)
* 1: low, 2: medium, 3: high
*/
public $scale = 3;
/**
* Blur effect for better image quality (but slower image processing).
* Better image results with scale=3
*/
public $blur = true;
/** Debug? */
public $debug = false;
/** Image format: jpeg or png */
public $imageFormat = 'png';
/** GD image */
public $im;
public function __construct($config = array()) {
}
public function CreateImage($text = false) {
$ini = microtime(true);
/** Initialization */
$this->ImageAllocate();
/** Text insertion */
if (!$text) $text = $this->GetCaptchaText();
$fontcfg = $this->fonts[array_rand($this->fonts)];
$this->WriteText($text, $fontcfg);
$_SESSION[$this->session_var] = $text;
/** Transformations */
if (!empty($this->lineWidth)) {
$this->WriteLine();
}
$this->WaveImage();
if ($this->blur && function_exists('imagefilter')) {
imagefilter($this->im, IMG_FILTER_GAUSSIAN_BLUR);
}
$this->ReduceImage();
if ($this->debug) {
imagestring($this->im, 1, 1, $this->height-8,
"$text {$fontcfg['font']} ".round((microtime(true)-$ini)*1000)."ms",
$this->GdFgColor
);
}
/** Output */
$this->WriteImage();
$this->Cleanup();
}
/**
* Creates the image resources
*/
protected function ImageAllocate() {
// Cleanup
if (!empty($this->im)) {
imagedestroy($this->im);
}
$this->im = imagecreatetruecolor($this->width*$this->scale, $this->height*$this->scale);
// Background color
$this->GdBgColor = imagecolorallocate($this->im,
$this->backgroundColor[0],
$this->backgroundColor[1],
$this->backgroundColor[2]
);
imagefilledrectangle($this->im, 0, 0, $this->width*$this->scale, $this->height*$this->scale, $this->GdBgColor);
// Foreground color
$color = $this->colors[mt_rand(0, sizeof($this->colors)-1)];
$this->GdFgColor = imagecolorallocate($this->im, $color[0], $color[1], $color[2]);
// Shadow color
if (!empty($this->shadowColor) && is_array($this->shadowColor) && sizeof($this->shadowColor) >= 3) {
$this->GdShadowColor = imagecolorallocate($this->im,
$this->shadowColor[0],
$this->shadowColor[1],
$this->shadowColor[2]
);
}
}
/**
* Text generation
*
* @return string Text
*/
protected function GetCaptchaText() {
$text = $this->GetDictionaryCaptchaText();
if (!$text) {
$text = $this->GetRandomCaptchaText();
}
return $text;
}
/**
* Random text generation
*
* @return string Text
*/
protected function GetRandomCaptchaText($length = null) {
if (empty($length)) {
$length = rand($this->minWordLength, $this->maxWordLength);
}
$words = "abcdefghijlmnopqrstvwyz";
$vocals = "aeiou";
$text = "";
$vocal = rand(0, 1);
for ($i=0; $i<$length; $i++) {
if ($vocal) {
$text .= substr($vocals, mt_rand(0, 4), 1);
} else {
$text .= substr($words, mt_rand(0, 22), 1);
}
$vocal = !$vocal;
}
return $text;
}
/**
* Random dictionary word generation
*
* @param boolean $extended Add extended "fake" words
* @return string Word
*/
function GetDictionaryCaptchaText($extended = false) {
if (empty($this->wordsFile)) {
return false;
}
// Full path of words file
if (substr($this->wordsFile, 0, 1) == '/') {
$wordsfile = $this->wordsFile;
} else {
$wordsfile = $this->resourcesPath.'/'.$this->wordsFile;
}
if (!file_exists($wordsfile)) {
return false;
}
$fp = fopen($wordsfile, "r");
$length = strlen(fgets($fp));
if (!$length) {
return false;
}
$line = rand(1, (filesize($wordsfile)/$length)-2);
if (fseek($fp, $length*$line) == -1) {
return false;
}
$text = trim(fgets($fp));
fclose($fp);
/** Change ramdom volcals */
if ($extended) {
$text = preg_split('//', $text, -1, PREG_SPLIT_NO_EMPTY);
$vocals = array('a', 'e', 'i', 'o', 'u');
foreach ($text as $i => $char) {
if (mt_rand(0, 1) && in_array($char, $vocals)) {
$text[$i] = $vocals[mt_rand(0, 4)];
}
}
$text = implode('', $text);
}
return $text;
}
/**
* Horizontal line insertion
*/
protected function WriteLine() {
$x1 = $this->width*$this->scale*.15;
$x2 = $this->textFinalX;
$y1 = rand($this->height*$this->scale*.40, $this->height*$this->scale*.65);
$y2 = rand($this->height*$this->scale*.40, $this->height*$this->scale*.65);
$width = $this->lineWidth/2*$this->scale;
for ($i = $width*-1; $i <= $width; $i++) {
imageline($this->im, $x1, $y1+$i, $x2, $y2+$i, $this->GdFgColor);
}
}
/**
* Text insertion
*/
protected function WriteText($text, $fontcfg = array()) {
if (empty($fontcfg)) {
// Select the font configuration
$fontcfg = $this->fonts[array_rand($this->fonts)];
}
// Full path of font file
$fontfile = $this->resourcesPath.'/fonts/'.$fontcfg['font'];
/** Increase font-size for shortest words: 9% for each glyp missing */
$lettersMissing = $this->maxWordLength-strlen($text);
$fontSizefactor = 1+($lettersMissing*0.09);
// Text generation (char by char)
$x = 20*$this->scale;
$y = round(($this->height*27/40)*$this->scale);
$length = strlen($text);
for ($i=0; $i<$length; $i++) {
$degree = rand($this->maxRotation*-1, $this->maxRotation);
$fontsize = rand($fontcfg['minSize'], $fontcfg['maxSize'])*$this->scale*$fontSizefactor;
$letter = substr($text, $i, 1);
if ($this->shadowColor) {
$coords = imagettftext($this->im, $fontsize, $degree,
$x+$this->scale, $y+$this->scale,
$this->GdShadowColor, $fontfile, $letter);
}
$coords = imagettftext($this->im, $fontsize, $degree,
$x, $y,
$this->GdFgColor, $fontfile, $letter);
$x += ($coords[2]-$x) + ($fontcfg['spacing']*$this->scale);
}
$this->textFinalX = $x;
}
/**
* Wave filter
*/
protected function WaveImage() {
// X-axis wave generation
$xp = $this->scale*$this->Xperiod*rand(1,3);
$k = rand(0, 100);
for ($i = 0; $i < ($this->width*$this->scale); $i++) {
imagecopy($this->im, $this->im,
$i-1, sin($k+$i/$xp) * ($this->scale*$this->Xamplitude),
$i, 0, 1, $this->height*$this->scale);
}
// Y-axis wave generation
$k = rand(0, 100);
$yp = $this->scale*$this->Yperiod*rand(1,2);
for ($i = 0; $i < ($this->height*$this->scale); $i++) {
imagecopy($this->im, $this->im,
sin($k+$i/$yp) * ($this->scale*$this->Yamplitude), $i-1,
0, $i, $this->width*$this->scale, 1);
}
}
/**
* Reduce the image to the final size
*/
protected function ReduceImage() {
// Reduzco el tamaño de la imagen
$imResampled = imagecreatetruecolor($this->width, $this->height);
imagecopyresampled($imResampled, $this->im,
0, 0, 0, 0,
$this->width, $this->height,
$this->width*$this->scale, $this->height*$this->scale
);
imagedestroy($this->im);
$this->im = $imResampled;
}
/**
* File generation
*/
protected function WriteImage() {
if ($this->imageFormat == 'png' && function_exists('imagepng')) {
//header("Content-type: image/png");
imagepng($this->im);
} else {
//header("Content-type: image/jpeg");
imagejpeg($this->im, null, 80);
}
}
/**
* Cleanup
*/
protected function Cleanup() {
imagedestroy($this->im);
}
}
?>

View File

@ -0,0 +1,11 @@
This script reads an standard words file (one word per line) and saves
a new word file with the format needed by the cool-php-capcha script.
For prevent remote web access, the output file will be a PHP script
and must have the .php extension
Usage: importwords.php <infile> <outfile>
Example: importwords.php english.txt en.php

View File

@ -0,0 +1,55 @@
<?php
/**
* Generate fixed-width dictionary file
*
* @author Jose Rodriguez <jose.rodriguez@exec.cl>
* @license GPLv3
* @link http://code.google.com/p/cool-php-captcha
* @package captcha
*
*/
/** Word lengths */
$minLength = 5;
$maxLength = 8;
if ($argc < 3) {
die("Usage: $argv[0] infile outfile\n");
}
if (!file_exists($argv[1])) {
die("File '$argv[1]' doesn't exists\n");
}
$fp = fopen($argv[1], "r");
$fp2 = fopen($argv[2], "w");
fwrite($fp2, "<?php /*\n");
while ($lin = fgets($fp)) {
$lin = trim(strtolower($lin));
$strlen = strlen($lin);
if ($strlen>=$minLength && $strlen<=$maxLength && preg_match("/^[a-z]+$/", $lin)) {
$lin = str_pad($lin, $maxLength);
fwrite($fp2, "$lin\n");
}
}
fwrite($fp2, "*/ ?>\n");
fclose($fp);
fclose($fp2);
?>

View File

@ -0,0 +1,9 @@
SET NAMES utf8;
CREATE TABLE `captchas` (
`cookie` VARCHAR(50),
`extra` VARCHAR(200),
`text` VARCHAR(255),
`created_at` INT(11),
PRIMARY KEY (cookie, extra)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;

View File

@ -0,0 +1,67 @@
<?php
header('Access-Control-Allow-Origin: *');
$mode = @$_GET['mode'];
require_once("config.php");
require_once("functions.php");
switch ($mode) {
// Request: GET entrypoint.php?mode=get&extra=1234567890
// Response: JSON: cookie => "generatedcookie", captchahtml => "captchahtml", expires_in => 120
case "get":
if (!isset ($_GET['extra'])) {
die();
}
$extra = $_GET['extra'];
$nojs = isset($_GET['nojs']);
$captcha = generate_captcha($extra);
$cookie = $captcha['cookie'];
$html = $captcha['html'];
if ($nojs) {
header("Content-type: text/html");
echo "<html><body>You do not have JavaScript enabled. To fill out the CAPTCHA, please copy the ID to the post form in the ID field, and write the answer in the answer field.<br><br>CAPTCHA ID: $cookie<br>CAPTCHA image: $html</body></html>";
} else {
header("Content-type: application/json");
echo json_encode(["cookie" => $cookie, "captchahtml" => $html, "expires_in" => $expires_in]);
}
break;
// Request: GET entrypoint.php?mode=check&cookie=generatedcookie&extra=1234567890&text=captcha
// Response: 0 OR 1
case "check":
if (!isset ($_GET['mode'])
|| !isset ($_GET['cookie'])
|| !isset ($_GET['extra'])
|| !isset ($_GET['text'])) {
die();
}
cleanup($pdo, $expires_in);
$query = $pdo->prepare("SELECT * FROM `captchas` WHERE `cookie` = ? AND `extra` = ?");
$query->execute([$_GET['cookie'], $_GET['extra']]);
$ary = $query->fetchAll();
if (!$ary) {
echo "0";
}
else {
$query = $pdo->prepare("DELETE FROM `captchas` WHERE `cookie` = ? AND `extra` = ?");
$query->execute([$_GET['cookie'], $_GET['extra']]);
if ($ary[0]['text'] !== $_GET['text']) {
echo "0";
}
else {
echo "1";
}
}
break;
}

View File

@ -0,0 +1,38 @@
<?php
if (strpos(getcwd(), '8chan-captcha') === false) chdir('8chan-captcha');
require_once("config.php");
require_once("cool-php-captcha-0.3.1/captcha.php");
function generate_captcha($extra = '1234567890') {
global $length, $pdo;
$text = rand_string($length, $extra);
$captcha = new SimpleCaptcha();
$cookie = rand_string(20, "abcdefghijklmnopqrstuvwxyz");
ob_start();
$captcha->CreateImage($text);
$image = ob_get_contents();
ob_end_clean();
$html = '<image src="data:image/png;base64,'.base64_encode($image).'">';
$query = $pdo->prepare("INSERT INTO `captchas` (`cookie`, `extra`, `text`, `created_at`) VALUES (?, ?, ?, ?)");
$query->execute( [$cookie, $extra, $text, time()]);
return array("cookie" => $cookie, "html" => $html);
}
function rand_string($length, $charset) {
$ret = "";
while ($length--) {
$ret .= mb_substr($charset, rand(0, mb_strlen($charset, 'utf-8')-1), 1, 'utf-8');
}
return $ret;
}
function cleanup ($pdo, $expires_in) {
$pdo->prepare("DELETE FROM `captchas` WHERE `created_at` < ?")->execute([time() - $expires_in]);
}

View File

@ -1,3 +1,26 @@
# License of infinity
Copyright (c) 2013-2015 Infinity Development Group (https://github.com/ctrlcctrlv/infinity)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
All copyright notices and permission notices (including this file) shall be
included and remain unedited in all copies or substantial portions of the
Software. This explicitly includes but is not limited to the vichan copyright
notices found in the footers of some template files.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# License of vichan
Copyright (c) 2012-2014 vichan-devel

173
README.md
View File

@ -1,147 +1,66 @@
8chan - The infinitely expanding imageboard.
infinity
========================================================
About
------------
8chan is a free light-weight, fast, highly configurable and user-friendly
imageboard software package. It is written in PHP and has few dependencies.
infinity is a fork of vichan, with the difference that infinity is geared towards allowing users to create their own boards. A running instance is at [8ch.net](https://8ch.net/) (new! a user of the software wrote to me that they created a Polish version: [8ch.pl](http://8ch.pl/))
8chan is a fork of [vichan](https://github.com/vichan-devel/vichan), which is a fork of [Tinyboard](http://tinyboard.org/), a great imageboard package, actively
building on it and adding a lot of features and other improvements.
Most things (other than installation) that apply to upstream vichan also apply to infinity. See their readme for a detailed FAQ: https://github.com/vichan-devel/vichan/blob/master/README.md
Support and announcements: https://int.vichan.net/devel/
If you are not interested in letting your users make their own boards, install vichan instead of infinity.
Requirements
------------
1. PHP >= 5.3
2. MySQL/MariaDB server
3. [mbstring](http://www.php.net/manual/en/mbstring.installation.php)
4. [PHP GD](http://www.php.net/manual/en/intro.image.php)
5. [PHP PDO](http://www.php.net/manual/en/intro.pdo.php)
We try to make sure vichan is compatible with all major web servers and
operating systems. vichan does not include an Apache ```.htaccess``` file nor does
it need one.
### Recommended
1. MySQL/MariaDB server >= 5.5.3
2. ImageMagick (command-line ImageMagick or GraphicsMagick preferred).
3. [APC (Alternative PHP Cache)](http://php.net/manual/en/book.apc.php),
[XCache](http://xcache.lighttpd.net/) or
[Memcached](http://www.php.net/manual/en/intro.memcached.php)
Contributing
------------
You can contribute to vichan by:
* Developing patches/improvements/translations and using GitHub to submit pull requests
* Providing feedback and suggestions
* Writing/editing documentation
If you need help developing a patch, please join our IRC channel.
**Much like Arch Linux, infinity should be considered ``rolling release''. Unlike upstream vichan, we have no install.php. Database schema and templates are changed often and it is on you to read the Git log before updating!**
Installation
-------------
1. Download and extract Tinyboard to your web directory or get the latest
development version with:
git clone git://github.com/ctrlcctrlv/8chan.git
2. Copy secrets.example.php to secrets.php and edit it.
3. Navigate to ```install.php``` in your web browser and follow the
prompts.
4. vichan should now be installed. Log in to ```mod.php``` with the
default username and password combination: **admin / password**.
Please remember to change the administrator account password.
See also: [Configuration Basics](http://tinyboard.org/docs/?p=Config).
Upgrade
-------
To upgrade from any version of Tinyboard or vichan:
Either run ```git pull``` to update your files, if you used git, or
backup your ```inc/instance-config.php```, replace all your files in place
(don't remove boards etc.), then put ```inc/instance-config.php``` back and
finally run ```install.php```.
Support
--------
vichan is still beta software -- there are bound to be bugs. If you find a
bug, please report it.
If you need assistance with installing, configuring, or using vichan, you may
find support from a variety of sources:
* If you're unsure about how to enable or configure certain features, make
sure you have read the comments in ```inc/config.php```.
* Check out an [official vichan board](http://int.vichan.net/devel/).
* You can join vichan's IRC channel for support
[irc.6irc.net #vichan-devel](irc://irc.6irc.net/vichan-devel)
### Tinyboard support
8chan is based on a Tinyboard, so both engines have very much in common. These
links may be helpful for you as well:
* Tinyboard documentation can be found [here](http://tinyboard.org/docs/).
* You can join Tinyboard's IRC channel for support and general queries:
[irc.datnode.net #tinyboard](irc://irc.datnode.net/tinyboard).
* You may find help at [tinyboard.org](http://tinyboard.org/#help).
Donations
---------
Do you like our work? You can motivate us financially to do better ;)
* Bitcoin: [![tip for next commit](http://tip4commit.com/projects/708.svg)](http://tip4commit.com/projects/708)
You can also ask us to develop some feature specially for you <3. Join our IRC
channel and ask for a quote (there are a few of us, who work with the codebase
and are skilled enough to develop such features pretty quickly).
CLI tools
-----------------
There are a few command line interface tools, based on Tinyboard-Tools. These need
to be launched from a Unix shell account (SSH, or something). They are located in a ```tools/```
directory.
You actually don't need these tools for your imageboard functioning, they are aimed
at the power users. You won't be able to run these from shared hosting accounts
(i.e. all free web servers).
Localisation
------------
Want to have vichan/8chan in your language? You can contribute your translations at this URL:
https://www.transifex.com/projects/p/tinyboard-vichan-devel/
Oekaki
------
vichan makes use of [wPaint](https://github.com/websanova/wPaint) for oekaki. After you pull the repository, however, you will need to download wPaint separately using git's `submodule` feature. Use the following commands:
Basic requirements:
A computer running a Unix or Unix-like OS(infinity has been specifically tested with and is known to work under Ubuntu 14.x), Apache, MySQL, and PHP
* Make sure Apache has read/write access to the directory infinity resides in.
* `install.php` is not maintained. Don't use it.
* As of February 22, 2015, you need the [DirectIO module (dio.so)](http://php.net/manual/en/ref.dio.php). This is for compatibility with NFS.
Step 1. Create infinity's database from the included install.sql file. Enter mysql and create an empty database named 'infinity'. Then cd into the infinity base directory and run:
```
git submodule init
git submodule update
mysql -uroot -p infinity < install.sql
echo '+ <a href="https://github.com/ctrlcctrlv/infinity">infinity</a> '`git rev-parse HEAD|head -c 10` > .installed
```
To enable oekaki, add all the scripts listed in `js/wpaint.js` to your `instance-config.php`.
Step 2. /inc/secrets.php does not exist by default, but infinity needs it in order to function. To fix this, cd into /inc/ and run:
```
sudo cp secrets.example.php secrets.php
```
WebM support
Now open secrets.php and edit the $config['db'] settings to point to the 'infinity' MySQL database you created in Step 1. 'user' and 'password' refer to your MySQL login credentials. It should look something like this when you're finished:
```
$config['db']['server'] = 'localhost';
$config['db']['database'] = 'infinity';
$config['db']['prefix'] = '';
$config['db']['user'] = 'root';
$config['db']['password'] = 'password';
$config['timezone'] = 'UTC';
$config['cache']['enabled'] = 'apc';
```
Step 3.(Optional) By default, infinity will ignore any changes you make to the template files until you log into mod.php, go to Rebuild, and select Flush Cache. You may find this inconvenient. To make infinity automatically accept your changes to the template files, set $config['twig_cache'].
Step 4. Infinity can function in a *very* barebones fashion after the first two steps, but you should probably install these additional packages if you want to seriously run it and/or contribute to it. ffmpeg may fail to install under certain versions of Ubuntu. If it does, remove it from this script and install it via an alternate method. Make sure to run the below as root:
```
apt-get install graphicsmagick gifsicle php5-fpm mysql-client php5-mysql php5-cli php-pear php5-apcu php5-dev; add-apt-repository ppa:jon-severinsson/ffmpeg; add-apt-repository ppa:nginx/stable; apt-get update; apt-get install nginx ffmpeg; pear install Net_DNS2; pecl install "channel://pecl.php.net/dio-0.0.7"
```
Page Generation
------------
Read `inc/lib/webm/README.md` for information about enabling webm.
A lot of the static pages (claim.html, boards.html, index.html) need to be regenerated every so often. You can do this with a crontab.
Static Pages
------------
Some pages like `/faq.html` need to be pre-generated:
```
$ php faq.php > faq.html
```cron
*/10 * * * * cd /srv/http; /usr/bin/php /srv/http/boards.php
*/5 * * * * cd /srv/http; /usr/bin/php /srv/http/claim.php > /srv/http/claim.html
*/20 * * * * cd /srv/http; /usr/bin/php -r 'include "inc/functions.php"; rebuildThemes("bans");'
*/5 * * * * cd /srv/http; /usr/bin/php /srv/http/index.php
```
vichan API
----------
vichan provides by default a 4chan-compatible JSON API. For documentation on this, see:
https://github.com/vichan-devel/vichan-API/ .
License
--------
See [LICENSE.md](http://github.com/vichan-devel/vichan/blob/master/LICENSE.md).
Also, main.js is empty by default. Run tools/rebuild.php to create it every time you update one of the JS files.
Have fun!

View File

@ -1,7 +0,0 @@
<?php
require_once 'inc/functions.php';
checkBan();
print "<!doctype html><html><head><meta charset='utf-8'><title>"._("Banned?")."</title></head><body>";
print "<h1>"._("You are not banned.")."</h1>";
print "</body></html>";
?>

View File

@ -8,7 +8,7 @@ header("Expires: 0");
function get_custom_banner(&$b) {
# Validate the board name
if (!(isset($b) && preg_match('/^[a-z0-9]{1,10}$/', $b)))
if (!(isset($b) && preg_match('/^[a-z0-9+]{1,30}$/', $b)))
return null;
# Check if directory exists

View File

@ -1,7 +1,6 @@
<?php
include "inc/functions.php";
include "inc/mod/auth.php";
include "inc/countries.php";
$admin = isset($mod["type"]) && $mod["type"]<=30;
@ -10,35 +9,64 @@ if (php_sapi_name() == 'fpm-fcgi' && !$admin) {
error('Cannot be run directly.');
}
$boards = listBoards();
$all_tags = array();
$total_posts_hour = 0;
$total_posts = 0;
$write_maxes = false;
function to_tag($str) {
$str = trim($str);
$str = strtolower($str);
$str = str_replace(['_', ' '], '-', $str);
return $str;
}
if (!file_exists('maxes.txt') || filemtime('maxes.txt') < (time() - (60*60))) {
$fp = fopen('maxes.txt', 'w+');
$write_maxes = true;
}
foreach ($boards as $i => $board) {
//$query = prepare(sprintf("SELECT (SELECT MAX(id) from ``posts_%s``) AS max, (SELECT MAX(id) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) < DATE_SUB(NOW(), INTERVAL 1 HOUR)) AS oldmax, (SELECT MAX(id) from ``posts_%s``) AS max_d, (SELECT MAX(id) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) < DATE_SUB(NOW(), INTERVAL 1 DAY)) AS oldmax_d, (SELECT count(id) FROM ``posts_%s``) AS count;", $board['uri'], $board['uri'], $board['uri'], $board['uri'], $board['uri']));
$query = prepare(sprintf("
SELECT MAX(id) max, (SELECT COUNT(*) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) > DATE_SUB(NOW(), INTERVAL 1 DAY)) ppd,
SELECT IFNULL(MAX(id),0) max,
(SELECT COUNT(*) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) > DATE_SUB(NOW(), INTERVAL 1 HOUR)) pph,
(SELECT count(id) FROM ``posts_%s``) count,
(SELECT COUNT(DISTINCT ip) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) > DATE_SUB(NOW(), INTERVAL 3 DAY)) uniq_ip
FROM ``posts_%s``
", $board['uri'], $board['uri'], $board['uri'], $board['uri'], $board['uri']));
$query->execute() or error(db_error($query));
$r = $query->fetch(PDO::FETCH_ASSOC);
$tquery = prepare("SELECT `tag` FROM ``board_tags`` WHERE `uri` = :uri");
$tquery->execute([":uri" => $board['uri']]) or error(db_error($tquery));
$r2 = $tquery->fetchAll(PDO::FETCH_ASSOC);
$tags = array();
if ($r2) {
foreach ($r2 as $ii => $t) {
$tag=to_tag($t['tag']);
$tags[] = $tag;
if (!isset($all_tags[$tag])) {
$all_tags[$tag] = (int)$r['uniq_ip'];
} else {
$all_tags[$tag] += $r['uniq_ip'];
}
}
}
$pph = $r['pph'];
$ppd = $r['ppd'];
$total_posts_hour += $pph;
$total_posts += $r['max'];
$boards[$i]['pph'] = $pph;
$boards[$i]['ppd'] = $ppd;
$boards[$i]['ppd'] = $pph*24;
$boards[$i]['max'] = $r['max'];
$boards[$i]['uniq_ip'] = $r['uniq_ip'];
$boards[$i]['tags'] = $tags;
if ($write_maxes) fwrite($fp, $board['uri'] . ':' . $boards[$i]['max'] . "\n");
}
if ($write_maxes) fclose($fp);
usort($boards,
function ($a, $b) {
@ -56,7 +84,7 @@ foreach ($boards as $i => &$board) {
if ($board_config && $board['uri'] !== 'int') {
$board_config = str_replace('$config', '$boardCONFIG', $board_config);
$board_config = str_replace('<?php', '', $board_config);
eval($board_config);
@eval($board_config);
}
$showboard = $board['indexed'];
$locale = isset($boardCONFIG['locale'])?$boardCONFIG['locale']:'en';
@ -86,17 +114,39 @@ foreach ($boards as $i => &$board) {
$n_boards = sizeof($boards);
$t_boards = $hidden_boards_total + $n_boards;
$boards = array_values($boards);
arsort($all_tags);
$config['additional_javascript'] = array('js/jquery.min.js', 'js/jquery.tablesorter.min.js');
$body = Element("8chan/boards.html", array("config" => $config, "n_boards" => $n_boards, "t_boards" => $t_boards, "hidden_boards_total" => $hidden_boards_total, "total_posts" => $total_posts, "total_posts_hour" => $total_posts_hour, "boards" => $boards, "last_update" => date('r'), "uptime_p" => shell_exec('uptime -p')));
$body = Element("8chan/boards-tags.html", array("config" => $config, "n_boards" => $n_boards, "t_boards" => $t_boards, "hidden_boards_total" => $hidden_boards_total, "total_posts" => $total_posts, "total_posts_hour" => $total_posts_hour, "boards" => $boards, "last_update" => date('r'), "uptime_p" => shell_exec('uptime -p'), 'tags' => $all_tags, 'top2k' => false));
$html = Element("page.html", array("config" => $config, "body" => $body, "title" => "Boards on &infin;chan"));
$boards_top2k = $boards;
array_splice($boards_top2k, 2000);
$boards_top2k = array_values($boards_top2k);
$body = Element("8chan/boards-tags.html", array("config" => $config, "n_boards" => $n_boards, "t_boards" => $t_boards, "hidden_boards_total" => $hidden_boards_total, "total_posts" => $total_posts, "total_posts_hour" => $total_posts_hour, "boards" => $boards_top2k, "last_update" => date('r'), "uptime_p" => shell_exec('uptime -p'), 'tags' => $all_tags, 'top2k' => true));
$html_top2k = Element("page.html", array("config" => $config, "body" => $body, "title" => "Boards on &infin;chan"));
if ($admin) {
echo $html;
} else {
foreach ($boards as $i => &$b) { unset($b['img']); }
file_write("boards.json", json_encode($boards));
array_splice($boards, 20);
file_write("tags.json", json_encode($all_tags));
foreach ($boards as $i => $b) {
if (in_array($b['uri'], $config['no_top_bar_boards'])) {
unset($boards[$i]);
}
unset($boards[$i]['img']);
}
array_splice($boards, 48);
$boards = array_values($boards);
file_write("boards-top20.json", json_encode($boards));
file_write("boards.html", $html);
file_write("boards.html", $html_top2k);
file_write("boards_full.html", $html);
echo 'Done';
}

View File

@ -109,7 +109,7 @@ $body = "<p>Please read the following instructions carefully:</p>
<p>The username of the admin of this board is <strong>{$mods[0]['username']}</strong>. Please write this down, you will need it to log in. It cannot be changed.</p>
<p>A new password has been generated for this board. It is <tt>{$password}</tt> . Please write this down, you will need it to log in. <em>If you forget your password, it cannot be changed. You must wait to reclaim the board again! Do not lose it.</em></p>
<p>The URL you use to manage your board is <a href='/mod.php'>8chan.co/mod.php</a>. Log in using the details above. Note: The board can still be claimed by another user until you log in for the first time or if it still meets the inactivity criteria, so post something!</p>
<p>The URL you use to manage your board is <a href='/mod.php'>https://8ch.net/mod.php</a>. Log in using the details above. Note: The board can still be claimed by another user until you log in for the first time or if it still meets the inactivity criteria, so post something!</p>
<p>
";

View File

@ -1,5 +1,61 @@
<?php
include 'inc/functions.php';
if (php_sapi_name() == 'fpm-fcgi') {
error('Cannot be run directly.');
}
function last_activity($board) {
// last post
$query = prepare(sprintf("SELECT MAX(time) AS time FROM posts_%s", $board));
$query->execute();
$row = $query->fetch();
$ago = (new DateTime)->sub(new DateInterval('P1W'));
$mod_ago = (new DateTime)->sub(new DateInterval('P2W'));
error('Automatic claiming is no longer available. To claim a board, send your request to admin@8chan.co along with the IP you used to post on that board.');
$last_activity_date = new DateTime();
$last_mod_date = new DateTime();
$last_activity_date->setTimestamp($row['time']);
$query = query("SELECT id, username FROM mods WHERE boards = '$board' AND type = 20");
$mods = $query->fetchAll();
if ($mods) {
$mod = $mods[0]['id'];
$query = query("SELECT MAX(time) AS time FROM modlogs WHERE `mod` = $mod");
$a = $query->fetchAll(PDO::FETCH_COLUMN);
if ($a[0]) {
$last_mod_date->setTimestamp($a[0]);
if (!$row['time'])
$last_activity_date->setTimestamp($a[0]);
} else {// no one ever logged in, try board creation time
$query = query("SELECT UNIX_TIMESTAMP(time) AS time FROM board_create WHERE uri = '$board'");
$crt = $query->fetchAll(PDO::FETCH_COLUMN);
if ($crt) $last_activity_date->setTimestamp($crt[0]);
$last_mod_date = false;
}
}
if (($last_activity_date < $ago or ($last_mod_date and $last_mod_date < $mod_ago))) {
return array($last_activity_date, $last_mod_date, $mods);
}
else {
return false;
}
}
$q = query("SELECT uri FROM boards");
$boards = $q->fetchAll(PDO::FETCH_COLUMN);
$delete = array();
foreach($boards as $board) {
$last_activity = last_activity($board);
if ($last_activity) {
list($last_activity_date, $last_mod_date, $mods) = $last_activity;
$last_mod_f = $last_mod_date ? $last_mod_date->format('Y-m-d H:i:s') : '<em>never</em>';
$last_activity_f = $last_activity_date ? $last_activity_date->format('Y-m-d H:i:s') : '<em>never</em>';
$delete[] = array('board' => $board, 'last_activity_date' => $last_activity_f, 'last_mod' => $last_mod_date, 'last_mod_f' => $last_mod_f);
}
}
$body = Element("8chan/claim.html", array("config" => $config, "delete" => $delete));
echo Element("page.html", array("config" => $config, "body" => $body, "title" => _("Claim"), "subtitle" => _("Take deserted boards back from their owners")));

View File

@ -1,36 +1,17 @@
<?php
include "inc/functions.php";
include "inc/lib/ayah/ayah.php";
include "inc/mod/auth.php";
$cbRecaptcha = false;
//don't load recaptcha LIB unless its enabled!
if ($config['cbRecaptcha']){
$cbRecaptcha = true;
include "inc/lib/recaptcha/recaptchalib.php";
}
checkBan('*');
$ayah = (($config['ayah_enabled']) ? new AYAH() : false);
if (!isset($_POST['uri'], $_POST['title'], $_POST['subtitle'], $_POST['username'], $_POST['password'])) {
if (!$ayah){
$game_html = '';
} else {
$game_html = '<tr><th>'._('Game').'</th><td>' . $ayah->getPublisherHTML() . '</td></tr>';
}
if (!$cbRecaptcha){
$recapcha_html = '';
} else {
$recapcha_html = '<tr><th>reCaptcha</th><td>' . recaptcha_get_html($config['recaptcha_public'], NULL, TRUE) . '</td></tr>';
}
include '8chan-captcha/functions.php';
$password = base64_encode(openssl_random_pseudo_bytes(9));
$body = Element("8chan/create.html", array("config" => $config, "password" => $password, "game_html" => $game_html, "recapcha_html" => $recapcha_html));
$captcha = generate_captcha($config['captcha']['extra']);
$body = Element("8chan/create.html", array("config" => $config, "password" => $password, "captcha" => $captcha));
echo Element("page.html", array("config" => $config, "body" => $body, "title" => _("Create your board"), "subtitle" => _("before someone else does")));
}
@ -42,27 +23,14 @@ $subtitle = $_POST['subtitle'];
$username = $_POST['username'];
$password = $_POST['password'];
$resp = ($cbRecaptcha) ? recaptcha_check_answer ($config['recaptcha_private'],
$_SERVER["REMOTE_ADDR"],
$_POST["recaptcha_challenge_field"],
$_POST["recaptcha_response_field"]):false;
$resp = file_get_contents($config['captcha']['provider_check'] . "?" . http_build_query([
'mode' => 'check',
'text' => $_POST['captcha_text'],
'extra' => $config['captcha']['extra'],
'cookie' => $_POST['captcha_cookie']
]));
if ($resp != false){
$passedCaptcha = $resp->is_valid;
} else {
$passedCaptcha = true;
}
if (!$ayah){
$score = true;
} else {
$score = $ayah->scoreResult();
}
if (!$score)
error(_('You failed the game'));
if (!$passedCaptcha)
error(_('You failed to enter the reCaptcha correctly'));
if (!preg_match('/^[a-z0-9]{1,10}$/', $uri))
if (!preg_match('/^[a-z0-9]{1,30}$/', $uri))
error(_('Invalid URI'));
if (!(strlen($title) < 40))
error(_('Invalid title'));
@ -70,6 +38,8 @@ if (!(strlen($subtitle) < 200))
error(_('Invalid subtitle'));
if (!preg_match('/^[a-zA-Z0-9._]{1,30}$/', $username))
error(_('Invalid username'));
if ($resp !== '1')
error($config['error']['captcha']);
foreach (listBoards() as $i => $board) {
if ($board['uri'] == $uri)

40
dnsbls_bypass.php Normal file
View File

@ -0,0 +1,40 @@
<?php
include 'inc/functions.php';
include '8chan-captcha/functions.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$captcha = generate_captcha($config['captcha']['extra']);
$html = "{$captcha['html']}<br/>
<input class='captcha_text' name='captcha_text' size='25' maxlength='6' autocomplete='off' type='text'>
<input class='captcha_cookie' name='captcha_cookie' type='hidden' autocomplete='off' value='{$captcha['cookie']}'><br/>";
$body = Element("8chan/dnsbls.html", array("config" => $config, "ayah_html" => $html));
echo Element("page.html", array("config" => $config, "body" => $body, "title" => _("Bypass DNSBL"), "subtitle" => _("Post even if blocked")));
} else {
$resp = file_get_contents($config['captcha']['provider_check'] . "?" . http_build_query([
'mode' => 'check',
'text' => $_POST['captcha_text'],
'extra' => $config['captcha']['extra'],
'cookie' => $_POST['captcha_cookie']
]));
if ($resp === '1') {
$tor = checkDNSBL($_SERVER['REMOTE_ADDR']);
if (!$tor) {
$query = prepare('INSERT INTO ``dnsbl_bypass`` VALUES(:ip, NOW()) ON DUPLICATE KEY UPDATE `created`=NOW()');
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->execute() or error(db_error($query));
} else {
$cookie = bin2hex(openssl_random_pseudo_bytes(16));
$query = prepare('INSERT INTO ``tor_cookies`` VALUES(:cookie, NOW(), 0)');
$query->bindValue(':cookie', $cookie);
$query->execute() or error(db_error($query));
setcookie("tor", $cookie);
}
echo Element("page.html", array("config" => $config, "body" => '', "title" => _("Success!"), "subtitle" => _("You may now go back and make your post.")));
} else {
error(_('You failed the CAPTCHA') . _('. <a href="https://8ch.net/dnsbls_bypass.php">Try again.</a> If it\'s not working, email admin@8chan.co for support.'));
}
}

View File

@ -4,11 +4,11 @@ include "inc/functions.php";
if (!(php_sapi_name() == "cli")) {
die('nope');
}
$protected = array('burgers', 'cow', 'wilno', 'cute');
$protected = array('burgers', 'cow', 'wilno', 'cute', 'yoga');
$q = query("SELECT uri FROM boards");
$boards = $q->fetchAll(PDO::FETCH_COLUMN);
$now = new DateTime();
$ago = (new DateTime)->sub(new DateInterval('P3D'));
$ago = (new DateTime)->sub(new DateInterval('P14D'));
$mod_ago = (new DateTime)->sub(new DateInterval('P7D'));
// Find out the last activity for our board
@ -53,7 +53,7 @@ foreach($boards as $board) {
}
}
if (($last_activity_date < $ago or ($last_mod_date and $last_mod_date < $mod_ago)) and (int)$count['count'] < 30 and isset($mods[0]['id'])) {
if (($last_activity_date < $ago or ($last_mod_date and $last_mod_date < $mod_ago)) and (int)$count['count'] < 5 and isset($mods[0]['id'])) {
#if (($last_activity_date < $ago or ($last_mod_date and $last_mod_date < $mod_ago)) and isset($mods[0]['id'])) {
echo $board, ' ', $last_activity_date->format('Y-m-d H:i:s'), ' ', ($last_mod_date ? $last_mod_date->format('Y-m-d H:i:s') : 'false'), ' ', $count['count'], ' ', $mod, "\r\n";
$delete[] = array('board' => $board, 'last_activity' => $last_activity_date, 'last_mod' => $last_mod_date, 'mod' => isset($mods[0]['username']) ? $mods[0]['username'] : false, 'count' => $count['count']);
@ -63,12 +63,11 @@ if ($argc > 1) {
$f = fopen('rip.txt', 'a');
fwrite($f, "--\r\n".date('c')."\r\n");
foreach($delete as $i => $d){
file_get_contents('http://8chan.co/listboards.php');
$s = "RIP /".$d['board']."/, created by ".($d['mod']?$d['mod']:'?')." and last active on ".$d['last_activity']->format('Y-m-d H:i:s.').($d['last_mod'] ? ' Mod last active on ' . $d['last_mod']->format('Y-m-d H:i:s.') : ' Mod never active.') . " Number of posts: {$d['count']}." . "\r\n";
echo $s;
fwrite($f, $s);
openBoard($d['board']);
if (!openBoard($d['board'])) continue;;
$query = prepare('DELETE FROM ``boards`` WHERE `uri` = :uri');
$query->bindValue(':uri', $board['uri']);
@ -129,13 +128,14 @@ foreach($delete as $i => $d){
}
// Delete entire board directory
rrmdir($board['uri'] . '/');
exec('rm -rf ' . $board['uri'] . '/');
rrmdir('static/banners/' . $board['uri']);
file_unlink("stylesheets/board/{$board['uri']}.css");
// HAAAAAX
if($config['dir']['img_root'] != '')
rrmdir($config['dir']['img_root'] . $board['uri']);
cache::delete('board_' . $board['uri']);
if ($config['cache']['enabled']) cache::delete('board_' . $board['uri']);
_syslog(LOG_NOTICE, "Board deleted: {$board['uri']}");
if ($d['mod']) {

133
faq.php
View File

@ -3,21 +3,43 @@
include "inc/functions.php";
$body = <<<EOT
<style>img{max-width:100%}</style>
<div class="ban">
<h2>What is chan?</h2>
<p>∞chan allows anyone who wants to to become the owner of their own board. They are given free reign to institute whatever rules they wish on their board, as long as they do not affect the global rules.</p>
<h2>What is 8chan?</h2>
<p>8ch.net is a site running 'infinity', which is an open source software that allows anyone to create and manage their own anonymous imageboard without any programming or webhosting experience for free.</p>
<p>The idea of ∞chan is to minimize the trust needed in individual moderators to prevent "do it for free" moderators from accepting bribes (sexual or otherwise) to corrupt the whole site.</p>
<h2>What is an imageboard?</h2>
<p>An imageboard is a type of internet forum which lets users post text and images anonymously (without name) about any topic, or no topic at all. Unlike forums, imageboards do not hold old content permamently, and old threads are pruned as new ones are created.</p>
<p>The imageboard format holds several advantages against traditional forums:</p>
<ol>
<li>There is no registration process, which allows for anyone to post what they like without having to jump through hoops.</li>
<li>Users do not have names and thus feel no reason to build up an identity or reputation for themselves. Post are judged based on their content rather than who made them.</li>
<li>sharing images and multimedia content is as easy as saving and uploading it to the site.</li>
</ol>
<p>The largest board owners are promoted to Global Volunteers. This is devised by an algorithm, but volunteers are only promoted with my express approval.</p>
<h2>How is 8chan run?</h2>
<p>8chan is a service that hosts a large selection of imageboards to browse. These boards are maintained by their respective board owners, who are not affiliated with the 8chan global staff.</p>
<p>The 8chan global staff are responsible for maintaining the site as a whole and protecting it from spam and illegal content. The administration is NOT responsible for enforcing any rules outside of the global rule. Any complaints about the content or management of a board should be addressed towards the owner of the board, unless it violates the law of the United States of America, or global policy.</p>
<p>All new global volunteers are sent a message congratulating them on becoming global volunteers and explaining the position. Global volunteers simply delete CP, excessive spam and other illegal content that comes on the server and ban the posting users and their IP ranges.</p>
<h2>How do I post a new thread?</h2>
<p>Navigate to the board you would like to post on, fill out the post form, and click "New Thread". On many boards, you are required to upload an image, but if you do not have one you can also draw a picture by clicking "Oekaki". No boards require you to fill out a name or email address.</p>
<img src="/static/faq/new-thread.png" alt="New thread FAQ">
<p>There is a large penalty for abusing their powers to ban users for other reasons. That penalty is that I will not only remove their global volunteer position, but also commandeer their board. I will then find another suitable owner for it among the board's users and give it to them.</p>
<h2>How do I comment on a thread?</h2>
<p>On 8chan, threads are ordered by newest to oldest and have no "score" like on other websites. There are no upvotes or "Like" buttons. This allows even unpopular opinions to rise to the top. The replies in threads are ordered oldest to newest and similarly have no score.</p>
<p>To reply to a thread, click [Reply] on any thread on a board's index.</p>
<img src="/static/faq/new-reply.png" alt="New thread FAQ">
<p>Thus, the only people who can delete content from 8chan.co are those who have a stake in the site itself because they own the boards. Thus, they are not really doing it for free - they are simply protecting their boards by protecting the site as a whole. It is in their interest to keep the site free of illegal content so that their boards stay up. If they abuse their power, the board that they worked hard to create is stripped from them.</p>
<p>Fill out your reply. Only the "Comment" field is required for replies on all boards.</p>
<img src="/static/faq/new-reply2.png" alt="New thread FAQ">
<p>This means that there's only one person that needs to be trusted: me. If I could have found a way to remove myself from the trust model, I would have, but that is impossible given someone has to run the server.</p>
<p>Your reply will be highlighted on the page.</p>
<img src="/static/faq/new-reply3.png" alt="New thread FAQ">
<h2>How do I reply to another poster?</h2>
<p>Click the number of their post. A reply box will open automatically prepopulated with the post number you're replying to. Write your post under their post number. Similarly, when people reply to you, it will show (You) after the post number as a hint.</p>
<img src="/static/faq/new-reply4.png" alt="New thread FAQ">
<h2>Are there any global rules regarding content?</h2>
@ -26,30 +48,51 @@ $body = <<<EOT
<li>Do not post, request, or link to any content illegal in the United States of America. Do not create boards with the sole purpose of posting or spreading such content.</li>
</ul>
<p>Other than that, you are free to institute whatever rules you want on your board.</p>
<p><a href="/obscenity.html">More information about US obscenity laws and how they relate to 8chan boards</a></p>
<p><a href="/dost.html">More information about the Dost test</a></p>
<p><a href="/personhood.html">Just who is this 8chan person anyway?</a></p>
<h2>How do I add more volunteers?</h2>
<p>Give them your password. If you don't trust them enough for that, you probably shouldn't be making them a volunteer.</p>
<p>You may do this in your board settings, click on "Edit board volunteers".
<h2>How do I manage my board?</h2>
<p>Go to <a href="/mod.php">the volunteer panel</a> and click on the board link for your board.</p>
<h2>How do I contact the admin?</h2>
<p>The admin can be reached at <tt>admin at 8chan dot co</tt>.</p>
<h2>Help! My board has been deleted!</h2>
<p>Were you inactive for longer than one week? Were there no posts on the board for 72 hours?</p>
<h2>What's your privacy policy?</h2>
<p>Find it <a href="/privacy.pdf">here</a>.</p>
<p>If either of those is true, the board was deleted automatically. You are free to recreate it. I cannot restore it, so don't bother emailing me about it.</p>
<h2>Help! My board has been deleted!</h2>
<p>As of November 13th, 2014, board expiration no longer occurs.</p>
<p>You still may lose access to your board, however, if you fail to log in for two weeks or it receives no posts for a week. See <a href="/claim.html">here</a> for a list of boards that are available for reclaiming.</p>
<h2>How do I post as a volunteer on my board?</h2>
<p>Make sure you are using the volunteer interface to view your board. The URL of your browser should be <a href="https://8chan.co/mod.php?/yourboard"><tt>https://8chan.co/mod.php?/yourboard</tt></a>. Then, put "## Board Volunteer" in the name field. Write your post and click "Reply". It will appear with your volunteer capcode.</p>
<p>Make sure you are using the volunteer interface to view your board. The URL of your browser should be <a href="/mod.php?/yourboard"><tt>https://8ch.net/mod.php?/yourboard</tt></a>.</p>
<p>If you are the owner of the board, put "## Board Owner" in the name field. If someone else is the owner and you are just assisting them, put "## Board Volunteer" in the name field. Write your post and click "Reply". It will appear with your capcode.</p>
<h2>Help! The owner of X board is doing something I don't like!</h2>
<p>If they aren't doing anything illegal, I can't help you. I don't dictate how board owners should manage their boards.</p>
<p>If they are doing something illegal, email me.</p>
<h2>Can you give me X board?</h2>
<p>If the owner of the board is inactive or the board is broken due to bad CSS, sure. Send me an email.</p>
<p>If the owner of the board is inactive or the board is broken due to bad CSS, sure. Send me an email. You can see a list of boards that qualify for being taken over <a href="/claim.html">here</a>.</p>
<h2>Can you add some new feature?</h2>
<p>Open a <a href="https://github.com/ctrlcctrlv/8chan/issues">Github issue</a>. Better yet, write it yourself and open a pull request.
<h2>What is "sage"?</h2>
<p>Posters may reply to threads without bumping them to the top of the index by putting "sage" in the email field.</p>
<h2>What is a tripcode?</h2>
<p>Most posts on 8chan are made anonymously, but this is not the only way to post. The name field can be used <em>four</em> ways to establish identity:</p>
<ol>
<li>By simply writing a name in the box. This is insecure as any other poster can write the same name.</li>
<li>By writing a # character and then a password. Putting #example in the name field would become !KtW6XcghiY. This is reasonably secure, but with increasing GPU speeds these tripcodes can be cracked in a few days by a dedicated attacker.</li>
<li>By writing two # characters and then a password. Putting ##example in the name field would become !!Dz.MSNRw9M. This is quite secure, but it relies on a secret salt on the server so the code will not work on sites other than 8chan.</li>
<li>Board owners and volunteers can enter the special codes "## Board Owner" and "## Board Volunteer" which become <em>capcodes</em> that display after the name. The 8chan administrator can type "## Admin" which becomes <span class="capcode" title="This post was written by the global 8chan administrator."> <i class="fa fa-wheelchair" style="color:blue;"></i> <span style="color:red">8chan Administrator</span></span>.</li>
</ol>
<p>Please note, many boards on 8chan have an option set called "Forced anonymity" which causes the name field to not work. This is because many users (and therefore board owners) do not like tripcode users.</p>
<h2>How do I format my text?</h2>
<ul>
<li>**spoiler** or [spoiler]spoiler[/spoiler] -&gt; spoiler</li>
@ -58,15 +101,19 @@ $body = <<<EOT
<li>__underline__ -&gt; <u>underline</u></li>
<li>==heading== -&gt; <span class='heading'>heading</span> (must be on own line)</li>
<li>~~strikethrough~~ -&gt; <s>strikethrough</s></li>
<li>[aa] tags for ASCII/JIS art (escape formatting)</li>
<li>[code] tags if enabled by board owner</li>
<li>[tex] tags if enabled by board owner</li>
<li>$$ and \( \) LaTeX tags if enabled by board owner</li>
</ul>
<h2>How are featured boards chosen?</h2>
<p>Top fifteen boards excluding /meta/, /b/ and /int/.</p>
<p>Top twenty-five boards excluding /meta/, /b/, /operate/, /boards/ and /news+/.</p>
<h2>Who owns /meta/, /b/, and /int/?</h2>
<p>No one, so they are <em>de facto</em> property of the administration.</p>
<h2>Who owns boards like /b/, /news+/ and /operate/?</h2>
<p>No one, so they are <em>de facto</em> managed by the administration.</p>
<h2>Why does <a href="/banned">https://8ch.net/banned</a> say that I'm banned? I can still use the boards?</h2>
<p>8chan is centered around user created boards. That's a board with CSS that makes it look like the ban page, not an official page. You've been tricked. 8chan has no official ban check page.</p>
<h2>Where's the mobile app?</h2>
<p>There is no official mobile app, however there is an unofficial Android app at <a href="https://github.com/wingy/Exodus/releases">wingy/Exodus</a>.</p>
@ -74,15 +121,55 @@ $body = <<<EOT
<p>I don't provide support for this app, ask the developer of it if you have a problem with it.</p>
<h2>Where's the archive?</h2>
<p>There isn't one yet and there will never be an official archive.</p>
<p><s>There isn't one yet and there will never be an official archive.</s></p>
<p>Given that archives are inevitable and will be created anyway via <a href="https://archive.today">archive.today</a>, Google cache, and anyone who installs Asagi, I'm softening my stance on this. Currently, 8archive.moe provides our archive, and I may set up an official one. <strong>All archives officially partnered with us will be opt-in by our board owners, not opt-out. Archives who archive boards that have not opted in will be considered pirate archives, and legal action may be taken.</strong></p>
<h2>Can I have a list of all API endpoints for getting raw data from 8chan?</h2>
<p>
Assuming the /b/ board, they are as follows:</p>
<ul>
<li><a href="https://8ch.net/b/index.rss">https://8ch.net/b/index.rss</a> - RSS formatted index so that you can watch smaller boards and get updates when they get new posts using a feed reader like Thunderbird or Feedly.</li>
<li><a href="https://8ch.net/b/0.json">https://8ch.net/b/0.json</a> - Index of all threads on page 0 of /b/.</li>
<li><a href="https://8ch.net/b/res/1.json">https://8ch.net/b/res/1.json</a> - All replies of thread 1 on /b/.</li>
<li><a href="https://8ch.net/b/threads.json">https://8ch.net/b/threads.json</a> - Thread index of all 15 pages of /b/.</li>
</ul>
<p>There are also endpoints for getting information about 8chan's boards:</p>
<ul>
<li><a href="https://8ch.net/boards.json">https://8ch.net/boards.json</a> - Boards on 8chan (warning, 1MB+)</a></li>
<li><a href="https://8ch.net/settings.php?board=b">https://8ch.net/settings.php?board=b</a> - Board settings of /b/ (JSON format)</li>
</ul>
<p>Just read the data to get an idea of what is exposed and under what attribute names. It should be self explanatory.</p>
<p><strong>Endpoints not listed here, like post.php, catalog.json or boards-top20.json are subject to change or removal at any time!</strong></p>
<h2>I would like to contribute a translation in my language.</h2>
<p>Great! See <a href="/translation.html">this page</a> for more information.</p>
<h2>Are there any publicly available statistics?</h2>
<p>Yes, take a look at <a href="http://stats.4ch.net/8chan/">http://stats.4ch.net/8chan/</a>.
<h2>I got an email from an @8chan.co email address, is that you?</h2>
<p>8chan.co uses <a href="https://cock.li">cock.li</a> to manage our domain's email. cock.li allows anyone to create an email account @8chan.co.</p>
<p>That said, we have quite a few official 8chan.co email addresses. They are:</p>
<ul>
<li>admin at 8chan dot co</li>
<li>dmca at 8chan dot co</li>
<li>claim at 8chan dot co</li>
</ul>
<h2>I would like to send you an encrypted message.</h2>
<p>The current admin contact private key can always be found at <a href="https://8ch.net/pubkey.txt">https://8ch.net/pubkey.txt</a>.</p>
<p>The current key fingerprint is <tt>6F12 EC72 A82A BCA3 5235 063A 10DD C983 901A A183</tt>.</p>
<h2>How do I donate?</h2>
<p>Donations can be sent to 1NpQaXqmCBji6gfX8UgaQEmEstvVY7U32C (Bitcoin) or LUPgSCJt3iGeJXUETVhmnbQ89Riaq1yjZm (Litecoin). PayPal is also accepted @ fredrick.brennan1@gmail.com .</p>
<p>You may also donate monthly via Patreon at <a href="http://www.patreon.com/user?u=162165">http://www.patreon.com/user?u=162165</a>.
<p>Donations can be sent to 1NpQaXqmCBji6gfX8UgaQEmEstvVY7U32C (Bitcoin) or LUPgSCJt3iGeJXUETVhmnbQ89Riaq1yjZm (Litecoin).</p>
<p>I am also a big fan of Monero (XMR). You can send XMR to our <a href="http://openalias.org">OpenAlias</a> in the simplewallet client, or simply send to 49dBJhGhYFxJEfydS6hH6GRyg1W4cDgupdNVtw7j1WtcUY7xPXwNLw6fUVay644viaCcEhMFG1Z7SjjxRXEFDdNWJdvH9kS.</p>
<p>If you would like to support development of the engine that 8chan runs on (infinity), you may also <a href="https://gratipay.com/infinitechan">donate via Gratipay</a>.</p>
<h2>Are you really a cripple?</h2>
<p>Yes.</p>
<img src="/static/Mamoru.jpg" alt="Mamoru" style="width:128px">
</div>
EOT;

View File

@ -54,3 +54,23 @@ function human_time_diff( $from, $to = '' ) {
return $since;
}
function is_billion_laughs($arr1, $arr2) {
$arr = array();
foreach ($arr1 as $k => $v) {
$arr[$v] = $arr2[$k];
}
for ($i = 0; $i <= sizeof($arr); $i++) {
$cur = array_slice($arr, $i, 1);
$pst = array_slice($arr, 0, $i);
if (!$cur) continue;
$kk = array_keys($cur)[0];
$vv = array_values($cur)[0];
foreach ($pst as $k => $v) {
if (str_replace($kk, $vv, $v) != $v)
return true;
}
}
return false;
}

53
inc/8chan-mod-config.php Normal file
View File

@ -0,0 +1,53 @@
<?php
$config['mod']['show_ip'] = GLOBALVOLUNTEER;
$config['mod']['show_ip_less'] = BOARDVOLUNTEER;
$config['mod']['manageusers'] = GLOBALVOLUNTEER;
$config['mod']['noticeboard_post'] = GLOBALVOLUNTEER;
$config['mod']['search'] = GLOBALVOLUNTEER;
$config['mod']['clean_global'] = GLOBALVOLUNTEER;
$config['mod']['view_notes'] = DISABLED;
$config['mod']['create_notes'] = DISABLED;
$config['mod']['edit_config'] = DISABLED;
$config['mod']['debug_recent'] = ADMIN;
$config['mod']['debug_antispam'] = ADMIN;
$config['mod']['noticeboard_post'] = ADMIN;
$config['mod']['modlog'] = GLOBALVOLUNTEER;
$config['mod']['mod_board_log'] = MOD;
$config['mod']['editpost'] = BOARDVOLUNTEER;
$config['mod']['edit_banners'] = MOD;
$config['mod']['edit_flags'] = MOD;
$config['mod']['edit_settings'] = MOD;
$config['mod']['edit_volunteers'] = MOD;
$config['mod']['edit_tags'] = MOD;
$config['mod']['clean'] = BOARDVOLUNTEER;
// new perms
$config['mod']['ban'] = BOARDVOLUNTEER;
$config['mod']['bandelete'] = BOARDVOLUNTEER;
$config['mod']['unban'] = BOARDVOLUNTEER;
$config['mod']['deletebyip'] = BOARDVOLUNTEER;
$config['mod']['sticky'] = BOARDVOLUNTEER;
$config['mod']['cycle'] = BOARDVOLUNTEER;
$config['mod']['lock'] = BOARDVOLUNTEER;
$config['mod']['postinlocked'] = BOARDVOLUNTEER;
$config['mod']['bumplock'] = BOARDVOLUNTEER;
$config['mod']['view_bumplock'] = BOARDVOLUNTEER;
$config['mod']['bypass_field_disable'] = BOARDVOLUNTEER;
$config['mod']['view_banlist'] = BOARDVOLUNTEER;
$config['mod']['view_banstaff'] = BOARDVOLUNTEER;
$config['mod']['public_ban'] = BOARDVOLUNTEER;
$config['mod']['recent'] = BOARDVOLUNTEER;
$config['mod']['ban_appeals'] = BOARDVOLUNTEER;
$config['mod']['view_ban_appeals'] = BOARDVOLUNTEER;
$config['mod']['view_ban'] = BOARDVOLUNTEER;
$config['mod']['reassign_board'] = GLOBALVOLUNTEER;
$config['mod']['move'] = GLOBALVOLUNTEER;
$config['mod']['shadow_capcode'] = 'Global Volunteer';
// Mod pages assignment
$config['mod']['custom_pages']['/tags/(\%b)'] = '8_tags';
$config['mod']['custom_pages']['/reassign/(\%b)'] = '8_reassign';
$config['mod']['custom_pages']['/volunteers/(\%b)'] = '8_volunteers';
$config['mod']['custom_pages']['/flags/(\%b)'] = '8_flags';
$config['mod']['custom_pages']['/banners/(\%b)'] = '8_banners';
$config['mod']['custom_pages']['/settings/(\%b)'] = '8_settings';

638
inc/8chan-mod-pages.php Normal file
View File

@ -0,0 +1,638 @@
<?php
function mod_8_tags ($b) {
global $board, $config;
if (!openBoard($b))
error("Could not open board!");
if (!hasPermission($config['mod']['edit_tags'], $b))
error($config['error']['noaccess']);
if (isset($_POST['tags'])) {
if (sizeof($_POST['tags']) > 5)
error(_('Too many tags.'));
$delete = prepare('DELETE FROM ``board_tags`` WHERE uri = :uri');
$delete->bindValue(':uri', $b);
$delete->execute();
foreach ($_POST['tags'] as $i => $tag) {
if ($tag) {
if (strlen($tag) > 255)
continue;
$insert = prepare('INSERT INTO ``board_tags``(uri, tag) VALUES (:uri, :tag)');
$insert->bindValue(':uri', $b);
$insert->bindValue(':tag', utf8tohtml($tag));
$insert->execute();
}
}
$update = prepare('UPDATE ``boards`` SET sfw = :sfw WHERE uri = :uri');
$update->bindValue(':uri', $b);
$update->bindValue(':sfw', isset($_POST['sfw']));
$update->execute();
}
$query = prepare('SELECT * FROM ``board_tags`` WHERE uri = :uri');
$query->bindValue(':uri', $b);
$query->execute();
$tags = $query->fetchAll();
$query = prepare('SELECT `sfw` FROM ``boards`` WHERE uri = :uri');
$query->bindValue(':uri', $b);
$query->execute();
$sfw = $query->fetchColumn();
mod_page(_('Edit tags'), 'mod/tags.html', array('board'=>$board,'token'=>make_secure_link_token('tags/'.$board['uri']), 'tags'=>$tags, 'sfw'=>$sfw));
}
function mod_8_reassign($b) {
global $board, $config;
if (!openBoard($b))
error("Could not open board!");
if (!hasPermission($config['mod']['reassign_board'], $b))
error($config['error']['noaccess']);
$query = query("SELECT id, username FROM mods WHERE boards = '$b' AND type = 20");
$mods = $query->fetchAll();
if (!$mods) {
error('No mods?');
}
$password = base64_encode(openssl_random_pseudo_bytes(9));
$salt = generate_salt();
$hashed = hash('sha256', $salt . sha1($password));
$query = prepare('UPDATE ``mods`` SET `password` = :hashed, `salt` = :salt WHERE BINARY username = :mod');
$query->bindValue(':hashed', $hashed);
$query->bindValue(':salt', $salt);
$query->bindValue(':mod', $mods[0]['username']);
$query->execute();
$body = "Thanks for your interest in this board. Kindly find the username and password below. You can login at https://8ch.net/mod.php.<br>Username: {$mods[0]['username']}<br>Password: {$password}<br>Thanks for using 8chan!";
modLog("Reassigned board /$b/");
mod_page(_('Edit reassign'), 'blank.html', array('board'=>$board,'token'=>make_secure_link_token('reassign/'.$board['uri']),'body'=>$body));
}
function mod_8_volunteers($b) {
global $board, $config, $pdo;
if (!hasPermission($config['mod']['edit_volunteers'], $b))
error($config['error']['noaccess']);
if (!openBoard($b))
error("Could not open board!");
if (isset($_POST['username'], $_POST['password'])) {
$query = prepare('SELECT * FROM ``mods`` WHERE type = 19 AND boards = :board');
$query->bindValue(':board', $b);
$query->execute() or error(db_error($query));
$count = $query->rowCount();
$query = prepare('SELECT `username` FROM ``mods``');
$query->execute() or error(db_error($query));
$volunteers = $query->fetchAll(PDO::FETCH_ASSOC);
if ($_POST['username'] == '')
error(sprintf($config['error']['required'], 'username'));
if ($_POST['password'] == '')
error(sprintf($config['error']['required'], 'password'));
if (!preg_match('/^[a-zA-Z0-9._]{1,30}$/', $_POST['username']))
error(_('Invalid username'));
if ($count > 10) {
error(_('Too many board volunteers!'));
}
foreach ($volunteers as $i => $v) {
if (strtolower($_POST['username']) == strtolower($v['username'])) {
error(_('Refusing to create a volunteer with the same username as an existing one.'));
}
}
$salt = generate_salt();
$password = hash('sha256', $salt . sha1($_POST['password']));
$query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :salt, 19, :board)');
$query->bindValue(':username', $_POST['username']);
$query->bindValue(':password', $password);
$query->bindValue(':salt', $salt);
$query->bindValue(':board', $b);
$query->execute() or error(db_error($query));
$userID = $pdo->lastInsertId();
modLog('Created a new volunteer: ' . utf8tohtml($_POST['username']) . ' <small>(#' . $userID . ')</small>');
}
if (isset($_POST['delete'])){
foreach ($_POST['delete'] as $i => $d){
$query = prepare('SELECT * FROM ``mods`` WHERE id = :id');
$query->bindValue(':id', $d);
$query->execute() or error(db_error($query));
$result = $query->fetch(PDO::FETCH_ASSOC);
if (!$result) {
error(_('Volunteer does not exist!'));
}
if ($result['boards'] != $b || $result['type'] != BOARDVOLUNTEER) {
error($config['error']['noaccess']);
}
$query = prepare('DELETE FROM ``mods`` WHERE id = :id');
$query->bindValue(':id', $d);
$query->execute() or error(db_error($query));
}
}
$query = prepare('SELECT * FROM ``mods`` WHERE type = 19 AND boards = :board');
$query->bindValue(':board', $b);
$query->execute() or error(db_error($query));
$volunteers = $query->fetchAll(PDO::FETCH_ASSOC);
mod_page(_('Edit volunteers'), 'mod/volunteers.html', array('board'=>$board,'token'=>make_secure_link_token('volunteers/'.$board['uri']),'volunteers'=>$volunteers));
}
function mod_8_flags($b) {
global $config, $mod, $board;
require_once 'inc/image.php';
if (!hasPermission($config['mod']['edit_flags'], $b))
error($config['error']['noaccess']);
if (!openBoard($b))
error("Could not open board!");
if (file_exists("$b/flags.ser"))
$config['user_flags'] = unserialize(file_get_contents("$b/flags.ser"));
$dir = 'static/custom-flags/'.$b;
if (!is_dir($dir)){
mkdir($dir, 0777, true);
}
function handle_file($id = false, $description, $b, $dir) {
global $config;
if (!isset($description) and $description)
error(_('You must enter a flag description!'));
if (strlen($description) > 255)
error(_('Flag description too long!'));
if ($id) {
$f = 'flag-'.$id;
} else {
$f = 'file';
$id = time() . substr(microtime(), 2, 3);
}
$upload = $_FILES[$f]['tmp_name'];
$banners = array_diff(scandir($dir), array('..', '.'));
if (!is_readable($upload))
error($config['error']['nomove']);
$extension = strtolower(mb_substr($_FILES[$f]['name'], mb_strrpos($_FILES[$f]['name'], '.') + 1));
if ($extension != 'png') {
error(_('Flags must be in PNG format.'));
}
if (filesize($upload) > 48000){
error(_('File too large!'));
}
if (!$size = @getimagesize($upload)) {
error($config['error']['invalidimg']);
}
if ($size[0] > 20 or $size[0] < 11 or $size[1] > 16 or $size[1] < 11){
error(_('Image wrong size!'));
}
if (sizeof($banners) > 256) {
error(_('Too many flags.'));
}
copy($upload, "$dir/$id.$extension");
purge("$dir/$id.$extension");
$config['user_flags'][$id] = utf8tohtml($description);
file_write($b.'/flags.ser', serialize($config['user_flags']));
}
// Handle a new flag, if any.
if (isset($_FILES['file'])){
handle_file(false, $_POST['description'], $b, $dir);
}
// Handle edits to existing flags.
foreach ($_FILES as $k => $a) {
if (empty($_FILES[$k]['tmp_name'])) continue;
if (preg_match('/^flag-(\d+)$/', $k, $matches)) {
$id = (int)$matches[1];
if (!isset($_POST['description-'.$id])) continue;
if (isset($config['user_flags'][$id])) {
handle_file($id, $_POST['description-'.$id], $b, $dir);
}
}
}
// Description just changed, flag not edited.
foreach ($_POST as $k => $v) {
if (!preg_match('/^description-(\d+)$/', $k, $matches)) continue;
$id = (int)$matches[1];
if (!isset($_POST['description-'.$id])) continue;
$description = $_POST['description-'.$id];
if (strlen($description) > 255)
error(_('Flag description too long!'));
$config['user_flags'][$id] = utf8tohtml($description);
file_write($b.'/flags.ser', serialize($config['user_flags']));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$flags = <<<FLAGS
<?php
\$config['country_flags'] = false;
\$config['country_flags_condensed'] = false;
\$config['user_flag'] = true;
\$config['uri_flags'] = '/static/custom-flags/$b/%s.png';
\$config['flag_style'] = '';
\$config['user_flags'] = unserialize(file_get_contents('$b/flags.ser'));
FLAGS;
if ($config['cache']['enabled']) {
cache::delete('config_' . $b);
cache::delete('events_' . $b);
}
file_write($b.'/flags.php', $flags);
}
if (isset($_POST['delete'])){
foreach ($_POST['delete'] as $i => $d){
if (!preg_match('/[0-9+]/', $d)){
error('Nice try.');
}
unlink("$dir/$d.png");
$id = explode('.', $d)[0];
unset($config['user_flags'][$id]);
file_write($b.'/flags.ser', serialize($config['user_flags']));
}
}
if (isset($_POST['alphabetize'])) {
asort($config['user_flags'], SORT_NATURAL | SORT_FLAG_CASE);
file_write($b.'/flags.ser', serialize($config['user_flags']));
}
$banners = array_diff(scandir($dir), array('..', '.'));
mod_page(_('Edit flags'), 'mod/flags.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri'])));
}
function mod_8_banners($b) {
global $config, $mod, $board;
require_once 'inc/image.php';
if (!hasPermission($config['mod']['edit_banners'], $b))
error($config['error']['noaccess']);
if (!openBoard($b))
error("Could not open board!");
$dir = 'static/banners/'.$b;
if (!is_dir($dir)){
mkdir($dir, 0777, true);
}
if (isset($_FILES['file'])){
$upload = $_FILES['file']['tmp_name'];
$banners = array_diff(scandir($dir), array('..', '.'));
if (!is_readable($upload))
error($config['error']['nomove']);
$id = time() . substr(microtime(), 2, 3);
$extension = strtolower(mb_substr($_FILES['file']['name'], mb_strrpos($_FILES['file']['name'], '.') + 1));
if (!in_array($extension, array('jpg','jpeg','png','gif'))){
error('Not an image extension.');
}
if (filesize($upload) > 512000){
error('File too large!');
}
if (!$size = @getimagesize($upload)) {
error($config['error']['invalidimg']);
}
if ($size[0] != 300 or $size[1] != 100){
error('Image wrong size!');
}
if (sizeof($banners) >= 50) {
error('Too many banners.');
}
copy($upload, "$dir/$id.$extension");
}
if (isset($_POST['delete'])){
foreach ($_POST['delete'] as $i => $d){
if (!preg_match('/[0-9+]\.(png|jpeg|jpg|gif)/', $d)){
error('Nice try.');
}
unlink("$dir/$d");
}
}
$banners = array_diff(scandir($dir), array('..', '.'));
mod_page(_('Edit banners'), 'mod/banners.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri'])));
}
function mod_8_settings($b) {
global $config, $mod;
//if ($b === 'infinity' && $mod['type'] !== ADMIN)
// error('Settings temporarily disabled for this board.');
if (!in_array($b, $mod['boards']) and $mod['boards'][0] != '*')
error($config['error']['noaccess']);
if (!hasPermission($config['mod']['edit_settings'], $b))
error($config['error']['noaccess']);
if (!openBoard($b))
error("Could not open board!");
$possible_languages = array_diff(scandir('inc/locale/'), array('..', '.', '.tx', 'README.md'));
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$title = $_POST['title'];
$subtitle = $_POST['subtitle'];
$country_flags = isset($_POST['country_flags']) ? 'true' : 'false';
$field_disable_name = isset($_POST['field_disable_name']) ? 'true' : 'false';
$enable_embedding = isset($_POST['enable_embedding']) ? 'true' : 'false';
$force_image_op = isset($_POST['force_image_op']) ? 'true' : 'false';
$disable_images = isset($_POST['disable_images']) ? 'true' : 'false';
$poster_ids = isset($_POST['poster_ids']) ? 'true' : 'false';
$show_sages = isset($_POST['show_sages']) ? 'true' : 'false';
$auto_unicode = isset($_POST['auto_unicode']) ? 'true' : 'false';
$strip_combining_chars = isset($_POST['strip_combining_chars']) ? 'true' : 'false';
$allow_roll = isset($_POST['allow_roll']) ? 'true' : 'false';
$image_reject_repost = isset($_POST['image_reject_repost']) ? 'true' : 'false';
$image_reject_repost_in_thread = isset($_POST['image_reject_repost_in_thread']) ? 'true' : 'false';
$early_404 = isset($_POST['early_404']) ? 'true' : 'false';
$allow_delete = isset($_POST['allow_delete']) ? 'true' : 'false';
$allow_flash = isset($_POST['allow_flash']) ? '$config[\'allowed_ext_files\'][] = \'swf\';' : '';
$allow_pdf = isset($_POST['allow_pdf']) ? '$config[\'allowed_ext_files\'][] = \'pdf\';' : '';
$code_tags = isset($_POST['code_tags']) ? '$config[\'additional_javascript\'][] = \'js/code_tags/run_prettify.js\';$config[\'markup\'][] = array("/\[code\](.+?)\[\/code\]/ms", "<code><pre class=\'prettyprint\' style=\'display:inline-block\'>\$1</pre></code>");' : '';
$katex = isset($_POST['katex']) ? '$config[\'katex\'] = true;$config[\'additional_javascript\'][] = \'js/katex/katex.min.js\'; $config[\'markup\'][] = array("/\[tex\](.+?)\[\/tex\]/ms", "<span class=\'tex\'>\$1</span>"); $config[\'additional_javascript\'][] = \'js/katex-enable.js\';' : '';
$user_flags = isset($_POST['user_flags']) ? "if (file_exists('$b/flags.php')) { include 'flags.php'; }\n" : '';
$captcha = isset($_POST['captcha']) ? 'true' : 'false';
$force_subject_op = isset($_POST['force_subject_op']) ? 'true' : 'false';
$force_flag = isset($_POST['force_flag']) ? 'true' : 'false';
$tor_posting = isset($_POST['tor_posting']) ? 'true' : 'false';
$new_thread_capt = isset($_POST['new_thread_capt']) ? 'true' : 'false';
$oekaki = isset($_POST['oekaki']) ? 'true' : 'false';
if ($_POST['locale'] !== 'en' && in_array($_POST['locale'], $possible_languages)) {
$locale = "\$config['locale'] = '{$_POST['locale']}.UTF-8';";
} else {
$locale = '';
}
if (isset($_POST['max_images']) && (int)$_POST['max_images'] && (int)$_POST['max_images'] <= 5) {
$_POST['max_images'] = (int)$_POST['max_images'];
$multiimage = "\$config['max_images'] = {$_POST['max_images']};
\$config['additional_javascript'][] = 'js/multi-image.js';";
} else {
$multiimage = '';
}
$anonymous = base64_encode($_POST['anonymous']);
$blotter = base64_encode(purify_html(html_entity_decode($_POST['blotter'])));
$add_to_config = @file_get_contents($b.'/extra_config.php');
$replace = '';
if (isset($_POST['replace'])) {
if (sizeof($_POST['replace']) > 200 || sizeof($_POST['with']) > 200) {
error(_('Sorry, max 200 wordfilters allowed.'));
}
if (count($_POST['replace']) == count($_POST['with'])) {
foreach ($_POST['replace'] as $i => $r ) {
if ($r !== '') {
$w = $_POST['with'][$i];
if (strlen($w) > 255) {
error(sprintf(_('Sorry, %s is too long. Max replacement is 255 characters'), utf8tohtml($w)));
}
$replace .= '$config[\'wordfilters\'][] = array(base64_decode(\'' . base64_encode($r) . '\'), base64_decode(\'' . base64_encode($w) . '\'));';
}
}
}
if (is_billion_laughs($_POST['replace'], $_POST['with'])) {
error(_('Wordfilters may not wordfilter previous wordfilters. For example, if a filters to bb and b filters to cc, that is not allowed.'));
}
}
if (isset($_POST['hour_max_threads']) && in_array($_POST['hour_max_threads'], ['10', '25', '50', '100'])) {
$hour_max_threads = $_POST['hour_max_threads'];
} else {
$hour_max_threads = 'false';
}
if (isset($_POST['max_pages'])) {
$mp = (int)$_POST['max_pages'];
if ($mp > 25 || $mp < 2) {
$max_pages = 15;
} else {
$max_pages = $mp;
}
} else {
$max_pages = 15;
}
if (isset($_POST['reply_limit'])) {
$rl = (int)$_POST['reply_limit'];
if ($rl > 750 || $rl < 250 || $rl % 25) {
$reply_limit = 250;
} else {
$reply_limit = $rl;
}
} else {
$reply_limit = 250;
}
if (isset($_POST['max_newlines'])) {
$mn = (int)$_POST['max_newlines'];
if ($mn < 20 || $mn > 300) {
$max_newlines = 0;
} else {
$max_newlines = $mn;
}
} else {
$max_newlines = $mn;
}
if (!(strlen($title) < 40))
error('Invalid title');
if (!(strlen($subtitle) < 200))
error('Invalid subtitle');
$query = prepare('UPDATE ``boards`` SET `title` = :title, `subtitle` = :subtitle, `indexed` = :indexed, `public_bans` = :public_bans, `public_logs` = :public_logs, `8archive` = :8archive WHERE `uri` = :uri');
$query->bindValue(':title', $title);
$query->bindValue(':subtitle', $subtitle);
$query->bindValue(':uri', $b);
$query->bindValue(':indexed', !isset($_POST['meta_noindex']));
$query->bindValue(':public_bans', isset($_POST['public_bans']));
$query->bindValue(':public_logs', (int)$_POST['public_logs']);
$query->bindValue(':8archive', isset($_POST['8archive']));
$query->execute() or error(db_error($query));
$config_file = <<<EOT
<?php
\$config['country_flags'] = $country_flags;
\$config['field_disable_name'] = $field_disable_name;
\$config['enable_embedding'] = $enable_embedding;
\$config['force_image_op'] = $force_image_op;
\$config['disable_images'] = $disable_images;
\$config['poster_ids'] = $poster_ids;
\$config['show_sages'] = $show_sages;
\$config['auto_unicode'] = $auto_unicode;
\$config['strip_combining_chars'] = $strip_combining_chars;
\$config['allow_roll'] = $allow_roll;
\$config['image_reject_repost'] = $image_reject_repost;
\$config['image_reject_repost_in_thread'] = $image_reject_repost_in_thread;
\$config['early_404'] = $early_404;
\$config['allow_delete'] = $allow_delete;
\$config['anonymous'] = base64_decode('$anonymous');
\$config['blotter'] = base64_decode('$blotter');
\$config['stylesheets']['Custom'] = 'board/$b.css';
\$config['default_stylesheet'] = array('Custom', \$config['stylesheets']['Custom']);
\$config['captcha']['enabled'] = $captcha;
\$config['force_subject_op'] = $force_subject_op;
\$config['force_flag'] = $force_flag;
\$config['tor_posting'] = $tor_posting;
\$config['new_thread_capt'] = $new_thread_capt;
\$config['hour_max_threads'] = $hour_max_threads;
\$config['reply_limit'] = $reply_limit;
\$config['max_pages'] = $max_pages;
\$config['max_newlines'] = $max_newlines;
\$config['oekaki'] = $oekaki;
$code_tags $katex $replace $multiimage $allow_flash $allow_pdf $user_flags
if (\$config['disable_images'])
\$config['max_pages'] = 10000;
$locale
$add_to_config
EOT;
// Clean up our CSS...no more expression() or off-site URLs.
$clean_css = preg_replace('/expression\s*\(/', '', $_POST['css']);
$matched = array();
preg_match_all("#{$config['link_regex']}#im", $clean_css, $matched);
if (isset($matched[0])) {
foreach ($matched[0] as $match) {
$match_okay = false;
foreach ($config['allowed_offsite_urls'] as $allowed_url) {
if (strpos($match, $allowed_url) !== false && strpos($match, '#') === false) {
$match_okay = true;
}
}
if ($match_okay !== true) {
error(sprintf(_("Off-site link \"%s\" is not allowed in the board stylesheet"), $match));
}
}
}
//Filter out imports from sites with potentially unsafe content
$match_imports = '@import[^;]*';
$matched = array();
preg_match_all("#$match_imports#im", $clean_css, $matched);
$unsafe_import_urls = array('https://a.pomf.se/');
if (isset($matched[0])) {
foreach ($matched[0] as $match) {
$match_okay = true;
foreach ($unsafe_import_urls as $unsafe_import_url) {
if (strpos($match, $unsafe_import_url) !== false && strpos($match, '#') === false) {
$match_okay = false;
}
}
if ($match_okay !== true) {
error(sprintf(_("Potentially unsafe import \"%s\" is not allowed in the board stylesheet"), $match));
}
}
}
$query = query('SELECT `uri`, `title`, `subtitle` FROM ``boards`` WHERE `8archive` = TRUE');
file_write('8archive.json', json_encode($query->fetchAll(PDO::FETCH_ASSOC)));
file_write($b.'/config.php', $config_file);
file_write('stylesheets/board/'.$b.'.css', $clean_css);
$_config = $config;
unset($config['wordfilters']);
// Faster than openBoard and bypasses cache...we're trusting the PHP output
// to be safe enough to run with every request, we can eval it here.
eval(str_replace('flags.php', "$b/flags.php", preg_replace('/^\<\?php$/m', '', $config_file)));
// czaks: maybe reconsider using it, now that config is cached?
// be smarter about rebuilds...only some changes really require us to rebuild all threads
if ($_config['captcha']['enabled'] != $config['captcha']['enabled']
|| $_config['new_thread_capt'] != $config['new_thread_capt'] /*New thread captcha - if toggling "enable captcha" requires this, toggling new thread capt does too, I guess.*/
|| $_config['captcha']['extra'] != $config['captcha']['extra']
|| $_config['blotter'] != $config['blotter']
|| $_config['field_disable_name'] != $config['field_disable_name']
|| $_config['show_sages'] != (isset($config['show_sages']) && $config['show_sages'])) {
buildIndex();
$query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL", $b)) or error(db_error());
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
buildThread($post['id']);
}
}
modLog('Edited board settings', $b);
}
$query = prepare('SELECT * FROM boards WHERE uri = :board');
$query->bindValue(':board', $b);
$query->execute() or error(db_error($query));
$board = $query->fetchAll()[0];
// Clean the cache
if ($config['cache']['enabled']) {
cache::delete('board_' . $board['uri']);
cache::delete('all_boards');
cache::delete('config_' . $board['uri']);
cache::delete('events_' . $board['uri']);
unlink('tmp/cache/locale_' . $board['uri']);
}
$css = @file_get_contents('stylesheets/board/' . $board['uri'] . '.css');
mod_page(_('Board configuration'), 'mod/settings.html', array('board'=>$board, 'css'=>prettify_textarea($css), 'token'=>make_secure_link_token('settings/'.$board['uri']), 'languages'=>$possible_languages,'allowed_urls'=>$config['allowed_offsite_urls']));
}

View File

@ -197,7 +197,7 @@ function _create_antibot($board, $thread) {
if (!isset($purged_old_antispam)) {
$purged_old_antispam = true;
query('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error());
//query('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error());
}
if ($thread)
@ -276,6 +276,8 @@ function checkSpam(array $extra_salt = array()) {
}
function incrementSpamHash($hash) {
if ($hash === true) return;
$query = prepare('UPDATE ``antispam`` SET `passed` = `passed` + 1 WHERE `hash` = :hash');
$query->bindValue(':hash', $hash);
$query->execute() or error(db_error($query));

View File

@ -32,7 +32,9 @@ class Api {
'images' => 'images',
'sticky' => 'sticky',
'locked' => 'locked',
'cycle' => 'cyclical',
'bump' => 'last_modified',
'embed' => 'embed',
);
$this->threadsPageFields = array(
@ -85,11 +87,23 @@ class Api {
}
}
private function translateFile($file, &$apiPost) {
$this->translateFields($this->fileFields, $file, $apiPost);
$apiPost['filename'] = @substr($file->name, 0, strrpos($file->name, '.'));
$dotPos = strrpos($file->file, '.');
$apiPost['ext'] = substr($file->file, $dotPos);
$apiPost['tim'] = substr($file->file, 0, $dotPos);
if (isset($file->hash))
$apiPost['md5'] = base64_encode(hex2bin($file->hash));
}
private function translatePost($post, $threadsPage = false) {
global $config, $board;
$apiPost = array();
$fields = $threadsPage ? $this->threadsPageFields : $this->postFields;
$this->translateFields($fields, $post, $apiPost);
if ($this->config['poster_ids']) $apiPost['id'] = poster_id($post->ip, $post->thread, $board['uri']);
if ($threadsPage) return $apiPost;
// Handle country field
@ -105,15 +119,21 @@ class Api {
}
// Handle files
// Note: 4chan only supports one file, so only the first file is taken into account for 4chan-compatible API.
if (isset($post->files) && $post->files && !$threadsPage) {
$file = $post->files[0];
$this->translateFields($this->fileFields, $file, $apiPost);
$apiPost['filename'] = @substr($file->name, 0, strrpos($file->name, '.'));
$dotPos = strrpos($file->file, '.');
$apiPost['ext'] = substr($file->file, $dotPos);
$apiPost['tim'] = substr($file->file, 0, $dotPos);
$apiPost['md5'] = base64_encode(hex2bin($post->filehash));
$this->translateFile($file, $apiPost);
if (sizeof($post->files) > 1) {
$extra_files = array();
foreach ($post->files as $i => $f) {
if ($i == 0) continue;
$extra_file = array();
$this->translateFile($f, $extra_file);
$extra_files[] = $extra_file;
}
$apiPost['extra_files'] = $extra_files;
}
}
return $apiPost;

View File

@ -156,17 +156,18 @@ class Bans {
static public function stream_json($out = false, $filter_ips = false, $filter_staff = false, $board_access = false) {
global $config, $pdo;
if ($board_access && $board_access[0] == '*') $board_access = false;
$query_addition = "";
if ($board_access !== FALSE) {
$query_addition .= "WHERE `public_bans` OR `public_bans` IS NULL";
}
if ($board_access) {
$boards = implode(", ", array_map(array($pdo, "quote"), $board_access));
$query_addition .= " OR `board` IN (".$boards.")";
$query_addition .= "WHERE `board` IN (".$boards.")";
}
if ($board_access !== FALSE) {
if (!$query_addition) {
$query_addition .= " WHERE (`public_bans` IS TRUE) OR ``bans``.`board` IS NULL";
}
}
$query = query("SELECT ``bans``.*, `username`, `type` FROM ``bans``
@ -202,11 +203,14 @@ class Bans {
case ADMIN:
$ban['username'] = 'Admin';
break;
case SUPERMOD:
case GLOBALVOLUNTEER:
$ban['username'] = 'Global Volunteer';
break;
case MOD:
$ban['username'] = 'Local Volunteer';
$ban['username'] = 'Board Owner';
break;
case BOARDVOLUNTEER:
$ban['username'] = 'Board Volunteer';
break;
default:
$ban['username'] = '?';
@ -215,14 +219,8 @@ class Bans {
}
unset($ban['type']);
if ($filter_ips || ($board_access !== false && !in_array($ban['board'], $board_access))) {
@list($ban['mask'], $subnet) = explode("/", $ban['mask']);
$ban['mask'] = preg_split("/[\.:]/", $ban['mask']);
$ban['mask'] = array_slice($ban['mask'], 0, 2);
$ban['mask'] = implode(".", $ban['mask']);
$ban['mask'] .= ".x.x";
if (isset ($subnet)) {
$ban['mask'] .= "/$subnet";
}
$ban['mask'] = @less_ip($ban['mask'], $ban['board']);
$ban['masked'] = true;
}
@ -239,13 +237,15 @@ class Bans {
}
static public function seen($ban_id) {
global $config;
$query = query("UPDATE ``bans`` SET `seen` = 1 WHERE `id` = " . (int)$ban_id) or error(db_error());
rebuildThemes('bans');
if (!$config['cron_bans']) rebuildThemes('bans');
}
static public function purge() {
global $config;
$query = query("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < " . time() . " AND `seen` = 1") or error(db_error());
rebuildThemes('bans');
if (!$config['cron_bans']) rebuildThemes('bans');
}
static public function delete($ban_id, $modlog = false, $boards = false, $dont_rebuild = false) {
@ -260,8 +260,11 @@ class Bans {
return false;
}
if ($boards !== false && !in_array($ban['board'], $boards))
if ($boards !== false && !in_array($ban['board'], $boards))
error($config['error']['noaccess']);
if ($ban['board'])
openBoard($ban['board']);
$mask = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
@ -271,7 +274,7 @@ class Bans {
query("DELETE FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error());
if (!$dont_rebuild) rebuildThemes('bans');
if (!$dont_rebuild || !$config['cron_bans']) rebuildThemes('bans');
return true;
}
@ -325,6 +328,17 @@ class Bans {
if ($post) {
$post['board'] = $board['uri'];
$match_urls = '(?xi)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))';
$matched = array();
preg_match_all("#$match_urls#im", $post['body_nomarkup'], $matched);
if (isset($matched[0]) && $matched[0]) {
$post['body'] = str_replace($matched[0], '###Link-Removed###', $post['body']);
$post['body_nomarkup'] = str_replace($matched[0], '###Link-Removed###', $post['body_nomarkup']);
}
$query->bindValue(':post', json_encode($post));
} else
$query->bindValue(':post', null, PDO::PARAM_NULL);
@ -342,7 +356,7 @@ class Bans {
' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason'));
}
rebuildThemes('bans');
if (!$config['cron_bans']) rebuildThemes('bans');
return $pdo->lastInsertId();
}

View File

@ -50,6 +50,17 @@ class Cache {
case 'php':
$data = isset(self::$cache[$key]) ? self::$cache[$key] : false;
break;
case 'fs':
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
if (!file_exists('tmp/cache/'.$key)) {
$data = false;
}
else {
$data = file_get_contents('tmp/cache/'.$key);
$data = json_decode($data, true);
}
break;
case 'redis':
if (!self::$cache)
self::init();
@ -87,6 +98,11 @@ class Cache {
case 'xcache':
xcache_set($key, $value, $expires);
break;
case 'fs':
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
file_put_contents('tmp/cache/'.$key, json_encode($value));
break;
case 'php':
self::$cache[$key] = $value;
break;
@ -113,6 +129,11 @@ class Cache {
case 'xcache':
xcache_unset($key);
break;
case 'fs':
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
@unlink('tmp/cache/'.$key);
break;
case 'php':
unset(self::$cache[$key]);
break;
@ -134,6 +155,12 @@ class Cache {
case 'php':
self::$cache = array();
break;
case 'fs':
$files = glob('tmp/cache/*');
foreach ($files as $file) {
unlink($file);
}
break;
case 'redis':
if (!self::$cache)
self::init();

View File

@ -36,11 +36,6 @@
// $config['global_message'] = 'This is an important announcement!';
$config['blotter'] = &$config['global_message'];
// Automatically check if a newer version of Tinyboard is available when an administrator logs in.
$config['check_updates'] = true;
// How often to check for updates
$config['check_updates_time'] = 43200; // 12 hours
// Shows some extra information at the bottom of pages. Good for development/debugging.
$config['debug'] = false;
// For development purposes. Displays (and "dies" on) all errors and warnings. Turn on with the above.
@ -137,6 +132,11 @@
// Tinyboard to use.
$config['cache']['redis'] = array('localhost', 6379, '', 1);
// EXPERIMENTAL: Should we cache configs? Warning: this changes board behaviour, i'd say, a lot.
// If you have any lambdas/includes present in your config, you should move them to instance-functions.php
// (this file will be explicitly loaded during cache hit, but not during cache miss).
$config['cache_config'] = false;
/*
* ====================
* Cookie settings
@ -184,7 +184,7 @@
// Prevents most Tor exit nodes from making posts. Recommended, as a lot of abuse comes from Tor because
// of the strong anonymity associated with it.
$config['dnsbl'][] = array('tor.dnsbl.sectoor.de', 1);
$config['dnsbl'][] = array('exitnodes.tor.dnsbl.sectoor.de', 1);
// http://www.sorbs.net/using.shtml
// $config['dnsbl'][] = array('dnsbl.sorbs.net', array(2, 3, 4, 5, 6, 7, 8, 9));
@ -272,6 +272,8 @@
'embed',
'recaptcha_challenge_field',
'recaptcha_response_field',
'captcha_cookie',
'captcha_text',
'spoiler',
'page',
'file_url',
@ -300,6 +302,24 @@
$config['recaptcha_public'] = '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f';
$config['recaptcha_private'] = '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_';
$config['captcha'] = array();
// Enable custom captcha provider
$config['captcha']['enabled'] = false;
/*
* Custom captcha provider path (You will need to change these depending on your configuration! It cannot be
* automatically determined because provider_check requires curl which needs to know the domain of your site.)
*
* Specify yourimageboard.com/$config['root']/8chan-captcha/entrypoint.php for the default provider or write your own
*/
$config['captcha']['provider_get'] = 'http://localhost/infinity/8chan-captcha/entrypoint.php';
$config['captcha']['provider_check'] = 'http://localhost/infinity/8chan-captcha/entrypoint.php';
// Custom captcha extra field (eg. charset)
$config['captcha']['extra'] = 'abcdefghijklmnopqrstuvwxyz';
/*
* Custom filters detect certain posts and reject/ban accordingly. They are made up of a condition and an
* action (for when ALL conditions are met). As every single post has to be put through each filter,
@ -435,12 +455,21 @@
* ====================
*/
//New thread captcha
//Require solving a captcha to post a thread.
//Default off.
$config['new_thread_capt'] = false;
// Do you need a body for your reply posts?
$config['force_body'] = false;
// Do you need a user or country flag for your posts?
$config['force_flag'] = false;
// Do you need a body for new threads?
$config['force_body_op'] = true;
// Require an image for threads?
$config['force_image_op'] = true;
// Require a subject for threads?
$config['force_subject_op'] = false;
// Strip superfluous new lines at the end of a post.
$config['strip_superfluous_returns'] = true;
@ -449,6 +478,8 @@
// Maximum post body length.
$config['max_body'] = 1800;
// Maximum number of newlines. (0 for unlimited)
$config['max_newlines'] = 0;
// Maximum number of post body lines to show on the index page.
$config['body_truncate'] = 15;
// Maximum number of characters to show on the index page.
@ -539,12 +570,15 @@
// When true, there will be no subject field.
$config['field_disable_subject'] = false;
// When true, there will be no subject field for replies.
$config['field_disable_reply_subject'] = false;
$config['field_disable_reply_subject'] = &$config['field_disable_name'];
// When true, a blank password will be used for files (not usable for deletion).
$config['field_disable_password'] = false;
// When true, users are instead presented a selectbox for email. Contains, blank, noko and sage.
$config['field_email_selectbox'] = false;
$config['field_email_selectbox'] = &$config['field_disable_name'];
// Prevent users from uploading files.
$config['disable_images'] = false;
// When true, the sage won't be displayed
$config['hide_sage'] = false;
@ -602,6 +636,17 @@
// How many ban appeals can be made for a single ban?
$config['ban_appeals_max'] = 1;
// Blacklisted board names. Default values to protect existing folders in the core codebase.
$config['banned_boards'] = array(
'.git',
'inc',
'js',
'static',
'stylesheets',
'templates',
'tools'
);
// Show moderator name on ban page.
$config['show_modname'] = false;
@ -612,6 +657,30 @@
* ====================
*/
// JIS ASCII art. This *must* be the first markup or it won't work.
$config['markup'][] = array(
"/\[(aa|code)\](.+?)\[\/(?:aa|code)\]/ms",
function($matches) {
$markupchars = array('_', '\'', '~', '*', '=');
$replacement = $markupchars;
array_walk($replacement, function(&$v) {
$v = "&#".ord($v).";";
});
// These are hacky fixes for ###board-tags### and >quotes.
$markupchars[] = '###';
$replacement[] = '&#35;&#35;&#35;';
$markupchars[] = '&gt;';
$replacement[] = '&#62;';
if ($matches[1] === 'aa') {
return '<span class="aa">' . str_replace($markupchars, $replacement, $matches[2]) . '</span>';
} else {
return str_replace($markupchars, $replacement, $matches[0]);
}
}
);
// "Wiki" markup syntax ($config['wiki_markup'] in pervious versions):
$config['markup'][] = array("/'''(.+?)'''/", "<strong>\$1</strong>");
$config['markup'][] = array("/''(.+?)''/", "<em>\$1</em>");
@ -727,7 +796,6 @@
// Allowed image file extensions.
$config['allowed_ext'][] = 'jpg';
$config['allowed_ext'][] = 'jpeg';
$config['allowed_ext'][] = 'bmp';
$config['allowed_ext'][] = 'gif';
$config['allowed_ext'][] = 'png';
// $config['allowed_ext'][] = 'svg';
@ -745,6 +813,7 @@
$config['file_icons']['default'] = 'file.png';
$config['file_icons']['zip'] = 'zip.png';
$config['file_icons']['webm'] = 'video.png';
$config['file_icons']['mp4'] = 'video.png';
// Example: Custom thumbnail for certain file extension.
// $config['file_icons']['extension'] = 'some_file.png';
@ -754,6 +823,8 @@
$config['spoiler_image'] = 'static/spoiler.png';
// Location of thumbnail to use for deleted images.
// $config['image_deleted'] = 'static/deleted.png';
// Location of placeholder image for fileless posts in catalog.
$config['no_file_image'] = 'static/no-file.png';
// When a thumbnailed image is going to be the same (in dimension), just copy the entire file and use
// that as a thumbnail instead of resizing/redrawing.
@ -858,7 +929,7 @@
$config['thread_subject_in_title'] = false;
// Additional lines added to the footer of all pages.
$config['footer'][] = _('All trademarks, copyrights, comments, and images on this page are owned by and are the responsibility of their respective parties.');
// $config['footer'][] = _('All trademarks, copyrights, comments, and images on this page are owned by and are the responsibility of their respective parties.');
// Characters used to generate a random password (with Javascript).
$config['genpassword_chars'] = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+';
@ -954,7 +1025,8 @@
// Width and height (and more?) of post flags. Can be overridden with the Tinyboard post modifier:
// <tinyboard flag style>.
$config['flag_style'] = 'width:16px;height:11px;';
// $config['flag_style'] = 'width:16px;height:11px;';
$config['flag_style'] = '';
/*
* ====================
@ -963,7 +1035,7 @@
*/
// Additional Javascript files to include on board index and thread pages. See js/ for available scripts.
$config['additional_javascript'][] = 'js/inline-expanding.js';
// $config['additional_javascript'][] = 'js/inline-expanding.js';
// $config['additional_javascript'][] = 'js/local-time.js';
// Some scripts require jQuery. Check the comments in script files to see what's needed. When enabling
@ -995,33 +1067,20 @@
// Enable embedding (see below).
$config['enable_embedding'] = false;
// Youtube.js embed HTML code
$config['youtube_js_html'] = '<div class="video-container" data-video="$1" data-params="&$2&$3">'.
'<span class="unimportant yt-help">YouTube embed. Click thumbnail to play.</span><br>'.
'<a href="$0" target="_blank" class="file">'.
'<img style="width:255px" src="//img.youtube.com/vi/$1/0.jpg" class="post-image"/>'.
'</a></div>';
// Custom embedding (YouTube, vimeo, etc.)
// It's very important that you match the entire input (with ^ and $) or things will not work correctly.
$config['embedding'] = array(
array(
'/^https?:\/\/(\w+\.)?youtube\.com\/watch\?v=([a-zA-Z0-9\-_]{10,11})(&.+)?$/i',
'<iframe style="float: left;margin: 10px 20px;" width="%%tb_width%%" height="%%tb_height%%" frameborder="0" id="ytplayer" src="http://www.youtube.com/embed/$2"></iframe>'
'/^https?:\/\/(?:\w+\.)?(?:youtube\.com\/watch\?|youtu\.be\/)(?:(?:&?v=)?([a-zA-Z0-9\-_]{10,11}))$/i',
$config['youtube_js_html']
),
array(
'/^https?:\/\/(\w+\.)?vimeo\.com\/(\d{2,10})(\?.+)?$/i',
'<object style="float: left;margin: 10px 20px;" width="%%tb_width%%" height="%%tb_height%%"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=$2&amp;server=vimeo.com&amp;show_title=0&amp;show_byline=0&amp;show_portrait=0&amp;color=00adef&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" /><embed src="http://vimeo.com/moogaloop.swf?clip_id=$2&amp;server=vimeo.com&amp;show_title=0&amp;show_byline=0&amp;show_portrait=0&amp;color=00adef&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="%%tb_width%%" height="%%tb_height%%"></object>'
),
array(
'/^https?:\/\/(\w+\.)?dailymotion\.com\/video\/([a-zA-Z0-9]{2,10})(_.+)?$/i',
'<object style="float: left;margin: 10px 20px;" width="%%tb_width%%" height="%%tb_height%%"><param name="movie" value="http://www.dailymotion.com/swf/video/$2"><param name="allowFullScreen" value="true"><param name="allowScriptAccess" value="always"><param name="wmode" value="transparent"><embed type="application/x-shockwave-flash" src="http://www.dailymotion.com/swf/video/$2" width="%%tb_width%%" height="%%tb_height%%" wmode="transparent" allowfullscreen="true" allowscriptaccess="always"></object>'
),
array(
'/^https?:\/\/(\w+\.)?metacafe\.com\/watch\/(\d+)\/([a-zA-Z0-9_\-.]+)\/(\?.+)?$/i',
'<div style="float:left;margin:10px 20px;width:%%tb_width%%px;height:%%tb_height%%px"><embed flashVars="playerVars=showStats=no|autoPlay=no" src="http://www.metacafe.com/fplayer/$2/$3.swf" width="%%tb_width%%" height="%%tb_height%%" wmode="transparent" allowFullScreen="true" allowScriptAccess="always" name="Metacafe_$2" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"></div>'
),
array(
'/^https?:\/\/video\.google\.com\/videoplay\?docid=(\d+)([&#](.+)?)?$/i',
'<embed src="http://video.google.com/googleplayer.swf?docid=$1&hl=en&fs=true" style="width:%%tb_width%%px;height:%%tb_height%%px;float:left;margin:10px 20px" allowFullScreen="true" allowScriptAccess="always" type="application/x-shockwave-flash"></embed>'
),
array(
'/^https?:\/\/(\w+\.)?vocaroo\.com\/i\/([a-zA-Z0-9]{2,15})$/i',
'<object style="float: left;margin: 10px 20px;" width="148" height="44"><param name="movie" value="http://vocaroo.com/player.swf?playMediaID=$2&autoplay=0"><param name="wmode" value="transparent"><embed src="http://vocaroo.com/player.swf?playMediaID=$2&autoplay=0" width="148" height="44" wmode="transparent" type="application/x-shockwave-flash"></object>'
)
);
// Embedding width and height.
@ -1051,12 +1110,13 @@
$config['error']['image_hard_limit'] = _('Thread has reached its maximum image limit.');
$config['error']['nopost'] = _('You didn\'t make a post.');
$config['error']['flood'] = _('Flood detected; Post discarded.');
$config['error']['spam'] = _('Your request looks automated; Post discarded.');
$config['error']['spam'] = _('Your request looks automated; Post discarded. Try refreshing the page. If that doesn\'t work, please post the board, thread and browser this error occurred on on /operate/.');
$config['error']['unoriginal'] = _('Unoriginal content!');
$config['error']['muted'] = _('Unoriginal content! You have been muted for %d seconds.');
$config['error']['youaremuted'] = _('You are muted! Expires in %d seconds.');
$config['error']['dnsbl'] = _('Your IP address is listed in %s.');
$config['error']['toomanylinks'] = _('Too many links; flood detected.');
$config['error']['notenoughlinks'] = _('OPs are required to have at least %d links on this board.');
$config['error']['toomanycites'] = _('Too many cites; post discarded.');
$config['error']['toomanycross'] = _('Too many cross-board links; post discarded.');
$config['error']['nodelete'] = _('You didn\'t select anything to delete.');
@ -1071,16 +1131,16 @@
$config['error']['webmerror'] = _('There was a problem processing your webm.');//Is this error used anywhere ?
$config['error']['invalidwebm'] = _('Invalid webm uploaded.');
$config['error']['webmhasaudio'] = _('The uploaded webm contains an audio or another type of additional stream.');
$config['error']['webmtoolong'] = _('The uploaded webm is longer than ' . $config['webm']['max_length'] . ' seconds.');
$config['error']['webmtoolong'] = _('The uploaded webm is longer than %d seconds.');
$config['error']['fileexists'] = _('That file <a href="%s">already exists</a>!');
$config['error']['fileexistsinthread'] = _('That file <a href="%s">already exists</a> in this thread!');
$config['error']['delete_too_soon'] = _('You\'ll have to wait another %s before deleting that.');
$config['error']['mime_exploit'] = _('MIME type detection XSS exploit (IE) detected; post discarded.');
$config['error']['invalid_embed'] = _('Couldn\'t make sense of the URL of the video you tried to embed.');
$config['error']['captcha'] = _('You seem to have mistyped the verification.');
$config['error']['images_disabled'] = _('Uploading files is disabled on this board.');
// Moderator errors
// mod.php errors
$config['error']['toomanyunban'] = _('You are only allowed to unban %s users at a time. You tried to unban %u users.');
$config['error']['invalid'] = _('Invalid username and/or password.');
$config['error']['notamod'] = _('You are not a mod…');
@ -1182,11 +1242,22 @@
// $config['url_javascript'] = 'http://static.example.org/main.js';
// Website favicon.
// $config['url_favicon'] = '/favicon.gif';
$config['url_favicon'] = 'static/favicon.ico';
// EXPERIMENTAL: Try not to build pages when we shouldn't have to.
// Try not to build pages when we shouldn't have to.
$config['try_smarter'] = true;
// EXPERIMENTAL: Defer static HTML building to a moment, when a given file is actually accessed.
// Warning: This option won't run out of the box. You need to tell your webserver, that a file
// for serving 403 and 404 pages is /smart_build.php. Also, you need to turn off indexes.
$config['smart_build'] = false;
// Smart build related: when a file doesn't exist, where should we redirect?
$config['page_404'] = '/404.html';
// Smart build related: extra entrypoints.
$config['smart_build_entrypoints'] = array();
/*
* ====================
* Mod settings
@ -1208,6 +1279,7 @@
$config['mod']['link_bandelete'] = '[B&amp;D]';
$config['mod']['link_deletefile'] = '[F]';
$config['mod']['link_spoilerimage'] = '[S]';
$config['mod']['link_spoilerimages'] = '[S+]';
$config['mod']['link_deletebyip'] = '[D+]';
$config['mod']['link_deletebyip_global'] = '[D++]';
$config['mod']['link_sticky'] = '[Sticky]';
@ -1218,6 +1290,8 @@
$config['mod']['link_bumpunlock'] = '[-Sage]';
$config['mod']['link_editpost'] = '[Edit]';
$config['mod']['link_move'] = '[Move]';
$config['mod']['link_cycle'] = '[Cycle]';
$config['mod']['link_uncycle'] = '[-Cycle]';
// Moderator capcodes.
$config['capcode'] = ' <span class="capcode">## %s</span>';
@ -1326,8 +1400,8 @@
// Capcode permissions.
$config['mod']['capcode'] = array(
// JANITOR => array('Janitor'),
MOD => array('Mod'),
ADMIN => true
MOD => array('Mod'),
ADMIN => true
);
// Example: Allow mods to post with "## Moderator" as well
@ -1361,6 +1435,9 @@
$config['mod']['deletebyip_global'] = ADMIN;
// Sticky a thread
$config['mod']['sticky'] = MOD;
// Cycle a thread
$config['mod']['cycle'] = MOD;
$config['cycle_limit'] = &$config['reply_limit'];
// Lock a thread
$config['mod']['lock'] = MOD;
// Post in a locked thread
@ -1410,7 +1487,7 @@
$config['mod']['view_banlist'] = MOD;
// View the username of the mod who made a ban
$config['mod']['view_banstaff'] = MOD;
// If the moderator doesn't fit the $config['mod']['view_banstaff''] (previous) permission, show him just
// If the moderator doesn't fit the $config['mod']['view_banstaff'] (previous) permission, show him just
// a "?" instead. Otherwise, it will be "Mod" or "Admin".
$config['mod']['view_banquestionmark'] = false;
// Show expired bans in the ban list (they are kept in cache until the culprit returns)
@ -1487,6 +1564,9 @@
$config['mod']['ban_appeals'] = MOD;
// View the recent posts page
$config['mod']['recent'] = MOD;
// Create pages
$config['mod']['edit_pages'] = MOD;
$config['pages_max'] = 10;
// Config editor permissions
$config['mod']['config'] = array();
@ -1654,10 +1734,29 @@
// Regex for board URIs. Don't add "`" character or any Unicode that MySQL can't handle. 58 characters
// is the absolute maximum, because MySQL cannot handle table names greater than 64 characters.
$config['board_regex'] = '[0-9a-zA-Z$_\x{0080}-\x{FFFF}]{1,58}';
$config['board_regex'] = '[0-9a-zA-Z\+$_\x{0080}-\x{FFFF}]{1,58}';
// Youtube.js embed HTML code
$config['youtube_js_html'] = '<div class="video-container" data-video="$2">'.
'<a href="$0" target="_blank" class="file">'.
'<img style="width:360px;height:270px;" src="//img.youtube.com/vi/$2/0.jpg" class="post-image"/>'.
'</a></div>';
// Regex for matching links.
$config['link_regex'] = '((?:(?:https?:)?\/\/|ftp:\/\/|irc:\/\/)[^\s<>()"]+?(?:\([^\s<>()"]*?\)[^\s<>()"]*?)*)((?:\s|<|>|"|\.|\]|!|\?|,|&\#44;|&quot;)*(?:[\s<>()"]|$))';
// Allowed URLs in ?/settings
$config['allowed_offsite_urls'] = array('https://i.imgur.com/', 'https://media.8ch.net/', 'https://media.8chan.co/', 'https://a.pomf.se/', 'https://fonts.googleapis.com/', 'https://fonts.gstatic.com/');
// Use read.php?
// read.php is a file that dynamically displays pages to users instead of the build on demand system in use in Tinyboard since 2010.
//
// read.php is basically a watered down mod.php -- if coupled with caching, it improves performance and allows for easier replication
// across machines.
$config['use_read_php'] = false;
// Use oekaki?
$config['oekaki'] = false;
// Twig cache?
$config['twig_cache'] = false;
// Use CAPTCHA for reports?
$config['report_captcha'] = false;
// Allowed HTML tags in ?/edit_pages.
$config['allowed_html'] = 'a[href|title],p,br,li,ol,ul,strong,em,u,h2,b,i,tt,div,img[src|alt|title],hr';

View File

@ -91,7 +91,11 @@ function sql_open() {
$message = str_replace($config['db']['password'], '<em>hidden</em>', $message);
// Print error
error(_('Database error: ') . $message);
if ($config['mask_db_error']) {
error(_('Could not connect to the database. Please try again later.'));
} else {
error(_('Database error: ') . $message);
}
}
}
@ -149,7 +153,11 @@ function query($query) {
}
function db_error($PDOStatement = null) {
global $pdo, $db_error;
global $pdo, $db_error, $config;
if ($config['mask_db_error']) {
return _('The database returned an error while processing your request. Please try again later.');
}
if (isset($PDOStatement)) {
$db_error = $PDOStatement->errorInfo();

View File

@ -73,6 +73,9 @@ function createBoardlist($mod=false) {
function error($message, $priority = true, $debug_stuff = false) {
global $board, $mod, $config, $db_error;
if (isset($debug_stuff['file']))
$message .= " {$debug_stuff['file']}";
if ($config['syslog'] && $priority !== false) {
// Use LOG_NOTICE instead of LOG_ERR or LOG_WARNING because most error message are not significant.
@ -356,8 +359,20 @@ class Post {
$this->{$key} = $value;
}
if (isset($this->files) && $this->files)
$this->files = json_decode($this->files);
if (isset($this->files) && $this->files) {
$this->files = is_string($this->files) ? json_decode($this->files) : $this->files;
// Compatibility for posts before individual file hashing
if ($this->files) {
foreach ($this->files as $i => &$file) {
if (empty($file)) {
unset($this->files[$i]);
continue;
}
if (!isset($file->hash))
$file->hash = $this->filehash;
}
}
}
$this->subject = utf8tohtml($this->subject);
$this->name = utf8tohtml($this->name);
@ -403,9 +418,13 @@ class Post {
}
public function getClean( ) {
global $board;
global $board, $config;
if( !isset( $this->clean ) ) {
if ($config['cache']['enabled'] && $this->clean = cache::get("post_clean_{$board['uri']}_{$this->id}")) {
return $this->clean;
}
$query = prepare("SELECT * FROM `post_clean` WHERE `post_id` = :post AND `board_id` = :board");
$query->bindValue( ':board', $board['uri'] );
$query->bindValue( ':post', $this->id );
@ -421,6 +440,8 @@ class Post {
'clean_local_mod_id' => null,
'clean_global_mod_id' => null,
);
if ($config['cache']['enabled'])
cache::set("post_clean_{$board['uri']}_{$this->id}", $this->clean);
}
}
@ -439,7 +460,7 @@ class Thread extends Post {
}
if (isset($this->files))
$this->files = json_decode($this->files);
$this->files = is_string($this->files) ? json_decode($this->files) : $this->files;
$this->subject = utf8tohtml($this->subject);
$this->name = utf8tohtml($this->name);

File diff suppressed because it is too large Load Diff

View File

@ -363,9 +363,10 @@ class ImageConvert extends ImageBase {
$this->height,
escapeshellarg($this->temp)))) || !file_exists($this->temp)) {
if (strpos($error, "known incorrect sRGB profile") === false) {
if (strpos($error, "known incorrect sRGB profile") === false &&
strpos($error, "iCCP: Not recognizing known sRGB profile that has been edited") === false) {
$this->destroy();
error('Failed to resize image!', null, array('convert_error' => $error));
error(_('Failed to resize image!')." "._('Details: ').nl2br(htmlspecialchars($error)), null, array('convert_error' => $error));
}
if (!file_exists($this->temp)) {
$this->destroy();

View File

@ -7,9 +7,6 @@
*
* You can copy values from config.php (defaults) and paste them here.
*/
require_once "lib/htmlpurifier-4.5.0/library/HTMLPurifier.auto.php";
require_once "8chan-functions.php";
// Note - you may want to change some of these in secrets.php instead of here
// See the secrets.example.php file
$config['db']['server'] = 'localhost';
@ -47,50 +44,34 @@
$config['spam']['hidden_inputs_max_pass'] = 128;
$config['ayah_enabled'] = true;
// Load database credentials
require "secrets.php";
// Image shit
$config['thumb_method'] = 'gm+gifsicle';
$config['thumb_ext'] = '';
$config['thumb_keep_animation_frames'] = 100;
$config['thumb_method'] = 'convert';
$config['thumb_ext'] = 'jpg';
$config['thumb_keep_animation_frames'] = 1;
$config['show_ratio'] = true;
//$config['allow_upload_by_url'] = true;
$config['max_filesize'] = 1024 * 1024 * 8; // 8MB
$config['disable_images'] = false;
$config['spoiler_images'] = true;
$config['image_reject_repost'] = true;
$config['allowed_ext_files'][] = 'webm';
$config['allowed_ext_files'][] = 'mp4';
$config['webm']['use_ffmpeg'] = true;
$config['webm']['allow_audio'] = true;
$config['webm']['max_length'] = 60 * 15;
$config['webm']['max_length'] = 60 * 30;
// Mod shit
$config['mod']['groups'][25] = 'Supermod';
$config['mod']['groups'][25] = 'GlobalVolunteer';
$config['mod']['groups'][19] = 'BoardVolunteer';
define_groups();
$config['mod']['capcode'][MOD] = array('Board Volunteer');
$config['mod']['capcode'][SUPERMOD] = array('Global Volunteer');
$config['mod']['capcode'][BOARDVOLUNTEER] = array('Board Volunteer');
$config['mod']['capcode'][MOD] = array('Board Owner');
$config['mod']['capcode'][GLOBALVOLUNTEER] = array('Global Volunteer');
$config['mod']['capcode'][ADMIN] = array('Admin', 'Global Volunteer');
$config['custom_capcode']['Admin'] = array(
'<span class="capcode" style="color:blue;font-weight:bold"> <i class="fa fa-wheelchair"></i> %s</span>',
'<span class="capcode" title="This post was written by the global 8chan administrator."> <i class="fa fa-wheelchair" style="color:blue;"></i> <span style="color:red">8chan Administrator</span></span>',
);
$config['custom_capcode']['Bear'] = array(
'<span class="capcode" style="color:brown;font-weight:bold"> <img src="/static/paw.svg" height="12" width="12"> %s</span>',
);
//$config['mod']['view_banlist'] = SUPERMOD;
$config['mod']['show_ip'] = SUPERMOD;
$config['mod']['show_ip_less'] = MOD;
$config['mod']['manageusers'] = SUPERMOD;
$config['mod']['noticeboard_post'] = SUPERMOD;
$config['mod']['search'] = SUPERMOD;
$config['mod']['clean_global'] = SUPERMOD;
$config['mod']['debug_recent'] = ADMIN;
$config['mod']['debug_antispam'] = ADMIN;
$config['mod']['modlog'] = SUPERMOD;
$config['mod']['editpost'] = MOD;
$config['mod']['edit_banners'] = MOD;
$config['mod']['edit_flags'] = MOD;
$config['mod']['edit_settings'] = MOD;
$config['mod']['clean'] = MOD;
//$config['mod']['view_banlist'] = GLOBALVOLUNTEER;
$config['mod']['recent_reports'] = 65535;
$config['mod']['ip_less_recentposts'] = 75;
$config['ban_show_post'] = true;
@ -105,7 +86,9 @@
//$config['default_stylesheet'] = array('Notsuba', 'notsuba.css');
$config['additional_javascript'][] = 'js/jquery.min.js';
$config['additional_javascript'][] = 'js/jquery.mixitup.min.js';
$config['additional_javascript'][] = 'js/jquery-ui.custom.min.js';
$config['additional_javascript'][] = 'js/catalog.js';
$config['additional_javascript'][] = 'js/captcha.js';
$config['additional_javascript'][] = 'js/jquery.tablesorter.min.js';
$config['additional_javascript'][] = 'js/options.js';
$config['additional_javascript'][] = 'js/style-select.js';
@ -114,27 +97,23 @@
$config['additional_javascript'][] = 'js/update_boards.js';
$config['additional_javascript'][] = 'js/favorites.js';
$config['additional_javascript'][] = 'js/show-op.js';
$config['additional_javascript'][] = 'js/hide-threads.js';
$config['additional_javascript'][] = 'js/smartphone-spoiler.js';
$config['additional_javascript'][] = 'js/inline-expanding.js';
$config['additional_javascript'][] = 'js/show-backlinks.js';
$config['additional_javascript'][] = 'js/webm-settings.js';
$config['additional_javascript'][] = 'js/expand-video.js';
$config['additional_javascript'][] = 'js/treeview.js';
$config['additional_javascript'][] = 'js/quick-post-controls.js';
$config['additional_javascript'][] = 'js/expand-too-long.js';
$config['additional_javascript'][] = 'js/settings.js';
$config['additional_javascript'][] = 'js/fix-report-delete-submit.js';
$config['additional_javascript'][] = 'js/hide-images.js';
$config['additional_javascript'][] = 'js/expand-all-images.js';
$config['additional_javascript'][] = 'js/local-time.js';
$config['additional_javascript'][] = 'js/no-animated-gif.js';
$config['additional_javascript'][] = 'js/expand.js';
$config['additional_javascript'][] = 'js/titlebar-notifications.js';
$config['additional_javascript'][] = 'js/auto-reload.js';
$config['additional_javascript'][] = 'js/quick-reply.js';
$config['additional_javascript'][] = 'js/options/user-css.js';
$config['additional_javascript'][] = 'js/options/user-js.js';
$config['additional_javascript'][] = 'js/options/fav.js';
$config['additional_javascript'][] = 'js/forced-anon.js';
$config['additional_javascript'][] = 'js/toggle-locked-threads.js';
$config['additional_javascript'][] = 'js/toggle-images.js';
@ -146,349 +125,86 @@
$config['additional_javascript'][] = 'js/download-original.js';
$config['additional_javascript'][] = 'js/thread-watcher.js';
$config['additional_javascript'][] = 'js/ajax.js';
$config['additional_javascript'][] = 'js/quick-reply.js';
$config['additional_javascript'][] = 'js/show-own-posts.js';
$config['additional_javascript'][] = 'js/youtube.js';
$config['additional_javascript'][] = 'js/comment-toolbar.js';
$config['additional_javascript'][] = 'js/catalog-search.js';
$config['additional_javascript'][] = 'js/thread-stats.js';
$config['additional_javascript'][] = 'js/quote-selection.js';
$config['additional_javascript'][] = 'js/flag-previews.js';
$config['additional_javascript'][] = 'js/post-menu.js';
$config['additional_javascript'][] = 'js/post-filter.js';
$config['additional_javascript'][] = 'js/fix-report-delete-submit.js';
$config['additional_javascript'][] = 'js/image-hover.js';
$config['additional_javascript'][] = 'js/auto-scroll.js';
$config['additional_javascript'][] = 'js/twemoji/twemoji.js';
$config['additional_javascript'][] = 'js/file-selector.js';
// Oekaki (now depends on config.oekaki so can be in all scripts)
$config['additional_javascript'][] = 'js/jquery-ui.custom.min.js';
$config['additional_javascript'][] = 'js/wPaint/8ch.js';
$config['additional_javascript'][] = 'js/wpaint.js';
// Code tags (fix because we no longer have different scripts for each board)
$config['additional_javascript'][] = 'js/code_tags/run_prettify.js';
//$config['font_awesome_css'] = '/netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css';
$config['stylesheets']['Dark'] = 'dark.css';
$config['stylesheets']['Photon'] = 'photon.css';
$config['stylesheets']['Redchanit'] = 'redchanit.css';
$config['stylesheets_board'] = true;
$config['markup'][] = array("/^[ |\t]*==(.+?)==[ |\t]*$/m", "<span class=\"heading\">\$1</span>");
$config['markup'][] = array("/\[spoiler\](.+?)\[\/spoiler\]/", "<span class=\"spoiler\">\$1</span>");
$config['markup'][] = array("/~~(.+?)~~/", "<s>\$1</s>");
$config['markup'][] = array("/__(.+?)__/", "<u>\$1</u>");
$config['markup'][] = array("/###([^\s']+)###/", "<a href='/boards.html#\$1'>###\$1###</a>");
$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-ban" title="Public ban list"></i>' => '/bans.html', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta', 'int'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-ban" title="Public ban list"></i>' => '/bans.html', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'news+', 'boards'), array('operate', 'meta'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
//$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta', 'int'), array('v', 'a', 'tg', 'fit', 'pol', 'tech', 'mu', 'co', 'sp', 'boards'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
$config['footer'][] = 'Contribute to 8chan.co development at <a href="https://github.com/ctrlcctrlv/8chan">github</a>';
$config['footer'][] = 'To make a DMCA request or report illegal content, please email <a href="mailto:admin@8chan.co">admin@8chan.co</a> or use the "Global Report" functionality on every page.';
$config['footer'][] = 'All posts on 8chan are the responsibility of the individual poster and not the administration of 8chan, pursuant to 47 U.S.C. &sect; 230.';
$config['footer'][] = 'We have not been served any secret court orders and are not under any gag orders.';
$config['footer'][] = 'To make a DMCA request or report illegal content, please email <a href="mailto:admin@8chan.co">admin@8chan.co</a>.';
$config['search']['enable'] = true;
//$config['debug'] = true;
$config['syslog'] = true;
$config['wordfilters'][] = array('\rule', ''); // 'true' means it's a regular expression
if (!function_exists('prettify_textarea')){
function prettify_textarea($s){
return str_replace("\t", '&#09;', str_replace("\n", '&#13;&#10;', htmlentities($s)));
}
}
if (!function_exists('purify')){
function purify($s){
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$clean_html = $purifier->purify($s);
return $clean_html;
}
}
$config['mod']['custom_pages']['/flags/(\%b)'] = function($b) {
global $config, $mod, $board;
require_once 'inc/image.php';
if (!hasPermission($config['mod']['edit_flags'], $b))
error($config['mod']['noaccess']);
if (!openBoard($b))
error("Could not open board!");
$dir = 'static/custom-flags/'.$b;
if (!is_dir($dir)){
mkdir($dir, 0777, true);
}
if (isset($_FILES['file'])){
$upload = $_FILES['file']['tmp_name'];
$banners = array_diff(scandir($dir), array('..', '.'));
if (!is_readable($upload))
error($config['error']['nomove']);
$id = time() . substr(microtime(), 2, 3);
$extension = strtolower(mb_substr($_FILES['file']['name'], mb_strrpos($_FILES['file']['name'], '.') + 1));
if ($extension != 'png') {
error(_('Flags must be in PNG format.'));
}
if (filesize($upload) > 48000){
error(_('File too large!'));
}
if (!$size = @getimagesize($upload)) {
error($config['error']['invalidimg']);
}
if ($size[0] != 16 or $size[1] != 11){
error(_('Image wrong size!'));
}
if (sizeof($banners) >= 100) {
error(_('Too many flags.'));
}
copy($upload, "$dir/$id.$extension");
}
if (isset($_POST['delete'])){
foreach ($_POST['delete'] as $i => $d){
if (!preg_match('/[0-9+]\.(png|jpeg|jpg|gif)/', $d)){
error('Nice try.');
}
unlink("$dir/$d");
}
}
$banners = array_diff(scandir($dir), array('..', '.'));
mod_page(_('Edit banners'), 'mod/banners.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri'])));
};
$config['mod']['custom_pages']['/banners/(\%b)'] = function($b) {
global $config, $mod, $board;
require_once 'inc/image.php';
if (!hasPermission($config['mod']['edit_banners'], $b))
error($config['error']['noaccess']);
if (!openBoard($b))
error("Could not open board!");
$dir = 'static/banners/'.$b;
if (!is_dir($dir)){
mkdir($dir, 0777, true);
}
if (isset($_FILES['file'])){
$upload = $_FILES['file']['tmp_name'];
$banners = array_diff(scandir($dir), array('..', '.'));
if (!is_readable($upload))
error($config['error']['nomove']);
$id = time() . substr(microtime(), 2, 3);
$extension = strtolower(mb_substr($_FILES['file']['name'], mb_strrpos($_FILES['file']['name'], '.') + 1));
if (!in_array($extension, array('jpg','jpeg','png','gif'))){
error('Not an image extension.');
}
if (filesize($upload) > 512000){
error('File too large!');
}
if (!$size = @getimagesize($upload)) {
error($config['error']['invalidimg']);
}
if ($size[0] != 300 or $size[1] != 100){
error('Image wrong size!');
}
if (sizeof($banners) >= 50) {
error('Too many banners.');
}
copy($upload, "$dir/$id.$extension");
}
if (isset($_POST['delete'])){
foreach ($_POST['delete'] as $i => $d){
if (!preg_match('/[0-9+]\.(png|jpeg|jpg|gif)/', $d)){
error('Nice try.');
}
unlink("$dir/$d");
}
}
$banners = array_diff(scandir($dir), array('..', '.'));
mod_page(_('Edit banners'), 'mod/banners.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri'])));
};
$config['mod']['custom_pages']['/settings/(\%b)'] = function($b) {
global $config, $mod;
if (!in_array($b, $mod['boards']) and $mod['boards'][0] != '*')
error($config['error']['noaccess']);
if (!openBoard($b))
error("Could not open board!");
$possible_languages = array_diff(scandir('inc/locale/'), array('..', '.', '.tx', 'README.md'));
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$title = $_POST['title'];
$subtitle = $_POST['subtitle'];
$country_flags = isset($_POST['country_flags']) ? 'true' : 'false';
$field_disable_name = isset($_POST['field_disable_name']) ? 'true' : 'false';
$enable_embedding = isset($_POST['enable_embedding']) ? 'true' : 'false';
$force_image_op = isset($_POST['force_image_op']) ? 'true' : 'false';
$disable_images = isset($_POST['disable_images']) ? 'true' : 'false';
$poster_ids = isset($_POST['poster_ids']) ? 'true' : 'false';
$show_sages = isset($_POST['show_sages']) ? 'true' : 'false';
$auto_unicode = isset($_POST['auto_unicode']) ? 'true' : 'false';
$allow_roll = isset($_POST['allow_roll']) ? 'true' : 'false';
$image_reject_repost = isset($_POST['image_reject_repost']) ? 'true' : 'false';
$allow_delete = isset($_POST['allow_delete']) ? 'true' : 'false';
$allow_flash = isset($_POST['allow_flash']) ? '$config[\'allowed_ext_files\'][] = \'swf\';' : '';
$code_tags = isset($_POST['code_tags']) ? '$config[\'additional_javascript\'][] = \'js/code_tags/run_prettify.js\';$config[\'markup\'][] = array("/\[code\](.+?)\[\/code\]/ms", "<code><pre class=\'prettyprint\' style=\'display:inline-block\'>\$1</pre></code>");' : '';
$katex = isset($_POST['katex']) ? '$config[\'katex\'] = true;$config[\'additional_javascript\'][] = \'js/katex/katex.min.js\'; $config[\'markup\'][] = array("/\[tex\](.+?)\[\/tex\]/ms", "<span class=\'tex\'>\$1</span>"); $config[\'additional_javascript\'][] = \'js/katex-enable.js\';' : '';
$oekaki_js = <<<OEKAKI
\$config['additional_javascript'][] = 'js/jquery-ui.custom.min.js';
\$config['additional_javascript'][] = 'js/wPaint/lib/wColorPicker.min.js';
\$config['additional_javascript'][] = 'js/wPaint/wPaint.min.js';
\$config['additional_javascript'][] = 'js/wPaint/plugins/main/wPaint.menu.main.min.js';
\$config['additional_javascript'][] = 'js/wPaint/plugins/text/wPaint.menu.text.min.js';
\$config['additional_javascript'][] = 'js/wPaint/plugins/shapes/wPaint.menu.main.shapes.min.js';
\$config['additional_javascript'][] = 'js/wPaint/plugins/file/wPaint.menu.main.file.min.js';
\$config['additional_javascript'][] = 'js/wpaint.js';
\$config['additional_javascript'][] = 'js/upload-selection.js';
OEKAKI;
$oekaki = isset($_POST['oekaki']) ? $oekaki_js : '';
if ($_POST['locale'] !== 'en' && in_array($_POST['locale'], $possible_languages)) {
$locale = "\$config['locale'] = '{$_POST['locale']}.UTF-8';";
} else {
$locale = '';
}
if (isset($_POST['max_images']) && (int)$_POST['max_images'] && (int)$_POST['max_images'] <= 5) {
$_POST['max_images'] = (int)$_POST['max_images'];
$multiimage = "\$config['max_images'] = {$_POST['max_images']};
\$config['additional_javascript'][] = 'js/multi-image.js';";
} else {
$multiimage = '';
}
$anonymous = base64_encode($_POST['anonymous']);
$blotter = base64_encode(purify(html_entity_decode($_POST['blotter'])));
$add_to_config = @file_get_contents($b.'/extra_config.php');
$replace = '';
if (isset($_POST['replace'])) {
if (count($_POST['replace']) == count($_POST['with'])) {
foreach ($_POST['replace'] as $i => $r ) {
if ($r !== '') {
$w = $_POST['with'][$i];
$replace .= '$config[\'wordfilters\'][] = array(base64_decode(\'' . base64_encode($r) . '\'), base64_decode(\'' . base64_encode($w) . '\'));';
}
}
}
}
if (!(strlen($title) < 40))
error('Invalid title');
if (!(strlen($subtitle) < 200))
error('Invalid subtitle');
$query = prepare('UPDATE ``boards`` SET `title` = :title, `subtitle` = :subtitle, `indexed` = :indexed, `public_bans` = :public_bans, `8archive` = :8archive WHERE `uri` = :uri');
$query->bindValue(':title', $title);
$query->bindValue(':subtitle', $subtitle);
$query->bindValue(':uri', $b);
$query->bindValue(':indexed', !isset($_POST['meta_noindex']));
$query->bindValue(':public_bans', isset($_POST['public_bans']));
$query->bindValue(':8archive', isset($_POST['8archive']));
$query->execute() or error(db_error($query));
$config_file = <<<EOT
<?php
\$config['file_script'] = '$b/main.js';
\$config['country_flags'] = $country_flags;
\$config['field_disable_name'] = $field_disable_name;
\$config['enable_embedding'] = $enable_embedding;
\$config['force_image_op'] = $force_image_op;
\$config['disable_images'] = $disable_images;
\$config['poster_ids'] = $poster_ids;
\$config['show_sages'] = $show_sages;
\$config['auto_unicode'] = $auto_unicode;
\$config['allow_roll'] = $allow_roll;
\$config['image_reject_repost'] = $image_reject_repost;
\$config['allow_delete'] = $allow_delete;
\$config['anonymous'] = base64_decode('$anonymous');
\$config['blotter'] = base64_decode('$blotter');
\$config['stylesheets']['Custom'] = 'board/$b.css';
\$config['default_stylesheet'] = array('Custom', \$config['stylesheets']['Custom']);
$code_tags $katex $oekaki $replace $multiimage $allow_flash
if (\$config['disable_images'])
\$config['max_pages'] = 10000;
$locale
$add_to_config
EOT;
$query = query('SELECT `uri`, `title`, `subtitle` FROM ``boards`` WHERE `8archive` = TRUE');
file_write('8archive.json', json_encode($query->fetchAll(PDO::FETCH_ASSOC)));
file_write($b.'/config.php', $config_file);
file_write('stylesheets/board/'.$b.'.css', $_POST['css']);
file_write($b.'/rules.html', Element('page.html', array('title'=>'Rules', 'subtitle'=>'', 'config'=>$config, 'body'=>'<div class="ban">'.purify($_POST['rules']).'</div>')));
file_write($b.'/rules.txt', $_POST['rules']);
$_config = $config;
openBoard($b);
// be smarter about rebuilds...only some changes really require us to rebuild all threads
if ($_config['blotter'] != $config['blotter'] || $_config['field_disable_name'] != $config['field_disable_name'] || $_config['show_sages'] != $config['show_sages']) {
buildIndex();
$query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL", $b)) or error(db_error());
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
buildThread($post['id']);
}
}
buildJavascript();
modLog('Edited board settings', $b);
}
$query = prepare('SELECT * FROM boards WHERE uri = :board');
$query->bindValue(':board', $b);
$query->execute() or error(db_error($query));
$board = $query->fetchAll()[0];
$rules = @file_get_contents($board['uri'] . '/rules.txt');
$css = @file_get_contents('stylesheets/board/' . $board['uri'] . '.css');
openBoard($b);
rebuildThemes('bans');
if ($config['cache']['enabled'])
cache::delete('board_' . $board['uri']);
cache::delete('all_boards');
mod_page(_('Board configuration'), 'mod/settings.html', array('board'=>$board, 'rules'=>prettify_textarea($rules), 'css'=>prettify_textarea($css), 'token'=>make_secure_link_token('settings/'.$board['uri']), 'languages'=>$possible_languages));
};
$config['embedding'] = array(
array(
'/^https?:\/\/(\w+\.)?youtube\.com\/watch\?v=([a-zA-Z0-9\-_]{10,11})(&.+)?$/i',
'<iframe style="float: left;margin: 10px 20px;" width="%%tb_width%%" height="%%tb_height%%" frameborder="0" id="ytplayer" type="text/html" src="https://www.youtube.com/embed/$2"></iframe>'
$config['hour_max_threads'] = 10;
$config['filters'][] = array(
'condition' => array(
'custom' => 'max_posts_per_hour'
),
array(
'/^https?:\/\/(\w+\.)?vimeo\.com\/(\d{2,10})(\?.+)?$/i',
'<object style="float: left;margin: 10px 20px;" width="%%tb_width%%" height="%%tb_height%%"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="https://vimeo.com/moogaloop.swf?clip_id=$2&amp;server=vimeo.com&amp;show_title=0&amp;show_byline=0&amp;show_portrait=0&amp;color=00adef&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" /><embed src="https://vimeo.com/moogaloop.swf?clip_id=$2&amp;server=vimeo.com&amp;show_title=0&amp;show_byline=0&amp;show_portrait=0&amp;color=00adef&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="%%tb_width%%" height="%%tb_height%%"></embed></object>'
),
array(
'/^https?:\/\/(\w+\.)?dailymotion\.com\/video\/([a-zA-Z0-9]{2,10})(_.+)?$/i',
'<object style="float: left;margin: 10px 20px;" width="%%tb_width%%" height="%%tb_height%%"><param name="movie" value="https://www.dailymotion.com/swf/video/$2"></param><param name="allowFullScreen" value="true"></param><param name="allowScriptAccess" value="always"></param><param name="wmode" value="transparent"></param><embed type="application/x-shockwave-flash" src="https://www.dailymotion.com/swf/video/$2" width="%%tb_width%%" height="%%tb_height%%" wmode="transparent" allowfullscreen="true" allowscriptaccess="always"></embed></object>'
),
array(
'/^https?:\/\/(\w+\.)?metacafe\.com\/watch\/(\d+)\/([a-zA-Z0-9_\-.]+)\/(\?.+)?$/i',
'<div style="float:left;margin:10px 20px;width:%%tb_width%%px;height:%%tb_height%%px"><embed flashVars="playerVars=showStats=no|autoPlay=no" src="https://www.metacafe.com/fplayer/$2/$3.swf" width="%%tb_width%%" height="%%tb_height%%" wmode="transparent" allowFullScreen="true" allowScriptAccess="always" name="Metacafe_$2" pluginspage="https://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"></embed></div>'
),
array(
'/^https?:\/\/video\.google\.com\/videoplay\?docid=(\d+)([&#](.+)?)?$/i',
'<embed src="https://video.google.com/googleplayer.swf?docid=$1&hl=en&fs=true" style="width:%%tb_width%%px;height:%%tb_height%%px;float:left;margin:10px 20px" allowFullScreen="true" allowScriptAccess="always" type="application/x-shockwave-flash"></embed>'
),
array(
'/^https?:\/\/(\w+\.)?vocaroo\.com\/i\/([a-zA-Z0-9]{2,15})$/i',
'<object style="float: left;margin: 10px 20px;" width="148" height="44"><param name="movie" value="https://vocaroo.com/player.swf?playMediaID=$2&autoplay=0"></param><param name="wmode" value="transparent"></param><embed src="https://vocaroo.com/player.swf?playMediaID=$2&autoplay=0" width="148" height="44" wmode="transparent" type="application/x-shockwave-flash"></embed></object>'
)
'action' => 'reject',
'message' => 'On this board, to prevent raids the number of threads that can be created per hour is limited. Please try again later, or post in an existing thread.'
);
$config['gzip_static'] = false;
$config['hash_masked_ip'] = true;
$config['force_subject_op'] = false;
$config['min_links'] = 0;
$config['min_body'] = 0;
$config['early_404'] = false;
$config['early_404_page'] = 5;
$config['early_404_replies'] = 10;
$config['cron_bans'] = true;
$config['mask_db_error'] = true;
$config['ban_appeals'] = true;
$config['show_sages'] = false;
$config['katex'] = false;
$config['enable_antibot'] = false;
$config['spam']['unicode'] = false;
$config['twig_cache'] = false;
$config['report_captcha'] = true;
$config['page_404'] = 'page_404';
// 8chan specific mod pages
require '8chan-mod-config.php';
// Load instance functions later on
require_once 'instance-functions.php';
// Load database credentials
require "secrets.php";

View File

@ -0,0 +1,24 @@
<?php
require_once("inc/8chan-functions.php");
require_once("inc/8chan-mod-pages.php");
require_once "lib/htmlpurifier-4.6.0/library/HTMLPurifier.auto.php";
function max_posts_per_hour($post) {
global $config, $board;
if (!$config['hour_max_threads']) return false;
if ($post['op']) {
$query = prepare(sprintf('SELECT COUNT(*) AS `count` FROM ``posts_%s`` WHERE `thread` IS NULL AND FROM_UNIXTIME(`time`) > DATE_SUB(NOW(), INTERVAL 1 HOUR);', $board['uri']));
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->execute() or error(db_error($query));
$r = $query->fetch(PDO::FETCH_ASSOC);
return ($r['count'] > $config['hour_max_threads']);
}
}
function page_404() {
include('404.php');
}

View File

@ -19,14 +19,14 @@ class Twig_Autoloader
/**
* Registers Twig_Autoloader as an SPL autoloader.
*
* @param Boolean $prepend Whether to prepend the autoloader or not.
* @param bool $prepend Whether to prepend the autoloader or not.
*/
public static function register($prepend = false)
{
if (version_compare(phpversion(), '5.3.0', '>=')) {
spl_autoload_register(array(new self, 'autoload'), true, $prepend);
if (PHP_VERSION_ID < 50300) {
spl_autoload_register(array(__CLASS__, 'autoload'));
} else {
spl_autoload_register(array(new self, 'autoload'));
spl_autoload_register(array(__CLASS__, 'autoload'), true, $prepend);
}
}

View File

@ -66,7 +66,7 @@ class Twig_Compiler implements Twig_CompilerInterface
* Compiles a node.
*
* @param Twig_NodeInterface $node The node to compile
* @param integer $indentation The current indentation
* @param int $indentation The current indentation
*
* @return Twig_Compiler The current compiler instance
*/
@ -74,6 +74,7 @@ class Twig_Compiler implements Twig_CompilerInterface
{
$this->lastLine = null;
$this->source = '';
$this->debugInfo = array();
$this->sourceOffset = 0;
// source code starts at 1 (as we then increment it when we encounter new lines)
$this->sourceLine = 1;
@ -181,14 +182,14 @@ class Twig_Compiler implements Twig_CompilerInterface
} elseif (is_array($value)) {
$this->raw('array(');
$first = true;
foreach ($value as $key => $value) {
foreach ($value as $key => $v) {
if (!$first) {
$this->raw(', ');
}
$first = false;
$this->repr($key);
$this->raw(' => ');
$this->repr($value);
$this->repr($v);
}
$this->raw(')');
} else {
@ -208,7 +209,7 @@ class Twig_Compiler implements Twig_CompilerInterface
public function addDebugInfo(Twig_NodeInterface $node)
{
if ($node->getLine() != $this->lastLine) {
$this->write("// line {$node->getLine()}\n");
$this->write(sprintf("// line %d\n", $node->getLine()));
// when mbstring.func_overload is set to 2
// mb_substr_count() replaces substr_count()
@ -230,13 +231,15 @@ class Twig_Compiler implements Twig_CompilerInterface
public function getDebugInfo()
{
ksort($this->debugInfo);
return $this->debugInfo;
}
/**
* Indents the generated code.
*
* @param integer $step The number of indentation to add
* @param int $step The number of indentation to add
*
* @return Twig_Compiler The current compiler instance
*/
@ -250,9 +253,11 @@ class Twig_Compiler implements Twig_CompilerInterface
/**
* Outdents the generated code.
*
* @param integer $step The number of indentation to remove
* @param int $step The number of indentation to remove
*
* @return Twig_Compiler The current compiler instance
*
* @throws LogicException When trying to outdent too much so the indentation would become negative
*/
public function outdent($step = 1)
{
@ -265,4 +270,9 @@ class Twig_Compiler implements Twig_CompilerInterface
return $this;
}
public function getVarName()
{
return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false));
}
}

View File

@ -13,7 +13,8 @@
* Interface implemented by compiler classes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*
* @deprecated since 1.12 (to be removed in 3.0)
*/
interface Twig_CompilerInterface
{

View File

@ -16,7 +16,7 @@
*/
class Twig_Environment
{
const VERSION = '1.14.0-DEV';
const VERSION = '1.18.1-DEV';
protected $charset;
protected $loader;
@ -44,7 +44,6 @@ class Twig_Environment
protected $functionCallbacks;
protected $filterCallbacks;
protected $staging;
protected $templateClasses;
/**
* Constructor.
@ -62,9 +61,9 @@ class Twig_Environment
* * cache: An absolute path where to store the compiled templates, or
* false to disable compilation cache (default).
*
* * auto_reload: Whether to reload the template is the original source changed.
* * auto_reload: Whether to reload the template if the original source changed.
* If you don't provide the auto_reload option, it will be
* determined automatically base on the debug value.
* determined automatically based on the debug value.
*
* * strict_variables: Whether to ignore invalid variables in templates
* (default to false).
@ -73,6 +72,7 @@ class Twig_Environment
* * false: disable auto-escaping
* * true: equivalent to html
* * html, js: set the autoescaping to one of the supported strategies
* * filename: set the autoescaping strategy based on the template filename extension
* * PHP callback: a PHP callback that returns an escaping strategy based on the template "filename"
*
* * optimizations: A flag that indicates which optimizations to apply
@ -108,7 +108,6 @@ class Twig_Environment
$this->setCache($options['cache']);
$this->functionCallbacks = array();
$this->filterCallbacks = array();
$this->templateClasses = array();
$this->addExtension(new Twig_Extension_Core());
$this->addExtension(new Twig_Extension_Escaper($options['autoescape']));
@ -156,7 +155,7 @@ class Twig_Environment
/**
* Checks if debug mode is enabled.
*
* @return Boolean true if debug mode is enabled, false otherwise
* @return bool true if debug mode is enabled, false otherwise
*/
public function isDebug()
{
@ -182,7 +181,7 @@ class Twig_Environment
/**
* Checks if the auto_reload option is enabled.
*
* @return Boolean true if auto_reload is enabled, false otherwise
* @return bool true if auto_reload is enabled, false otherwise
*/
public function isAutoReload()
{
@ -208,7 +207,7 @@ class Twig_Environment
/**
* Checks if the strict_variables option is enabled.
*
* @return Boolean true if strict_variables is enabled, false otherwise
* @return bool true if strict_variables is enabled, false otherwise
*/
public function isStrictVariables()
{
@ -225,12 +224,12 @@ class Twig_Environment
return $this->cache;
}
/**
* Sets the cache directory or false if cache is disabled.
*
* @param string|false $cache The absolute path to the compiled templates,
* or false to disable cache
*/
/**
* Sets the cache directory or false if cache is disabled.
*
* @param string|false $cache The absolute path to the compiled templates,
* or false to disable cache
*/
public function setCache($cache)
{
$this->cache = $cache ? $cache : false;
@ -241,7 +240,7 @@ class Twig_Environment
*
* @param string $name The template name
*
* @return string The cache file name
* @return string|false The cache file name or false when caching is disabled
*/
public function getCacheFilename($name)
{
@ -257,20 +256,14 @@ class Twig_Environment
/**
* Gets the template class associated with the given string.
*
* @param string $name The name for which to calculate the template class name
* @param integer $index The index if it is an embedded template
* @param string $name The name for which to calculate the template class name
* @param int $index The index if it is an embedded template
*
* @return string The template class name
*/
public function getTemplateClass($name, $index = null)
{
$suffix = null === $index ? '' : '_'.$index;
$cls = $name.$suffix;
if (isset($this->templateClasses[$cls])) {
return $this->templateClasses[$cls];
}
return $this->templateClasses[$cls] = $this->templateClassPrefix.hash('sha256', $this->getLoader()->getCacheKey($name)).$suffix;
return $this->templateClassPrefix.hash('sha256', $this->getLoader()->getCacheKey($name)).(null === $index ? '' : '_'.$index);
}
/**
@ -290,6 +283,10 @@ class Twig_Environment
* @param array $context An array of parameters to pass to the template
*
* @return string The rendered template
*
* @throws Twig_Error_Loader When the template cannot be found
* @throws Twig_Error_Syntax When an error occurred during compilation
* @throws Twig_Error_Runtime When an error occurred during rendering
*/
public function render($name, array $context = array())
{
@ -301,6 +298,10 @@ class Twig_Environment
*
* @param string $name The template name
* @param array $context An array of parameters to pass to the template
*
* @throws Twig_Error_Loader When the template cannot be found
* @throws Twig_Error_Syntax When an error occurred during compilation
* @throws Twig_Error_Runtime When an error occurred during rendering
*/
public function display($name, array $context = array())
{
@ -310,10 +311,13 @@ class Twig_Environment
/**
* Loads a template by name.
*
* @param string $name The template name
* @param integer $index The index if it is an embedded template
* @param string $name The template name
* @param int $index The index if it is an embedded template
*
* @return Twig_TemplateInterface A template instance representing the given template name
*
* @throws Twig_Error_Loader When the template cannot be found
* @throws Twig_Error_Syntax When an error occurred during compilation
*/
public function loadTemplate($name, $index = null)
{
@ -342,6 +346,41 @@ class Twig_Environment
return $this->loadedTemplates[$cls] = new $cls($this);
}
/**
* Creates a template from source.
*
* This method should not be used as a generic way to load templates.
*
* @param string $name The template name
* @param int $index The index if it is an embedded template
*
* @return Twig_Template A template instance representing the given template name
*
* @throws Twig_Error_Loader When the template cannot be found
* @throws Twig_Error_Syntax When an error occurred during compilation
*/
public function createTemplate($template)
{
$name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), true), false));
$loader = new Twig_Loader_Chain(array(
new Twig_Loader_Array(array($name => $template)),
$current = $this->getLoader(),
));
$this->setLoader($loader);
try {
$template = $this->loadTemplate($name);
} catch (Exception $e) {
$this->setLoader($current);
throw $e;
}
$this->setLoader($current);
return $template;
}
/**
* Returns true if the template is still fresh.
*
@ -349,10 +388,10 @@ class Twig_Environment
* this method also checks if the enabled extensions have
* not changed.
*
* @param string $name The template name
* @param timestamp $time The last modification time of the cached template
* @param string $name The template name
* @param int $time The last modification time of the cached template
*
* @return Boolean true if the template is fresh, false otherwise
* @return bool true if the template is fresh, false otherwise
*/
public function isTemplateFresh($name, $time)
{
@ -366,6 +405,19 @@ class Twig_Environment
return $this->getLoader()->isFresh($name, $time);
}
/**
* Tries to load a template consecutively from an array.
*
* Similar to loadTemplate() but it also accepts Twig_TemplateInterface instances and an array
* of templates where each is tried to be loaded.
*
* @param string|Twig_Template|array $names A template or an array of templates to try consecutively
*
* @return Twig_Template
*
* @throws Twig_Error_Loader When none of the templates can be found
* @throws Twig_Error_Syntax When an error occurred during compilation
*/
public function resolveTemplate($names)
{
if (!is_array($names)) {
@ -445,6 +497,8 @@ class Twig_Environment
* @param string $name The template name
*
* @return Twig_TokenStream A Twig_TokenStream instance
*
* @throws Twig_Error_Syntax When the code is syntactically wrong
*/
public function tokenize($source, $name = null)
{
@ -476,15 +530,17 @@ class Twig_Environment
}
/**
* Parses a token stream.
* Converts a token stream to a node tree.
*
* @param Twig_TokenStream $tokens A Twig_TokenStream instance
* @param Twig_TokenStream $stream A token stream instance
*
* @return Twig_Node_Module A Node tree
* @return Twig_Node_Module A node tree
*
* @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong
*/
public function parse(Twig_TokenStream $tokens)
public function parse(Twig_TokenStream $stream)
{
return $this->getParser()->parse($tokens);
return $this->getParser()->parse($stream);
}
/**
@ -512,7 +568,7 @@ class Twig_Environment
}
/**
* Compiles a Node.
* Compiles a node and returns the PHP code.
*
* @param Twig_NodeInterface $node A Twig_NodeInterface instance
*
@ -530,6 +586,8 @@ class Twig_Environment
* @param string $name The template name
*
* @return string The compiled PHP source code
*
* @throws Twig_Error_Syntax When there was an error during tokenizing, parsing or compiling
*/
public function compileSource($source, $name = null)
{
@ -539,7 +597,7 @@ class Twig_Environment
$e->setTemplateFile($name);
throw $e;
} catch (Exception $e) {
throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
}
}
@ -604,7 +662,7 @@ class Twig_Environment
*
* @param string $name The extension name
*
* @return Boolean Whether the extension is registered or not
* @return bool Whether the extension is registered or not
*/
public function hasExtension($name)
{
@ -772,11 +830,11 @@ class Twig_Environment
$filter = $name;
$name = $filter->getName();
}
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name));
}
$this->staging->addFilter($name, $filter);
}
@ -861,7 +919,7 @@ class Twig_Environment
$test = $name;
$name = $test->getName();
}
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name));
}
@ -919,11 +977,11 @@ class Twig_Environment
$function = $name;
$name = $function->getName();
}
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name));
}
$this->staging->addFunction($name, $function);
}
@ -1210,14 +1268,17 @@ class Twig_Environment
{
$dir = dirname($file);
if (!is_dir($dir)) {
if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
if (false === @mkdir($dir, 0777, true)) {
clearstatcache(false, $dir);
if (!is_dir($dir)) {
throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
}
}
} elseif (!is_writable($dir)) {
throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
}
$tmpFile = tempnam(dirname($file), basename($file));
$tmpFile = tempnam($dir, basename($file));
if (false !== @file_put_contents($tmpFile, $content)) {
// rename does not work on Win32 before 5.2.6
if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {

View File

@ -51,13 +51,13 @@ class Twig_Error extends Exception
* By default, automatic guessing is enabled.
*
* @param string $message The error message
* @param integer $lineno The template line where the error occurred
* @param int $lineno The template line where the error occurred
* @param string $filename The template file name where the error occurred
* @param Exception $previous The previous exception
*/
public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
{
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
if (PHP_VERSION_ID < 50300) {
$this->previous = $previous;
parent::__construct('');
} else {
@ -111,7 +111,7 @@ class Twig_Error extends Exception
/**
* Gets the template line where the error occurred.
*
* @return integer The template line
* @return int The template line
*/
public function getTemplateLine()
{
@ -121,7 +121,7 @@ class Twig_Error extends Exception
/**
* Sets the template line where the error occurred.
*
* @param integer $lineno The template line
* @param int $lineno The template line
*/
public function setTemplateLine($lineno)
{
@ -188,7 +188,7 @@ class Twig_Error extends Exception
$template = null;
$templateClass = null;
if (version_compare(phpversion(), '5.3.6', '>=')) {
if (PHP_VERSION_ID >= 50306) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
} else {
$backtrace = debug_backtrace();
@ -217,6 +217,11 @@ class Twig_Error extends Exception
$r = new ReflectionObject($template);
$file = $r->getFileName();
// hhvm has a bug where eval'ed files comes out as the current directory
if (is_dir($file)) {
$file = '';
}
$exceptions = array($e = $this);
while (($e instanceof self || method_exists($e, 'getPrevious')) && $e = $e->getPrevious()) {
$exceptions[] = $e;
@ -224,6 +229,8 @@ class Twig_Error extends Exception
while ($e = array_pop($exceptions)) {
$traces = $e->getTrace();
array_unshift($traces, array('file' => $e->getFile(), 'line' => $e->getLine()));
while ($trace = array_shift($traces)) {
if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
continue;

View File

@ -13,7 +13,8 @@
* Adds an exists() method for loaders.
*
* @author Florin Patan <florinpatan@gmail.com>
* @deprecated since 1.12 (to be removed in 2.0)
*
* @deprecated since 1.12 (to be removed in 3.0)
*/
interface Twig_ExistsLoaderInterface
{
@ -22,7 +23,7 @@ interface Twig_ExistsLoaderInterface
*
* @param string $name The name of the template to check if we can load
*
* @return boolean If the template source code is handled by this loader or not
* @return bool If the template source code is handled by this loader or not
*/
public function exists($name);
}

View File

@ -86,18 +86,15 @@ class Twig_ExpressionParser
protected function parseConditionalExpression($expr)
{
while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) {
$this->parser->getStream()->next();
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
while ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, '?')) {
if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
$expr2 = $this->parseExpression();
if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
$this->parser->getStream()->next();
if ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
$expr3 = $this->parseExpression();
} else {
$expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine());
}
} else {
$this->parser->getStream()->next();
$expr2 = $expr;
$expr3 = $this->parseExpression();
}
@ -161,13 +158,36 @@ class Twig_ExpressionParser
$node = $this->parseStringExpression();
break;
case Twig_Token::OPERATOR_TYPE:
if (preg_match(Twig_Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
// in this context, string operators are variable names
$this->parser->getStream()->next();
$node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
break;
} elseif (isset($this->unaryOperators[$token->getValue()])) {
$class = $this->unaryOperators[$token->getValue()]['class'];
$ref = new ReflectionClass($class);
$negClass = 'Twig_Node_Expression_Unary_Neg';
$posClass = 'Twig_Node_Expression_Unary_Pos';
if (!(in_array($ref->getName(), array($negClass, $posClass)) || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass))) {
throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename());
}
$this->parser->getStream()->next();
$expr = $this->parsePrimaryExpression();
$node = new $class($expr, $token->getLine());
break;
}
default:
if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
$node = $this->parseArrayExpression();
} elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
$node = $this->parseHashExpression();
} else {
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine(), $this->parser->getFilename());
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getFilename());
}
}
@ -182,12 +202,10 @@ class Twig_ExpressionParser
// a string cannot be followed by another string in a single expression
$nextCanBeString = true;
while (true) {
if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) {
$token = $stream->next();
if ($nextCanBeString && $token = $stream->nextIf(Twig_Token::STRING_TYPE)) {
$nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
$nextCanBeString = false;
} elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) {
$stream->next();
} elseif ($stream->nextIf(Twig_Token::INTERPOLATION_START_TYPE)) {
$nodes[] = $this->parseExpression();
$stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
$nextCanBeString = true;
@ -253,15 +271,14 @@ class Twig_ExpressionParser
// * a string -- 'a'
// * a name, which is equivalent to a string -- a
// * an expression, which must be enclosed in parentheses -- (1 + 2)
if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) {
$token = $stream->next();
if (($token = $stream->nextIf(Twig_Token::STRING_TYPE)) || ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) || $token = $stream->nextIf(Twig_Token::NUMBER_TYPE)) {
$key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
} elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$key = $this->parseExpression();
} else {
$current = $stream->getCurrent();
throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine(), $this->parser->getFilename());
throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $this->parser->getFilename());
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
@ -316,23 +333,23 @@ class Twig_ExpressionParser
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
}
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_Template::ANY_CALL, $line);
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : null, Twig_Template::ANY_CALL, $line);
default:
$args = $this->parseArguments(true);
if (null !== $alias = $this->parser->getImportedSymbol('macro', $name)) {
return new Twig_Node_Expression_MacroCall($alias['node'], $alias['name'], $this->createArrayFromArguments($args), $line);
}
try {
$class = $this->getFunctionNodeClass($name, $line);
} catch (Twig_Error_Syntax $e) {
if (!$this->parser->hasMacro($name)) {
throw $e;
if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
$arguments = new Twig_Node_Expression_Array(array(), $line);
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
return new Twig_Node_Expression_MacroCall(new Twig_Node_Expression_Name('_self', $line), $name, $this->createArrayFromArguments($args), $line);
$node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
$node->setAttribute('safe', true);
return $node;
}
$args = $this->parseArguments(true);
$class = $this->getFunctionNodeClass($name, $line);
return new $class($name, $args, $line);
}
}
@ -354,6 +371,13 @@ class Twig_ExpressionParser
($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
) {
$arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$type = Twig_TemplateInterface::METHOD_CALL;
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
}
} else {
throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
}
@ -363,14 +387,10 @@ class Twig_ExpressionParser
throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
}
$arguments = $this->createArrayFromArguments($this->parseArguments(true));
$node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno);
$node->setAttribute('safe', true);
return new Twig_Node_Expression_MacroCall($node, $arg->getAttribute('value'), $arguments, $lineno);
}
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$type = Twig_Template::METHOD_CALL;
$arguments = $this->createArrayFromArguments($this->parseArguments());
return $node;
}
} else {
$type = Twig_Template::ARRAY_CALL;
@ -384,9 +404,8 @@ class Twig_ExpressionParser
$arg = $this->parseExpression();
}
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
if ($stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
$slice = true;
$stream->next();
}
if ($slice) {
@ -447,10 +466,8 @@ class Twig_ExpressionParser
/**
* Parses arguments.
*
* @param Boolean $namedArguments Whether to allow named arguments or not
* @param Boolean $definition Whether we are parsing arguments for a function definition
*
* @return Twig_Node
* @param bool $namedArguments Whether to allow named arguments or not
* @param bool $definition Whether we are parsing arguments for a function definition
*/
public function parseArguments($namedArguments = false, $definition = false)
{
@ -471,8 +488,7 @@ class Twig_ExpressionParser
}
$name = null;
if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
$token = $stream->next();
if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) {
if (!$value instanceof Twig_Node_Expression_Name) {
throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename());
}
@ -482,26 +498,25 @@ class Twig_ExpressionParser
$value = $this->parsePrimaryExpression();
if (!$this->checkConstantExpression($value)) {
throw new Twig_Error_Syntax('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $this->parser->getFilename());
throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $this->parser->getFilename());
}
} else {
$value = $this->parseExpression();
}
}
if ($definition && null === $name) {
$name = $value->getAttribute('name');
$value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
}
if (null === $name) {
$args[] = $value;
} else {
if ($definition && isset($args[$name])) {
throw new Twig_Error_Syntax(sprintf('Arguments cannot contain the same argument name more than once ("%s" is defined twice).', $name), $token->getLine(), $this->parser->getFilename());
if ($definition) {
if (null === $name) {
$name = $value->getAttribute('name');
$value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
}
$args[$name] = $value;
} else {
if (null === $name) {
$args[] = $value;
} else {
$args[$name] = $value;
}
}
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
@ -519,10 +534,9 @@ class Twig_ExpressionParser
}
$targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) {
break;
}
$this->parser->getStream()->next();
}
return new Twig_Node($targets);
@ -533,10 +547,9 @@ class Twig_ExpressionParser
$targets = array();
while (true) {
$targets[] = $this->parseExpression();
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) {
break;
}
$this->parser->getStream()->next();
}
return new Twig_Node($targets);
@ -585,7 +598,9 @@ class Twig_ExpressionParser
// checks that the node only contains "constant" elements
protected function checkConstantExpression(Twig_NodeInterface $node)
{
if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array)) {
if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array
|| $node instanceof Twig_Node_Expression_Unary_Neg || $node instanceof Twig_Node_Expression_Unary_Pos
)) {
return false;
}
@ -597,15 +612,4 @@ class Twig_ExpressionParser
return true;
}
private function createArrayFromArguments(Twig_Node $arguments, $line = null)
{
$line = null === $line ? $arguments->getLine() : $line;
$array = new Twig_Node_Expression_Array(array(), $line);
foreach ($arguments as $key => $value) {
$array->addElement($value, new Twig_Node_Expression_Constant($key, $value->getLine()));
}
return $array;
}
}

View File

@ -34,7 +34,7 @@ abstract class Twig_Extension implements Twig_ExtensionInterface
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors()
{

View File

@ -1,7 +1,8 @@
<?php
if (!defined('ENT_SUBSTITUTE')) {
define('ENT_SUBSTITUTE', 8);
// use 0 as hhvm does not support several flags yet
define('ENT_SUBSTITUTE', 0);
}
/*
@ -17,6 +18,28 @@ class Twig_Extension_Core extends Twig_Extension
protected $dateFormats = array('F j, Y H:i', '%d days');
protected $numberFormat = array(0, '.', ',');
protected $timezone = null;
protected $escapers = array();
/**
* Defines a new escaper to be used via the escape filter.
*
* @param string $strategy The strategy name that should be used as a strategy in the escape call
* @param callable $callable A valid PHP callable
*/
public function setEscaper($strategy, $callable)
{
$this->escapers[$strategy] = $callable;
}
/**
* Gets all defined escapers.
*
* @return array An array of escapers
*/
public function getEscapers()
{
return $this->escapers;
}
/**
* Sets the default format to be used by the date filter.
@ -72,9 +95,9 @@ class Twig_Extension_Core extends Twig_Extension
/**
* Sets the default format to be used by the number_format filter.
*
* @param integer $decimal The number of decimal places to use.
* @param string $decimalPoint The character(s) to use for the decimal point.
* @param string $thousandSep The character(s) to use for the thousands separator.
* @param int $decimal The number of decimal places to use.
* @param string $decimalPoint The character(s) to use for the decimal point.
* @param string $thousandSep The character(s) to use for the thousands separator.
*/
public function setNumberFormat($decimal, $decimalPoint, $thousandSep)
{
@ -94,7 +117,7 @@ class Twig_Extension_Core extends Twig_Extension
/**
* Returns the token parser instance to add to the existing list.
*
* @return array An array of Twig_TokenParser instances
* @return Twig_TokenParser[] An array of Twig_TokenParser instances
*/
public function getTokenParsers()
{
@ -132,6 +155,7 @@ class Twig_Extension_Core extends Twig_Extension
new Twig_SimpleFilter('replace', 'strtr'),
new Twig_SimpleFilter('number_format', 'twig_number_format_filter', array('needs_environment' => true)),
new Twig_SimpleFilter('abs', 'abs'),
new Twig_SimpleFilter('round', 'twig_round'),
// encoding
new Twig_SimpleFilter('url_encode', 'twig_urlencode_filter'),
@ -149,7 +173,7 @@ class Twig_Extension_Core extends Twig_Extension
// array helpers
new Twig_SimpleFilter('join', 'twig_join_filter'),
new Twig_SimpleFilter('split', 'twig_split_filter'),
new Twig_SimpleFilter('split', 'twig_split_filter', array('needs_environment' => true)),
new Twig_SimpleFilter('sort', 'twig_sort_filter'),
new Twig_SimpleFilter('merge', 'twig_array_merge'),
new Twig_SimpleFilter('batch', 'twig_array_batch'),
@ -186,12 +210,15 @@ class Twig_Extension_Core extends Twig_Extension
public function getFunctions()
{
return array(
new Twig_SimpleFunction('max', 'max'),
new Twig_SimpleFunction('min', 'min'),
new Twig_SimpleFunction('range', 'range'),
new Twig_SimpleFunction('constant', 'twig_constant'),
new Twig_SimpleFunction('cycle', 'twig_cycle'),
new Twig_SimpleFunction('random', 'twig_random', array('needs_environment' => true)),
new Twig_SimpleFunction('date', 'twig_date_converter', array('needs_environment' => true)),
new Twig_SimpleFunction('include', 'twig_include', array('needs_environment' => true, 'needs_context' => true, 'is_safe' => array('all'))),
new Twig_SimpleFunction('source', 'twig_source', array('needs_environment' => true, 'is_safe' => array('all'))),
);
}
@ -207,9 +234,11 @@ class Twig_Extension_Core extends Twig_Extension
new Twig_SimpleTest('odd', null, array('node_class' => 'Twig_Node_Expression_Test_Odd')),
new Twig_SimpleTest('defined', null, array('node_class' => 'Twig_Node_Expression_Test_Defined')),
new Twig_SimpleTest('sameas', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')),
new Twig_SimpleTest('same as', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')),
new Twig_SimpleTest('none', null, array('node_class' => 'Twig_Node_Expression_Test_Null')),
new Twig_SimpleTest('null', null, array('node_class' => 'Twig_Node_Expression_Test_Null')),
new Twig_SimpleTest('divisibleby', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')),
new Twig_SimpleTest('divisible by', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')),
new Twig_SimpleTest('constant', null, array('node_class' => 'Twig_Node_Expression_Test_Constant')),
new Twig_SimpleTest('empty', 'twig_test_empty'),
new Twig_SimpleTest('iterable', 'twig_test_iterable'),
@ -230,65 +259,89 @@ class Twig_Extension_Core extends Twig_Extension
'+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'),
),
array(
'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'matches' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Matches', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'starts with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_StartsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'ends with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_EndsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
),
);
}
public function parseNotTestExpression(Twig_Parser $parser, $node)
public function parseNotTestExpression(Twig_Parser $parser, Twig_NodeInterface $node)
{
return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine());
}
public function parseTestExpression(Twig_Parser $parser, $node)
public function parseTestExpression(Twig_Parser $parser, Twig_NodeInterface $node)
{
$stream = $parser->getStream();
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
$name = $this->getTestName($parser, $node->getLine());
$class = $this->getTestNodeClass($parser, $name);
$arguments = null;
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$arguments = $parser->getExpressionParser()->parseArguments(true);
}
$class = $this->getTestNodeClass($parser, $name, $node->getLine());
return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine());
}
protected function getTestNodeClass(Twig_Parser $parser, $name, $line)
protected function getTestName(Twig_Parser $parser, $line)
{
$stream = $parser->getStream();
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
$env = $parser->getEnvironment();
$testMap = $env->getTests();
if (isset($testMap[$name])) {
return $name;
}
if ($stream->test(Twig_Token::NAME_TYPE)) {
// try 2-words tests
$name = $name.' '.$parser->getCurrentToken()->getValue();
if (isset($testMap[$name])) {
$parser->getStream()->next();
return $name;
}
}
$message = sprintf('The test "%s" does not exist', $name);
if ($alternatives = $env->computeAlternatives($name, array_keys($testMap))) {
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
}
throw new Twig_Error_Syntax($message, $line, $parser->getFilename());
}
protected function getTestNodeClass(Twig_Parser $parser, $name)
{
$env = $parser->getEnvironment();
$testMap = $env->getTests();
if (!isset($testMap[$name])) {
$message = sprintf('The test "%s" does not exist', $name);
if ($alternatives = $env->computeAlternatives($name, array_keys($env->getTests()))) {
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
}
throw new Twig_Error_Syntax($message, $line, $parser->getFilename());
}
if ($testMap[$name] instanceof Twig_SimpleTest) {
return $testMap[$name]->getNodeClass();
@ -312,7 +365,7 @@ class Twig_Extension_Core extends Twig_Extension
* Cycles over a value.
*
* @param ArrayAccess|array $values An array or an ArrayAccess instance
* @param integer $position The cycle position
* @param int $position The cycle position
*
* @return string The next value in the cycle
*/
@ -332,7 +385,7 @@ function twig_cycle($values, $position)
* - a random integer between 0 and the integer parameter
*
* @param Twig_Environment $env A Twig_Environment instance
* @param Traversable|array|integer|string $values The values to pick a random item from
* @param Traversable|array|int|string $values The values to pick a random item from
*
* @throws Twig_Error_Runtime When $values is an empty array (does not apply to an empty string which is returned as is).
*
@ -348,7 +401,7 @@ function twig_random(Twig_Environment $env, $values = null)
return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values);
}
if (is_object($values) && $values instanceof Traversable) {
if ($values instanceof Traversable) {
$values = iterator_to_array($values);
} elseif (is_string($values)) {
if ('' === $values) {
@ -391,10 +444,10 @@ function twig_random(Twig_Environment $env, $values = null)
* {{ post.published_at|date("m/d/Y") }}
* </pre>
*
* @param Twig_Environment $env A Twig_Environment instance
* @param DateTime|DateInterval|string $date A date
* @param string $format A format
* @param DateTimeZone|string $timezone A timezone
* @param Twig_Environment $env A Twig_Environment instance
* @param DateTime|DateTimeInterface|DateInterval|string $date A date
* @param string|null $format The target format, null to use the default
* @param DateTimeZone|string|null|false $timezone The target timezone, null to use the default, false to leave unchanged
*
* @return string The formatted date
*/
@ -428,9 +481,12 @@ function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $
function twig_date_modify_filter(Twig_Environment $env, $date, $modifier)
{
$date = twig_date_converter($env, $date, false);
$date->modify($modifier);
$resultDate = $date->modify($modifier);
return $date;
// This is a hack to ensure PHP 5.2 support and support for DateTimeImmutable
// DateTime::modify does not return the modified DateTime object < 5.3.0
// and DateTimeImmutable does not modify $date.
return null === $resultDate ? $date : $resultDate;
}
/**
@ -442,27 +498,32 @@ function twig_date_modify_filter(Twig_Environment $env, $date, $modifier)
* {% endif %}
* </pre>
*
* @param Twig_Environment $env A Twig_Environment instance
* @param DateTime|string $date A date
* @param DateTimeZone|string $timezone A timezone
* @param Twig_Environment $env A Twig_Environment instance
* @param DateTime|DateTimeInterface|string|null $date A date
* @param DateTimeZone|string|null|false $timezone The target timezone, null to use the default, false to leave unchanged
*
* @return DateTime A DateTime instance
*/
function twig_date_converter(Twig_Environment $env, $date = null, $timezone = null)
{
// determine the timezone
if (!$timezone) {
$defaultTimezone = $env->getExtension('core')->getTimezone();
} elseif (!$timezone instanceof DateTimeZone) {
$defaultTimezone = new DateTimeZone($timezone);
} else {
$defaultTimezone = $timezone;
if (false !== $timezone) {
if (null === $timezone) {
$timezone = $env->getExtension('core')->getTimezone();
} elseif (!$timezone instanceof DateTimeZone) {
$timezone = new DateTimeZone($timezone);
}
}
if ($date instanceof DateTime) {
// immutable dates
if ($date instanceof DateTimeImmutable) {
return false !== $timezone ? $date->setTimezone($timezone) : $date;
}
if ($date instanceof DateTime || $date instanceof DateTimeInterface) {
$date = clone $date;
if (false !== $timezone) {
$date->setTimezone($defaultTimezone);
$date->setTimezone($timezone);
}
return $date;
@ -473,14 +534,36 @@ function twig_date_converter(Twig_Environment $env, $date = null, $timezone = nu
$date = '@'.$date;
}
$date = new DateTime($date, $defaultTimezone);
$date = new DateTime($date, $env->getExtension('core')->getTimezone());
if (false !== $timezone) {
$date->setTimezone($defaultTimezone);
$date->setTimezone($timezone);
}
return $date;
}
/**
* Rounds a number.
*
* @param int|float $value The value to round
* @param int|float $precision The rounding precision
* @param string $method The method to use for rounding
*
* @return int|float The rounded number
*/
function twig_round($value, $precision = 0, $method = 'common')
{
if ('common' == $method) {
return round($value, $precision);
}
if ('ceil' != $method && 'floor' != $method) {
throw new Twig_Error_Runtime('The round filter only supports the "common", "ceil", and "floor" methods.');
}
return $method($value * pow(10, $precision)) / pow(10, $precision);
}
/**
* Number format filter.
*
@ -490,7 +573,7 @@ function twig_date_converter(Twig_Environment $env, $date = null, $timezone = nu
*
* @param Twig_Environment $env A Twig_Environment instance
* @param mixed $number A float/int/string of the number to format
* @param integer $decimal The number of decimal points to display.
* @param int $decimal The number of decimal points to display.
* @param string $decimalPoint The character(s) to use for the decimal point.
* @param string $thousandSep The character(s) to use for the thousands separator.
*
@ -515,32 +598,31 @@ function twig_number_format_filter(Twig_Environment $env, $number, $decimal = nu
}
/**
* URL encodes a string as a path segment or an array as a query string.
* URL encodes (RFC 3986) a string as a path segment or an array as a query string.
*
* @param string|array $url A URL or an array of query parameters
* @param bool $raw true to use rawurlencode() instead of urlencode
*
* @return string The URL encoded value
*/
function twig_urlencode_filter($url, $raw = false)
function twig_urlencode_filter($url)
{
if (is_array($url)) {
if (defined('PHP_QUERY_RFC3986')) {
return http_build_query($url, '', '&', PHP_QUERY_RFC3986);
}
return http_build_query($url, '', '&');
}
if ($raw) {
return rawurlencode($url);
}
return urlencode($url);
return rawurlencode($url);
}
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
if (PHP_VERSION_ID < 50300) {
/**
* JSON encodes a variable.
*
* @param mixed $value The value to encode.
* @param integer $options Not used on PHP 5.2.x
* @param mixed $value The value to encode.
* @param int $options Not used on PHP 5.2.x
*
* @return mixed The JSON encoded value
*/
@ -558,8 +640,8 @@ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
/**
* JSON encodes a variable.
*
* @param mixed $value The value to encode.
* @param integer $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT
* @param mixed $value The value to encode.
* @param int $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT
*
* @return mixed The JSON encoded value
*/
@ -601,7 +683,7 @@ function _twig_markup2string(&$value)
function twig_array_merge($arr1, $arr2)
{
if (!is_array($arr1) || !is_array($arr2)) {
throw new Twig_Error_Runtime('The merge filter only works with arrays or hashes.');
throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or hashes; %s and %s given.', gettype($arr1), gettype($arr2)));
}
return array_merge($arr1, $arr2);
@ -612,16 +694,28 @@ function twig_array_merge($arr1, $arr2)
*
* @param Twig_Environment $env A Twig_Environment instance
* @param mixed $item A variable
* @param integer $start Start of the slice
* @param integer $length Size of the slice
* @param Boolean $preserveKeys Whether to preserve key or not (when the input is an array)
* @param int $start Start of the slice
* @param int $length Size of the slice
* @param bool $preserveKeys Whether to preserve key or not (when the input is an array)
*
* @return mixed The sliced variable
*/
function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false)
{
if (is_object($item) && $item instanceof Traversable) {
$item = iterator_to_array($item, false);
if ($item instanceof Traversable) {
if ($item instanceof IteratorAggregate) {
$item = $item->getIterator();
}
if ($start >= 0 && $length >= 0 && $item instanceof Iterator) {
try {
return iterator_to_array(new LimitIterator($item, $start, $length === null ? -1 : $length), $preserveKeys);
} catch (OutOfBoundsException $exception) {
return array();
}
}
$item = iterator_to_array($item, $preserveKeys);
}
if (is_array($item)) {
@ -631,10 +725,10 @@ function twig_slice(Twig_Environment $env, $item, $start, $length = null, $prese
$item = (string) $item;
if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) {
return mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset);
return (string) mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset);
}
return null === $length ? substr($item, $start) : substr($item, $start, $length);
return (string) (null === $length ? substr($item, $start) : substr($item, $start, $length));
}
/**
@ -649,7 +743,7 @@ function twig_first(Twig_Environment $env, $item)
{
$elements = twig_slice($env, $item, 0, 1, false);
return is_string($elements) ? $elements[0] : current($elements);
return is_string($elements) ? $elements : current($elements);
}
/**
@ -664,7 +758,7 @@ function twig_last(Twig_Environment $env, $item)
{
$elements = twig_slice($env, $item, -1, 1, false);
return is_string($elements) ? $elements[0] : current($elements);
return is_string($elements) ? $elements : current($elements);
}
/**
@ -687,7 +781,7 @@ function twig_last(Twig_Environment $env, $item)
*/
function twig_join_filter($value, $glue = '')
{
if (is_object($value) && $value instanceof Traversable) {
if ($value instanceof Traversable) {
$value = iterator_to_array($value, false);
}
@ -713,17 +807,35 @@ function twig_join_filter($value, $glue = '')
*
* @param string $value A string
* @param string $delimiter The delimiter
* @param integer $limit The limit
* @param int $limit The limit
*
* @return array The split string as an array
*/
function twig_split_filter($value, $delimiter, $limit = null)
function twig_split_filter(Twig_Environment $env, $value, $delimiter, $limit = null)
{
if (empty($delimiter)) {
if (!empty($delimiter)) {
return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit);
}
if (!function_exists('mb_get_info') || null === $charset = $env->getCharset()) {
return str_split($value, null === $limit ? 1 : $limit);
}
return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit);
if ($limit <= 1) {
return preg_split('/(?<!^)(?!$)/u', $value);
}
$length = mb_strlen($value, $charset);
if ($length < $limit) {
return array($value);
}
$r = array();
for ($i = 0; $i < $length; $i += $limit) {
$r[] = mb_substr($value, $i, $limit, $charset);
}
return $r;
}
// The '_default' filter is used internally to avoid using the ternary operator
@ -771,7 +883,7 @@ function twig_get_array_keys_filter($array)
*
* @param Twig_Environment $env A Twig_Environment instance
* @param array|Traversable|string $item An array, a Traversable instance, or a string
* @param Boolean $preserveKeys Whether to preserve key or not
* @param bool $preserveKeys Whether to preserve key or not
*
* @return mixed The reversed input
*/
@ -822,15 +934,11 @@ function twig_sort_filter($array)
function twig_in_filter($value, $compare)
{
if (is_array($compare)) {
return in_array($value, $compare, is_object($value));
} elseif (is_string($compare)) {
if (!strlen($value)) {
return empty($compare);
}
return false !== strpos($compare, (string) $value);
} elseif (is_object($compare) && $compare instanceof Traversable) {
return in_array($value, iterator_to_array($compare, false), is_object($value));
return in_array($value, $compare, is_object($value) || is_resource($value));
} elseif (is_string($compare) && (is_string($value) || is_int($value) || is_float($value))) {
return '' === $value || false !== strpos($compare, (string) $value);
} elseif ($compare instanceof Traversable) {
return in_array($value, iterator_to_array($compare, false), is_object($value) || is_resource($value));
}
return false;
@ -843,7 +951,7 @@ function twig_in_filter($value, $compare)
* @param string $string The value to be escaped
* @param string $strategy The escaping strategy
* @param string $charset The charset
* @param Boolean $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
* @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
*/
function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
{
@ -870,22 +978,30 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html',
// Using a static variable to avoid initializing the array
// each time the function is called. Moving the declaration on the
// top of the function slow downs other escaping strategies.
static $htmlspecialcharsCharsets = array(
'ISO-8859-1' => true, 'ISO8859-1' => true,
'ISO-8859-15' => true, 'ISO8859-15' => true,
'utf-8' => true, 'UTF-8' => true,
'CP866' => true, 'IBM866' => true, '866' => true,
'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
'1251' => true,
'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
'BIG5' => true, '950' => true,
'GB2312' => true, '936' => true,
'BIG5-HKSCS' => true,
'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
'EUC-JP' => true, 'EUCJP' => true,
'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
);
static $htmlspecialcharsCharsets;
if (null === $htmlspecialcharsCharsets) {
if (defined('HHVM_VERSION')) {
$htmlspecialcharsCharsets = array('utf-8' => true, 'UTF-8' => true);
} else {
$htmlspecialcharsCharsets = array(
'ISO-8859-1' => true, 'ISO8859-1' => true,
'ISO-8859-15' => true, 'ISO8859-15' => true,
'utf-8' => true, 'UTF-8' => true,
'CP866' => true, 'IBM866' => true, '866' => true,
'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
'1251' => true,
'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
'BIG5' => true, '950' => true,
'GB2312' => true, '936' => true,
'BIG5-HKSCS' => true,
'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
'EUC-JP' => true, 'EUCJP' => true,
'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
);
}
}
if (isset($htmlspecialcharsCharsets[$charset])) {
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
@ -957,16 +1073,26 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html',
return $string;
case 'url':
// hackish test to avoid version_compare that is much slower, this works unless PHP releases a 5.10.*
// at that point however PHP 5.2.* support can be removed
if (PHP_VERSION < '5.3.0') {
if (PHP_VERSION_ID < 50300) {
return str_replace('%7E', '~', rawurlencode($string));
}
return rawurlencode($string);
default:
throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: html, js, url, css, and html_attr).', $strategy));
static $escapers;
if (null === $escapers) {
$escapers = $env->getExtension('core')->getEscapers();
}
if (isset($escapers[$strategy])) {
return call_user_func($escapers[$strategy], $env, $string, $charset);
}
$validStrategies = implode(', ', array_merge(array('html', 'js', 'url', 'css', 'html_attr'), array_keys($escapers)));
throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
}
}
@ -1088,7 +1214,6 @@ function _twig_escape_html_attr_callback($matches)
* Per OWASP recommendations, we'll use hex entities for any other
* characters where a named entity does not exist.
*/
return sprintf('&#x%s;', $hex);
}
@ -1100,7 +1225,7 @@ if (function_exists('mb_get_info')) {
* @param Twig_Environment $env A Twig_Environment instance
* @param mixed $thing A variable
*
* @return integer The length of the value
* @return int The length of the value
*/
function twig_length_filter(Twig_Environment $env, $thing)
{
@ -1184,7 +1309,7 @@ else {
* @param Twig_Environment $env A Twig_Environment instance
* @param mixed $thing A variable
*
* @return integer The length of the value
* @return int The length of the value
*/
function twig_length_filter(Twig_Environment $env, $thing)
{
@ -1240,7 +1365,7 @@ function twig_ensure_traversable($seq)
*
* @param mixed $value A variable
*
* @return Boolean true if the value is empty, false otherwise
* @return bool true if the value is empty, false otherwise
*/
function twig_test_empty($value)
{
@ -1263,7 +1388,7 @@ function twig_test_empty($value)
*
* @param mixed $value A variable
*
* @return Boolean true if the value is traversable
* @return bool true if the value is traversable
*/
function twig_test_iterable($value)
{
@ -1273,16 +1398,18 @@ function twig_test_iterable($value)
/**
* Renders a template.
*
* @param string $template The template to render
* @param array $variables The variables to pass to the template
* @param Boolean $with_context Whether to pass the current context variables or not
* @param Boolean $ignore_missing Whether to ignore missing templates or not
* @param Boolean $sandboxed Whether to sandbox the template or not
* @param string|array $template The template to render or an array of templates to try consecutively
* @param array $variables The variables to pass to the template
* @param bool $with_context Whether to pass the current context variables or not
* @param bool $ignore_missing Whether to ignore missing templates or not
* @param bool $sandboxed Whether to sandbox the template or not
*
* @return string The rendered template
*/
function twig_include(Twig_Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false)
{
$alreadySandboxed = false;
$sandbox = null;
if ($withContext) {
$variables = array_merge($context, $variables);
}
@ -1307,6 +1434,18 @@ function twig_include(Twig_Environment $env, $context, $template, $variables = a
}
}
/**
* Returns a template content without rendering it.
*
* @param string $name The template name
*
* @return string The template source
*/
function twig_source(Twig_Environment $env, $name)
{
return $env->getLoader()->getSource($name);
}
/**
* Provides the ability to get constants from instances as well as class/global constants.
*
@ -1328,14 +1467,14 @@ function twig_constant($constant, $object = null)
* Batches item.
*
* @param array $items An array of items
* @param integer $size The size of the batch
* @param int $size The size of the batch
* @param mixed $fill A value used to fill missing items
*
* @return array
*/
function twig_array_batch($items, $size, $fill = null)
{
if (is_object($items) && $items instanceof Traversable) {
if ($items instanceof Traversable) {
$items = iterator_to_array($items, false);
}

View File

@ -30,7 +30,7 @@ class Twig_Extension_Escaper extends Twig_Extension
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors()
{
@ -64,6 +64,10 @@ class Twig_Extension_Escaper extends Twig_Extension
$defaultStrategy = 'html';
}
if ('filename' === $defaultStrategy) {
$defaultStrategy = array('Twig_FileExtensionEscapingStrategy', 'guess');
}
$this->defaultStrategy = $defaultStrategy;
}

View File

@ -0,0 +1,52 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2015 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Extension_Profiler extends Twig_Extension
{
private $actives;
public function __construct(Twig_Profiler_Profile $profile)
{
$this->actives = array($profile);
}
public function enter(Twig_Profiler_Profile $profile)
{
$this->actives[0]->addProfile($profile);
array_unshift($this->actives, $profile);
}
public function leave(Twig_Profiler_Profile $profile)
{
$profile->leave();
array_shift($this->actives);
if (1 === count($this->actives)) {
$this->actives[0]->leave();
}
}
/**
* {@inheritdoc}
*/
public function getNodeVisitors()
{
return array(new Twig_Profiler_NodeVisitor_Profiler($this->getName()));
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'profiler';
}
}

View File

@ -33,7 +33,7 @@ class Twig_Extension_Sandbox extends Twig_Extension
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors()
{
@ -93,7 +93,7 @@ class Twig_Extension_Sandbox extends Twig_Extension
public function ensureToStringAllowed($obj)
{
if (is_object($obj)) {
if ($this->isSandboxed() && is_object($obj)) {
$this->policy->checkMethodAllowed($obj, '__toString');
}

View File

@ -43,22 +43,5 @@ class Twig_Extension_StringLoader extends Twig_Extension
*/
function twig_template_from_string(Twig_Environment $env, $template)
{
$name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), true), false));
$loader = new Twig_Loader_Chain(array(
new Twig_Loader_Array(array($name => $template)),
$current = $env->getLoader(),
));
$env->setLoader($loader);
try {
$template = $env->loadTemplate($name);
} catch (Exception $e) {
$env->setLoader($current);
throw $e;
}
$env->setLoader($current);
return $template;
return $env->createTemplate($template);
}

View File

@ -35,7 +35,7 @@ interface Twig_ExtensionInterface
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors();

View File

@ -17,6 +17,7 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
new Twig_SimpleFilter('extension', 'twig_extension_filter'),
new Twig_SimpleFilter('sprintf', 'sprintf'),
new Twig_SimpleFilter('capcode', 'capcode'),
new Twig_SimpleFilter('remove_modifiers', 'remove_modifiers'),
new Twig_SimpleFilter('hasPermission', 'twig_hasPermission_filter'),
new Twig_SimpleFilter('date', 'twig_date_filter'),
new Twig_SimpleFilter('remove_whitespace', 'twig_remove_whitespace_filter'),
@ -132,6 +133,6 @@ function twig_secure_link_confirm($text, $title, $confirm_message, $href) {
function twig_secure_link($href) {
return $href . '/' . make_secure_link_token($href);
}
function twig_less_ip($ip) {
return less_ip($ip);
function twig_less_ip($ip, $board = '') {
return less_ip($ip, $board);
}

View File

@ -0,0 +1,49 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2015 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Default autoescaping strategy based on file names.
*
* This strategy sets the HTML as the default autoescaping strategy,
* but changes it based on the filename.
*
* Note that there is no runtime performance impact as the
* default autoescaping strategy is set at compilation time.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_FileExtensionEscapingStrategy
{
/**
* Guesses the best autoescaping strategy based on the file name.
*
* @param string $filename The template file name
*
* @return string The escaping strategy name to use
*/
public static function guess($filename)
{
if (!preg_match('{\.(js|css|txt)(?:\.[^/\\\\]+)?$}', $filename, $match)) {
return 'html';
}
switch ($match[1]) {
case 'js':
return 'js';
case 'css':
return 'css';
case 'txt':
return false;
}
}
}

31
inc/lib/Twig/LICENSE Normal file
View File

@ -0,0 +1,31 @@
Copyright (c) 2009-2014 by the Twig Team.
Some rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -73,18 +73,15 @@ class Twig_Lexer implements Twig_LexerInterface
}
/**
* Tokenizes a source code.
*
* @param string $code The source code
* @param string $filename A unique identifier for the source code
*
* @return Twig_TokenStream A token stream instance
* {@inheritdoc}
*/
public function tokenize($code, $filename = null)
{
if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
} else {
$mbEncoding = null;
}
$this->code = str_replace(array("\r\n", "\r"), "\n", $code);
@ -135,7 +132,7 @@ class Twig_Lexer implements Twig_LexerInterface
throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
}
if (isset($mbEncoding)) {
if ($mbEncoding) {
mb_internal_encoding($mbEncoding);
}
@ -233,7 +230,7 @@ class Twig_Lexer implements Twig_LexerInterface
// operators
if (preg_match($this->regexes['operator'], $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]);
$this->pushToken(Twig_Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0]));
$this->moveCursor($match[0]);
}
// names
@ -320,13 +317,10 @@ class Twig_Lexer implements Twig_LexerInterface
$this->pushToken(Twig_Token::INTERPOLATION_START_TYPE);
$this->moveCursor($match[0]);
$this->pushState(self::STATE_INTERPOLATION);
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, null, $this->cursor) && strlen($match[0]) > 0) {
$this->pushToken(Twig_Token::STRING_TYPE, stripcslashes($match[0]));
$this->moveCursor($match[0]);
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) {
list($expect, $lineno) = array_pop($this->brackets);
if ($this->code[$this->cursor] != '"') {
throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
@ -382,10 +376,15 @@ class Twig_Lexer implements Twig_LexerInterface
// an operator that ends with a character must be followed by
// a whitespace or a parenthesis
if (ctype_alpha($operator[$length - 1])) {
$regex[] = preg_quote($operator, '/').'(?=[\s()])';
$r = preg_quote($operator, '/').'(?=[\s()])';
} else {
$regex[] = preg_quote($operator, '/');
$r = preg_quote($operator, '/');
}
// an operator with a space can be any amount of whitespaces
$r = preg_replace('/\s+/', '\s+', $r);
$regex[] = $r;
}
return '/'.implode('|', $regex).'/A';

View File

@ -13,7 +13,8 @@
* Interface implemented by lexer classes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*
* @deprecated since 1.12 (to be removed in 3.0)
*/
interface Twig_LexerInterface
{
@ -24,6 +25,8 @@ interface Twig_LexerInterface
* @param string $filename A unique identifier for the source code
*
* @return Twig_TokenStream A token stream instance
*
* @throws Twig_Error_Syntax When the code is syntactically wrong
*/
public function tokenize($code, $filename = null);
}

View File

@ -17,6 +17,8 @@
* source code of the template). If you don't want to see your cache grows out of
* control, you need to take care of clearing the old cache file by yourself.
*
* This loader should only be used for unit testing.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface

View File

@ -143,7 +143,8 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
*/
public function exists($name)
{
$name = (string) $name;
$name = $this->normalizeName($name);
if (isset($this->cache[$name])) {
return true;
}
@ -167,10 +168,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
protected function findTemplate($name)
{
$name = (string) $name;
// normalize name
$name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/'));
$name = $this->normalizeName($name);
if (isset($this->cache[$name])) {
return $this->cache[$name];
@ -178,16 +176,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
$this->validateName($name);
$namespace = self::MAIN_NAMESPACE;
$shortname = $name;
if (isset($name[0]) && '@' == $name[0]) {
if (false === $pos = strpos($name, '/')) {
throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
}
$namespace = substr($name, 1, $pos - 1);
$shortname = substr($name, $pos + 1);
}
list($namespace, $shortname) = $this->parseName($name);
if (!isset($this->paths[$namespace])) {
throw new Twig_Error_Loader(sprintf('There are no registered paths for namespace "%s".', $namespace));
@ -195,6 +184,10 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
foreach ($this->paths[$namespace] as $path) {
if (is_file($path.'/'.$shortname)) {
if (false !== $realpath = realpath($path.'/'.$shortname)) {
return $this->cache[$name] = $realpath;
}
return $this->cache[$name] = $path.'/'.$shortname;
}
}
@ -202,6 +195,27 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])));
}
protected function parseName($name, $default = self::MAIN_NAMESPACE)
{
if (isset($name[0]) && '@' == $name[0]) {
if (false === $pos = strpos($name, '/')) {
throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
}
$namespace = substr($name, 1, $pos - 1);
$shortname = substr($name, $pos + 1);
return array($namespace, $shortname);
}
return array($default, $name);
}
protected function normalizeName($name)
{
return preg_replace('#/{2,}#', '/', strtr((string) $name, '\\', '/'));
}
protected function validateName($name)
{
if (false !== strpos($name, "\0")) {

View File

@ -12,15 +12,15 @@
/**
* Loads a template from a string.
*
* This loader should only be used for unit testing as it has many limitations
* (for instance, the include or extends tag does not make any sense for a string
* loader).
* This loader should NEVER be used. It only exists for Twig internal purposes.
*
* When using this loader with a cache mechanism, you should know that a new cache
* key is generated each time a template content "changes" (the cache key being the
* source code of the template). If you don't want to see your cache grows out of
* control, you need to take care of clearing the old cache file by yourself.
*
* @deprecated since 1.18.1 (to be removed in 2.0)
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Loader_String implements Twig_LoaderInterface, Twig_ExistsLoaderInterface

View File

@ -44,7 +44,7 @@ interface Twig_LoaderInterface
* @param string $name The template name
* @param timestamp $time The last modification time of the cached template
*
* @return Boolean true if the template is fresh, false otherwise
* @return bool true if the template is fresh, false otherwise
*
* @throws Twig_Error_Loader When $name is not found
*/

View File

@ -28,10 +28,10 @@ class Twig_Node implements Twig_NodeInterface
* The nodes are automatically made available as properties ($this->node).
* The attributes are automatically made available as array items ($this['name']).
*
* @param array $nodes An array of named nodes
* @param array $attributes An array of attributes (should not be nodes)
* @param integer $lineno The line number
* @param string $tag The tag name associated with the Node
* @param array $nodes An array of named nodes
* @param array $attributes An array of attributes (should not be nodes)
* @param int $lineno The line number
* @param string $tag The tag name associated with the Node
*/
public function __construct(array $nodes = array(), array $attributes = array(), $lineno = 0, $tag = null)
{
@ -69,6 +69,9 @@ class Twig_Node implements Twig_NodeInterface
return implode("\n", $repr);
}
/**
* @deprecated since 1.16.1 (to be removed in 2.0)
*/
public function toXml($asDom = false)
{
$dom = new DOMDocument('1.0', 'UTF-8');
@ -121,7 +124,7 @@ class Twig_Node implements Twig_NodeInterface
*
* @param string The attribute name
*
* @return Boolean true if the attribute is defined, false otherwise
* @return bool true if the attribute is defined, false otherwise
*/
public function hasAttribute($name)
{
@ -170,7 +173,7 @@ class Twig_Node implements Twig_NodeInterface
*
* @param string The node name
*
* @return Boolean true if the node with the given name exists, false otherwise
* @return bool true if the node with the given name exists, false otherwise
*/
public function hasNode($name)
{

View File

@ -30,7 +30,7 @@ class Twig_Node_AutoEscape extends Twig_Node
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -25,7 +25,7 @@ class Twig_Node_Block extends Twig_Node
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -25,7 +25,7 @@ class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInter
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -0,0 +1,78 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2015 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_CheckSecurity extends Twig_Node
{
protected $usedFilters;
protected $usedTags;
protected $usedFunctions;
public function __construct(array $usedFilters, array $usedTags, array $usedFunctions)
{
$this->usedFilters = $usedFilters;
$this->usedTags = $usedTags;
$this->usedFunctions = $usedFunctions;
parent::__construct();
}
public function compile(Twig_Compiler $compiler)
{
$tags = $filters = $functions = array();
foreach (array('tags', 'filters', 'functions') as $type) {
foreach ($this->{'used'.ucfirst($type)} as $name => $node) {
if ($node instanceof Twig_Node) {
${$type}[$name] = $node->getLine();
} else {
${$type}[$node] = null;
}
}
}
$compiler
->write("\$tags = ")->repr(array_filter($tags))->raw(";\n")
->write("\$filters = ")->repr(array_filter($filters))->raw(";\n")
->write("\$functions = ")->repr(array_filter($functions))->raw(";\n\n")
->write("try {\n")
->indent()
->write("\$this->env->getExtension('sandbox')->checkSecurity(\n")
->indent()
->write(!$tags ? "array(),\n" : "array('".implode("', '", array_keys($tags))."'),\n")
->write(!$filters ? "array(),\n" : "array('".implode("', '", array_keys($filters))."'),\n")
->write(!$functions ? "array()\n" : "array('".implode("', '", array_keys($functions))."')\n")
->outdent()
->write(");\n")
->outdent()
->write("} catch (Twig_Sandbox_SecurityError \$e) {\n")
->indent()
->write("\$e->setTemplateFile(\$this->getTemplateName());\n\n")
->write("if (\$e instanceof Twig_Sandbox_SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n")
->indent()
->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n")
->outdent()
->write("} elseif (\$e instanceof Twig_Sandbox_SecurityNotAllowedFilterError && isset(\$filters[\$e->getFilterName()])) {\n")
->indent()
->write("\$e->setTemplateLine(\$filters[\$e->getFilterName()]);\n")
->outdent()
->write("} elseif (\$e instanceof Twig_Sandbox_SecurityNotAllowedFunctionError && isset(\$functions[\$e->getFunctionName()])) {\n")
->indent()
->write("\$e->setTemplateLine(\$functions[\$e->getFunctionName()]);\n")
->outdent()
->write("}\n\n")
->write("throw \$e;\n")
->outdent()
->write("}\n\n")
;
}
}

View File

@ -24,7 +24,7 @@ class Twig_Node_Do extends Twig_Node
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -28,9 +28,13 @@ class Twig_Node_Embed extends Twig_Node_Include
protected function addGetTemplate(Twig_Compiler $compiler)
{
$compiler
->write("\$this->env->loadTemplate(")
->write("\$this->loadTemplate(")
->string($this->getAttribute('filename'))
->raw(', ')
->repr($compiler->getFilename())
->raw(', ')
->repr($this->getLine())
->raw(', ')
->string($this->getAttribute('index'))
->raw(")")
;

View File

@ -63,7 +63,7 @@ class Twig_Node_Expression_Array extends Twig_Node_Expression
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -15,7 +15,7 @@ class Twig_Node_Expression_AssignName extends Twig_Node_Expression_Name
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -19,7 +19,7 @@ abstract class Twig_Node_Expression_Binary extends Twig_Node_Expression
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2013 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_Expression_Binary_EndsWith extends Twig_Node_Expression_Binary
{
public function compile(Twig_Compiler $compiler)
{
$left = $compiler->getVarName();
$right = $compiler->getVarName();
$compiler
->raw(sprintf('(is_string($%s = ', $left))
->subcompile($this->getNode('left'))
->raw(sprintf(') && is_string($%s = ', $right))
->subcompile($this->getNode('right'))
->raw(sprintf(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right))
;
}
public function operator(Twig_Compiler $compiler)
{
return $compiler->raw('');
}
}

View File

@ -13,7 +13,7 @@ class Twig_Node_Expression_Binary_FloorDiv extends Twig_Node_Expression_Binary
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -13,7 +13,7 @@ class Twig_Node_Expression_Binary_In extends Twig_Node_Expression_Binary
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -0,0 +1,28 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2013 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_Expression_Binary_Matches extends Twig_Node_Expression_Binary
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('preg_match(')
->subcompile($this->getNode('right'))
->raw(', ')
->subcompile($this->getNode('left'))
->raw(')')
;
}
public function operator(Twig_Compiler $compiler)
{
return $compiler->raw('');
}
}

View File

@ -13,7 +13,7 @@ class Twig_Node_Expression_Binary_NotIn extends Twig_Node_Expression_Binary
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -13,7 +13,7 @@ class Twig_Node_Expression_Binary_Power extends Twig_Node_Expression_Binary
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -13,7 +13,7 @@ class Twig_Node_Expression_Binary_Range extends Twig_Node_Expression_Binary
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2013 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_Expression_Binary_StartsWith extends Twig_Node_Expression_Binary
{
public function compile(Twig_Compiler $compiler)
{
$left = $compiler->getVarName();
$right = $compiler->getVarName();
$compiler
->raw(sprintf('(is_string($%s = ', $left))
->subcompile($this->getNode('left'))
->raw(sprintf(') && is_string($%s = ', $right))
->subcompile($this->getNode('right'))
->raw(sprintf(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right))
;
}
public function operator(Twig_Compiler $compiler)
{
return $compiler->raw('');
}
}

View File

@ -25,7 +25,7 @@ class Twig_Node_Expression_BlockReference extends Twig_Node_Expression
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -12,10 +12,8 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
{
protected function compileCallable(Twig_Compiler $compiler)
{
$callable = $this->getAttribute('callable');
$closingParenthesis = false;
if ($callable) {
if ($this->hasAttribute('callable') && $callable = $this->getAttribute('callable')) {
if (is_string($callable)) {
$compiler->raw($callable);
} elseif (is_array($callable) && $callable[0] instanceof Twig_ExtensionInterface) {
@ -92,6 +90,9 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
protected function getArguments($callable, $arguments)
{
$callType = $this->getAttribute('type');
$callName = $this->getAttribute('name');
$parameters = array();
$named = false;
foreach ($arguments as $name => $node) {
@ -99,7 +100,7 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
$named = true;
$name = $this->normalizeName($name);
} elseif ($named) {
throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name')));
throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName));
}
$parameters[$name] = $node;
@ -110,7 +111,7 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
}
if (!$callable) {
throw new LogicException(sprintf('Named arguments are not supported for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name')));
throw new LogicException(sprintf('Named arguments are not supported for %s "%s".', $callType, $callName));
}
// manage named arguments
@ -119,6 +120,8 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
} elseif (is_object($callable) && !$callable instanceof Closure) {
$r = new ReflectionObject($callable);
$r = $r->getMethod('__invoke');
} elseif (is_string($callable) && false !== strpos($callable, '::')) {
$r = new ReflectionMethod($callable);
} else {
$r = new ReflectionFunction($callable);
}
@ -140,32 +143,61 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
}
$arguments = array();
$names = array();
$missingArguments = array();
$optionalArguments = array();
$pos = 0;
foreach ($definition as $param) {
$name = $this->normalizeName($param->name);
$names[] = $name = $this->normalizeName($param->name);
if (array_key_exists($name, $parameters)) {
if (array_key_exists($pos, $parameters)) {
throw new Twig_Error_Syntax(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
throw new Twig_Error_Syntax(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName));
}
if (!empty($missingArguments)) {
throw new Twig_Error_Syntax(sprintf(
'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".',
$name, $callType, $callName, implode(', ', $names), count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments))
);
}
$arguments = array_merge($arguments, $optionalArguments);
$arguments[] = $parameters[$name];
unset($parameters[$name]);
$optionalArguments = array();
} elseif (array_key_exists($pos, $parameters)) {
$arguments = array_merge($arguments, $optionalArguments);
$arguments[] = $parameters[$pos];
unset($parameters[$pos]);
$optionalArguments = array();
++$pos;
} elseif ($param->isDefaultValueAvailable()) {
$arguments[] = new Twig_Node_Expression_Constant($param->getDefaultValue(), -1);
$optionalArguments[] = new Twig_Node_Expression_Constant($param->getDefaultValue(), -1);
} elseif ($param->isOptional()) {
break;
if (empty($parameters)) {
break;
} else {
$missingArguments[] = $name;
}
} else {
throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName));
}
}
if (!empty($parameters)) {
throw new Twig_Error_Syntax(sprintf('Unknown argument%s "%s" for %s "%s".', count($parameters) > 1 ? 's' : '' , implode('", "', array_keys($parameters)), $this->getAttribute('type'), $this->getAttribute('name')));
$unknownParameter = null;
foreach ($parameters as $parameter) {
if ($parameter instanceof Twig_Node) {
$unknownParameter = $parameter;
break;
}
}
throw new Twig_Error_Syntax(sprintf(
'Unknown argument%s "%s" for %s "%s(%s)".',
count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names)
), $unknownParameter ? $unknownParameter->getLine() : -1);
}
return $arguments;

View File

@ -24,7 +24,7 @@ class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -11,7 +11,7 @@
*/
class Twig_Node_Expression_GetAttr extends Twig_Node_Expression
{
public function __construct(Twig_Node_Expression $node, Twig_Node_Expression $attribute, Twig_Node_Expression_Array $arguments, $type, $lineno)
public function __construct(Twig_Node_Expression $node, Twig_Node_Expression $attribute, Twig_Node_Expression $arguments = null, $type, $lineno)
{
parent::__construct(array('node' => $node, 'attribute' => $attribute, 'arguments' => $arguments), array('type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false), $lineno);
}
@ -32,20 +32,30 @@ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression
$compiler->raw(', ')->subcompile($this->getNode('attribute'));
if (count($this->getNode('arguments')) || Twig_Template::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
$compiler->raw(', ')->subcompile($this->getNode('arguments'));
// only generate optional arguments when needed (to make generated code more readable)
$needFourth = $this->getAttribute('ignore_strict_check');
$needThird = $needFourth || $this->getAttribute('is_defined_test');
$needSecond = $needThird || Twig_Template::ANY_CALL !== $this->getAttribute('type');
$needFirst = $needSecond || null !== $this->getNode('arguments');
if (Twig_Template::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
$compiler->raw(', ')->repr($this->getAttribute('type'));
if ($needFirst) {
if (null !== $this->getNode('arguments')) {
$compiler->raw(', ')->subcompile($this->getNode('arguments'));
} else {
$compiler->raw(', array()');
}
}
if ($this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
$compiler->raw(', '.($this->getAttribute('is_defined_test') ? 'true' : 'false'));
}
if ($needSecond) {
$compiler->raw(', ')->repr($this->getAttribute('type'));
}
if ($this->getAttribute('ignore_strict_check')) {
$compiler->raw(', '.($this->getAttribute('ignore_strict_check') ? 'true' : 'false'));
}
if ($needThird) {
$compiler->raw(', ')->repr($this->getAttribute('is_defined_test'));
}
if ($needFourth) {
$compiler->raw(', ')->repr($this->getAttribute('ignore_strict_check'));
}
$compiler->raw(')');

View File

@ -1,60 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a macro call node.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class Twig_Node_Expression_MacroCall extends Twig_Node_Expression
{
public function __construct(Twig_Node_Expression $template, $name, Twig_Node_Expression_Array $arguments, $lineno)
{
parent::__construct(array('template' => $template, 'arguments' => $arguments), array('name' => $name), $lineno);
}
public function compile(Twig_Compiler $compiler)
{
$namedNames = array();
$namedCount = 0;
$positionalCount = 0;
foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
$name = $pair['key']->getAttribute('value');
if (!is_int($name)) {
$namedCount++;
$namedNames[$name] = 1;
} elseif ($namedCount > 0) {
throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for macro "%s".', $this->getAttribute('name')), $this->lineno);
} else {
$positionalCount++;
}
}
$compiler
->raw('$this->callMacro(')
->subcompile($this->getNode('template'))
->raw(', ')->repr($this->getAttribute('name'))
->raw(', ')->subcompile($this->getNode('arguments'))
;
if ($namedCount > 0) {
$compiler
->raw(', ')->repr($namedNames)
->raw(', ')->repr($namedCount)
->raw(', ')->repr($positionalCount)
;
}
$compiler
->raw(')')
;
}
}

View File

@ -26,6 +26,8 @@ class Twig_Node_Expression_Name extends Twig_Node_Expression
{
$name = $this->getAttribute('name');
$compiler->addDebugInfo($this);
if ($this->getAttribute('is_defined_test')) {
if ($this->isSpecial()) {
$compiler->repr(true);
@ -44,7 +46,7 @@ class Twig_Node_Expression_Name extends Twig_Node_Expression
// remove the non-PHP 5.4 version when PHP 5.3 support is dropped
// as the non-optimized version is just a workaround for slow ternary operator
// when the context has a lot of variables
if (version_compare(phpversion(), '5.4.0RC1', '>=')) {
if (PHP_VERSION_ID >= 50400) {
// PHP 5.4 ternary operator performance was optimized
$compiler
->raw('(isset($context[')

View File

@ -25,7 +25,7 @@ class Twig_Node_Expression_Parent extends Twig_Node_Expression
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -13,7 +13,7 @@
* Checks if a variable is divisible by a number.
*
* <pre>
* {% if loop.index is divisibleby(3) %}
* {% if loop.index is divisible by(3) %}
* </pre>
*
* @author Fabien Potencier <fabien@symfony.com>

View File

@ -18,12 +18,9 @@ abstract class Twig_Node_Expression_Unary extends Twig_Node_Expression
public function compile(Twig_Compiler $compiler)
{
$compiler->raw('(');
$compiler->raw(' ');
$this->operator($compiler);
$compiler
->subcompile($this->getNode('node'))
->raw(')')
;
$compiler->subcompile($this->getNode('node'));
}
abstract public function operator(Twig_Compiler $compiler);

View File

@ -24,7 +24,7 @@ class Twig_Node_Flush extends Twig_Node
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -33,7 +33,7 @@ class Twig_Node_For extends Twig_Node
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -24,7 +24,7 @@ class Twig_Node_ForLoop extends Twig_Node
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{

View File

@ -25,12 +25,12 @@ class Twig_Node_If extends Twig_Node
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{
$compiler->addDebugInfo($this);
for ($i = 0; $i < count($this->getNode('tests')); $i += 2) {
for ($i = 0, $count = count($this->getNode('tests')); $i < $count; $i += 2) {
if ($i > 0) {
$compiler
->outdent()

View File

@ -24,7 +24,7 @@ class Twig_Node_Import extends Twig_Node
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
* @param Twig_Compiler $compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{
@ -39,8 +39,12 @@ class Twig_Node_Import extends Twig_Node
$compiler->raw("\$this");
} else {
$compiler
->raw('$this->env->loadTemplate(')
->raw('$this->loadTemplate(')
->subcompile($this->getNode('expr'))
->raw(', ')
->repr($compiler->getFilename())
->raw(', ')
->repr($this->getLine())
->raw(")")
;
}

Some files were not shown because too many files have changed in this diff Show More