diff --git a/.dockerignore b/.dockerignore
index 5d4b8fcc0..e47e3356b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,6 +1,7 @@
storage
data
Dockerfile
+docker-compose*.yml
.dockerignore
.git
.gitignore
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..3c44241cc
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.env.example b/.env.example
index db3e5175b..7fd42e89f 100644
--- a/.env.example
+++ b/.env.example
@@ -1,46 +1,50 @@
-APP_NAME=Laravel
+APP_NAME="PixelFed Test"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
+ADMIN_DOMAIN="localhost"
+APP_DOMAIN="localhost"
+
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
-DB_DATABASE=homestead
-DB_USERNAME=homestead
-DB_PASSWORD=secret
+DB_DATABASE=
+DB_USERNAME=
+DB_PASSWORD=
BROADCAST_DRIVER=log
-CACHE_DRIVER=file
-SESSION_DRIVER=file
+CACHE_DRIVER=redis
+SESSION_DRIVER=redis
SESSION_LIFETIME=120
-QUEUE_DRIVER=sync
+QUEUE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
-MAIL_DRIVER=smtp
+MAIL_DRIVER=log
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
-PUSHER_APP_ID=
-PUSHER_APP_KEY=
-PUSHER_APP_SECRET=
-PUSHER_APP_CLUSTER=mt1
-
-SESSION_DOMAIN=".pixelfed.dev"
+SESSION_DOMAIN="${APP_DOMAIN}"
SESSION_SECURE_COOKIE=true
API_BASE="/api/1/"
API_SEARCH="/api/search"
OPEN_REGISTRATION=true
+RECAPTCHA_ENABLED=false
+ENFORCE_EMAIL_VERIFICATION=true
+
+MAX_PHOTO_SIZE=15000
+MAX_CAPTION_LENGTH=150
+MAX_ALBUM_LENGTH=4
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
diff --git a/.gitignore b/.gitignore
index d52873f29..82945e0a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ Homestead.yaml
npm-debug.log
yarn-error.log
.env
+.DS_Store
diff --git a/Dockerfile b/Dockerfile
index b64f08282..c9ee294a0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,17 +1,31 @@
-FROM php:7.2-fpm-alpine
+FROM php:7.2.6-fpm-alpine
-RUN apk add --no-cache git imagemagick \
- && apk add --no-cache --virtual .build build-base autoconf imagemagick-dev libtool \
- && docker-php-ext-install pdo_mysql \
- && pecl install imagick \
- && docker-php-ext-enable imagick \
- && apk del --purge .build
+ARG COMPOSER_VERSION="1.6.5"
+ARG COMPOSER_CHECKSUM="67bebe9df9866a795078bb2cf21798d8b0214f2e0b2fd81f2e907a8ef0be3434"
-RUN curl -sS https://getcomposer.org/installer | php \
- && mv composer.phar /usr/local/bin/ \
- && ln -s /usr/local/bin/composer.phar /usr/local/bin/composer
+RUN apk add --no-cache --virtual .build build-base autoconf imagemagick-dev libtool && \
+ apk --no-cache add imagemagick git && \
+ docker-php-ext-install pdo_mysql pcntl && \
+ pecl install imagick && \
+ docker-php-ext-enable imagick pcntl imagick && \
+ curl -LsS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar -o /tmp/composer.phar && \
+ echo "${COMPOSER_CHECKSUM} /tmp/composer.phar" | sha256sum -c - && \
+ install -m0755 -o root -g root /tmp/composer.phar /usr/bin/composer.phar && \
+ ln -sf /usr/bin/composer.phar /usr/bin/composer && \
+ rm /tmp/composer.phar && \
+ apk --no-cache del --purge .build
+
+COPY . /var/www/html/
WORKDIR /var/www/html
-COPY . .
-RUN composer install --prefer-source --no-interaction
+RUN install -d -m0755 -o www-data -g www-data \
+ /var/www/html/storage \
+ /var/www/html/storage/framework \
+ /var/www/html/storage/logs \
+ /var/www/html/storage/framework/sessions \
+ /var/www/html/storage/framework/views \
+ /var/www/html/storage/framework/cache && \
+ composer install --prefer-source --no-interaction
+
+VOLUME ["/var/www/html"]
ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}"
diff --git a/LICENSE b/LICENSE
index 2ba4e5e35..dbbe35581 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,661 @@
-MIT License
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
-Copyright (c) 2018 Daniel Supernault
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
-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:
+ Preamble
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are 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.
+
+ 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.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ 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 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 work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero 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 Affero 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 Affero 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 Affero 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.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ 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 AGPL, see
+.
diff --git a/README.md b/README.md
index c6ea6e002..15f30985f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,76 @@
-# PixelFed
-Federated Image Sharing
+# PixelFed: Federated Image Sharing
-> This project is still in active development and not yet ready for use.
\ No newline at end of file
+PixelFed is a federated social image sharing platform, similar to instagram.
+Federation is done using the [ActivityPub](https://activitypub.rocks/) protocol,
+which is used by [Mastodon](http://joinmastodon.org/), [PeerTube](https://joinpeertube.org/en/),
+[Pleroma](https://pleroma.social/), and more. Through ActivityPub PixelFed can share
+and interact with these platforms, as well as other instances of PixelFed.
+
+**_Please note this is alpha software, not recommended for production use,
+and federation is not supported yet._**
+
+PixelFed is very early into the development stage. If you would like to have a
+permanent instance with minimal breakage, **do not use this software until
+there is a stable release**. The following setup instructions are intended for
+testing and development.
+
+## Requirements
+ - PHP >= 7.1.3 (7.2+ recommended for stable version)
+ - MySQL, Postgres (MariaDB and sqlite are not supported yet)
+ - Redis
+ - Composer
+ - GD or ImageMagick
+ - OpenSSL PHP Extension
+ - PDO PHP Extension
+ - Mbstring PHP Extension
+ - Tokenizer PHP Extension
+ - XML PHP Extension
+ - Ctype PHP Extension
+ - JSON PHP Extension
+ - JpegOptim
+ - Optipng
+ - Pngquant 2
+ - SVGO
+ - Gifsicle
+
+## Installation
+
+This guide assumes you have NGINX/Apache installed, along with the dependencies.
+Those will not be covered in these early docs.
+
+```bash
+git clone https://github.com/dansup/pixelfed.git
+cd pixelfed
+composer install
+cp .env.example .env
+```
+
+**Edit .env file with proper values**
+
+```bash
+php artisan key:generate
+```
+
+```bash
+php artisan storage:link
+php artisan migrate
+php artisan horizon
+php artisan serve --host=localhost --port=80
+```
+
+Check your browser at http://localhost
+
+## Communication
+
+The ways you can communicate on the project are below. Before interacting, please
+read through the [Code Of Conduct](CODE_OF_CONDUCT.md).
+
+* IRC: #pixelfed on irc.freenode.net ([#freenode_#pixelfed:matrix.org through
+Matrix](https://matrix.to/#/#freenode_#pixelfed:matrix.org)
+* Project on Mastodon: [@pixelfed@mastodon.social](https://mastodon.social/@pixelfed)
+* E-mail: [hello@pixelfed.org](mailto:hello@pixelfed.org)
+
+## Support
+
+The lead maintainer is on Patreon! You can become a Patron at
+https://www.patreon.com/dansup
\ No newline at end of file
diff --git a/app/Avatar.php b/app/Avatar.php
index a7f8e4e67..b72510765 100644
--- a/app/Avatar.php
+++ b/app/Avatar.php
@@ -3,8 +3,16 @@
namespace App;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
class Avatar extends Model
{
+ use SoftDeletes;
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['deleted_at'];
}
diff --git a/app/Bookmark.php b/app/Bookmark.php
new file mode 100644
index 000000000..65cdfec19
--- /dev/null
+++ b/app/Bookmark.php
@@ -0,0 +1,10 @@
+take(50)->get();
+ foreach($medias as $media) {
+ ImageOptimize::dispatch($media);
+ }
+ }
+}
diff --git a/app/Console/Commands/SeedFollows.php b/app/Console/Commands/SeedFollows.php
new file mode 100644
index 000000000..d11685648
--- /dev/null
+++ b/app/Console/Commands/SeedFollows.php
@@ -0,0 +1,60 @@
+firstOrFail();
+ $target = Profile::inRandomOrder()->firstOrFail();
+
+ $follow = new Follower;
+ $follow->profile_id = $actor->id;
+ $follow->following_id = $target->id;
+ $follow->save();
+
+ FollowPipeline::dispatch($follow);
+ } catch (Exception $e) {
+ continue;
+ }
+ }
+ }
+}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index a8c515859..6912655e6 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -24,8 +24,9 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
- // $schedule->command('inspire')
- // ->hourly();
+ $schedule->command('media:optimize')
+ ->hourly();
+ $schedule->command('horizon:snapshot')->everyFiveMinutes();
}
/**
diff --git a/app/EmailVerification.php b/app/EmailVerification.php
new file mode 100644
index 000000000..cdc9b8bb1
--- /dev/null
+++ b/app/EmailVerification.php
@@ -0,0 +1,15 @@
+user_token . '/' . $this->random_token;
+ return "{$base}{$path}";
+ }
+}
diff --git a/app/Follower.php b/app/Follower.php
index 597f82aac..b2b8675de 100644
--- a/app/Follower.php
+++ b/app/Follower.php
@@ -6,5 +6,32 @@ use Illuminate\Database\Eloquent\Model;
class Follower extends Model
{
- //
+ public function actor()
+ {
+ return $this->belongsTo(Profile::class, 'profile_id', 'id');
+ }
+
+ public function target()
+ {
+ return $this->belongsTo(Profile::class, 'following_id', 'id');
+ }
+
+ public function profile()
+ {
+ return $this->belongsTo(Profile::class, 'following_id', 'id');
+ }
+
+ public function toText()
+ {
+ $actorName = $this->actor->username;
+ return "{$actorName} " . __('notification.startedFollowingYou');
+ }
+
+ public function toHtml()
+ {
+ $actorName = $this->actor->username;
+ $actorUrl = $this->actor->url();
+ return "{$actorName} " .
+ __('notification.startedFollowingYou');
+ }
}
diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php
new file mode 100644
index 000000000..9f03ef728
--- /dev/null
+++ b/app/Http/Controllers/AccountController.php
@@ -0,0 +1,98 @@
+middleware('auth');
+ }
+
+ public function notifications(Request $request)
+ {
+ $this->validate($request, [
+ 'page' => 'nullable|min:1|max:3'
+ ]);
+ $profile = Auth::user()->profile;
+ $timeago = Carbon::now()->subMonths(6);
+ $notifications = Notification::whereProfileId($profile->id)
+ ->whereDate('created_at', '>', $timeago)
+ ->orderBy('id','desc')
+ ->take(30)
+ ->simplePaginate();
+
+ return view('account.activity', compact('profile', 'notifications'));
+ }
+
+ public function verifyEmail(Request $request)
+ {
+ return view('account.verify_email');
+ }
+
+ public function sendVerifyEmail(Request $request)
+ {
+ if(EmailVerification::whereUserId(Auth::id())->count() !== 0) {
+ return redirect()->back()->with('status', 'A verification email has already been sent! Please check your email.');
+ }
+
+ $user = User::whereNull('email_verified_at')->find(Auth::id());
+ $utoken = hash('sha512', $user->id);
+ $rtoken = str_random(40);
+
+ $verify = new EmailVerification;
+ $verify->user_id = $user->id;
+ $verify->email = $user->email;
+ $verify->user_token = $utoken;
+ $verify->random_token = $rtoken;
+ $verify->save();
+
+ Mail::to($user->email)->send(new ConfirmEmail($verify));
+
+ return redirect()->back()->with('status', 'Email verification email sent!');
+ }
+
+ public function confirmVerifyEmail(Request $request, $userToken, $randomToken)
+ {
+ $verify = EmailVerification::where(DB::raw('BINARY user_token'), $userToken)
+ ->where(DB::raw('BINARY random_token'), $randomToken)
+ ->firstOrFail();
+ if(Auth::id() === $verify->user_id) {
+ $user = User::find(Auth::id());
+ $user->email_verified_at = Carbon::now();
+ $user->save();
+ return redirect('/timeline');
+ }
+ }
+
+ public function fetchNotifications($id)
+ {
+ $key = config('cache.prefix') . ":user.{$id}.notifications";
+ $redis = Redis::connection();
+ $notifications = $redis->lrange($key, 0, 30);
+ if(empty($notifications)) {
+ $notifications = Notification::whereProfileId($id)
+ ->orderBy('id','desc')->take(30)->get();
+ } else {
+ $notifications = $this->hydrateNotifications($notifications);
+ }
+
+ return $notifications;
+ }
+
+ public function hydrateNotifications($keys)
+ {
+ $prefix = 'notification.';
+ $notifications = collect([]);
+ foreach($keys as $key) {
+ $notifications->push(Cache::get("{$prefix}{$key}"));
+ }
+ return $notifications;
+ }
+}
diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php
index a9fd75a3a..71f2b8a64 100644
--- a/app/Http/Controllers/AdminController.php
+++ b/app/Http/Controllers/AdminController.php
@@ -3,8 +3,42 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
+use App\{Comment, Like, Media, Profile, Status, User};
class AdminController extends Controller
{
- //
+ public function __construct()
+ {
+ return $this->middleware('admin');
+ }
+
+ public function home()
+ {
+ return view('admin.home');
+ }
+
+ public function users(Request $request)
+ {
+ $users = User::orderBy('id', 'desc')->paginate(10);
+ return view('admin.users.home', compact('users'));
+ }
+
+
+ public function statuses(Request $request)
+ {
+ $statuses = Status::orderBy('id', 'desc')->paginate(10);
+ return view('admin.statuses.home', compact('statuses'));
+ }
+
+ public function showStatus(Request $request, $id)
+ {
+ $status = Status::findOrFail($id);
+ return view('admin.statuses.show', compact('status'));
+ }
+
+ public function media(Request $request)
+ {
+ $media = Status::whereHas('media')->orderby('id', 'desc')->paginate(12);
+ return view('admin.media.home', compact('media'));
+ }
}
diff --git a/app/Http/Controllers/ApiController.php b/app/Http/Controllers/ApiController.php
new file mode 100644
index 000000000..a596346bc
--- /dev/null
+++ b/app/Http/Controllers/ApiController.php
@@ -0,0 +1,32 @@
+middleware('auth');
+ }
+
+ public function hydrateLikes(Request $request)
+ {
+ $this->validate($request, [
+ 'min' => 'nullable|integer|min:1',
+ 'max' => 'nullable|integer',
+ ]);
+
+ $profile = Auth::user()->profile;
+
+ $likes = Like::whereProfileId($profile->id)
+ ->orderBy('id', 'desc')
+ ->take(1000)
+ ->pluck('status_id');
+
+ return response()->json($likes);
+ }
+}
diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php
index b2ea669a0..83f844da6 100644
--- a/app/Http/Controllers/Auth/LoginController.php
+++ b/app/Http/Controllers/Auth/LoginController.php
@@ -36,4 +36,24 @@ class LoginController extends Controller
{
$this->middleware('guest')->except('logout');
}
+
+ /**
+ * Validate the user login request.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return void
+ */
+ public function validateLogin($request)
+ {
+ $rules = [
+ $this->username() => 'required|string',
+ 'password' => 'required|string',
+ ];
+
+ if(config('pixelfed.recaptcha')) {
+ $rules['g-recaptcha-response'] = 'required|recaptcha';
+ }
+
+ $this->validate($request, $rules);
+ }
}
diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php
index a2c36356f..1b9de0513 100644
--- a/app/Http/Controllers/Auth/RegisterController.php
+++ b/app/Http/Controllers/Auth/RegisterController.php
@@ -52,12 +52,19 @@ class RegisterController extends Controller
{
$this->validateUsername($data['username']);
- return Validator::make($data, [
+
+ $rules = [
'name' => 'required|string|max:255',
'username' => 'required|alpha_dash|min:2|max:15|unique:users',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
- ]);
+ ];
+
+ if(config('pixelfed.recaptcha')) {
+ $rules['g-recaptcha-response'] = 'required|recaptcha';
+ }
+
+ return Validator::make($data, $rules);
}
/**
diff --git a/app/Http/Controllers/AvatarController.php b/app/Http/Controllers/AvatarController.php
new file mode 100644
index 000000000..afb893059
--- /dev/null
+++ b/app/Http/Controllers/AvatarController.php
@@ -0,0 +1,10 @@
+middleware('auth');
+ }
+
+ public function store(Request $request)
+ {
+ $this->validate($request, [
+ 'item' => 'required|integer|min:1'
+ ]);
+
+ $profile = Auth::user()->profile;
+ $status = Status::findOrFail($request->input('item'));
+
+ $bookmark = Bookmark::firstOrCreate(
+ ['status_id' => $status->id], ['profile_id' => $profile->id]
+ );
+
+ if($request->ajax()) {
+ $response = ['code' => 200, 'msg' => 'Bookmark saved!'];
+ } else {
+ $response = redirect()->back();
+ }
+
+ return $response;
+ }
+
+}
diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php
index 2150bf4ef..70a7825bd 100644
--- a/app/Http/Controllers/CommentController.php
+++ b/app/Http/Controllers/CommentController.php
@@ -3,40 +3,52 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
-use Auth;
+use App\Jobs\CommentPipeline\CommentPipeline;
+use App\Jobs\StatusPipeline\NewStatusPipeline;
+use Auth, Hashids;
use App\{Comment, Profile, Status};
-use Vinkla\Hashids\Facades\Hashids;
class CommentController extends Controller
{
+
+ public function show(Request $request, $username, int $id, int $cid)
+ {
+ $user = Profile::whereUsername($username)->firstOrFail();
+ $status = Status::whereProfileId($user->id)->whereInReplyToId($id)->findOrFail($cid);
+ return view('status.reply', compact('user', 'status'));
+ }
+
public function store(Request $request)
{
if(Auth::check() === false) { abort(403); }
$this->validate($request, [
- 'item' => 'required|alpha_num',
+ 'item' => 'required|integer',
'comment' => 'required|string|max:500'
]);
-
- try {
- $statusId = Hashids::decode($request->item)[0];
- } catch (Exception $e) {
- abort(500);
- }
+ $comment = $request->input('comment');
+ $statusId = $request->item;
$user = Auth::user();
$profile = $user->profile;
$status = Status::findOrFail($statusId);
- $comment = new Comment;
- $comment->profile_id = $profile->id;
- $comment->user_id = $user->id;
- $comment->status_id = $status->id;
- $comment->comment = e($request->comment);
- $comment->rendered = e($request->comment);
- $comment->is_remote = false;
- $comment->entities = null;
- $comment->save();
+ $reply = new Status();
+ $reply->profile_id = $profile->id;
+ $reply->caption = e($comment);
+ $reply->rendered = $comment;
+ $reply->in_reply_to_id = $status->id;
+ $reply->in_reply_to_profile_id = $status->profile_id;
+ $reply->save();
- return redirect($status->url());
+ NewStatusPipeline::dispatch($reply, false);
+ CommentPipeline::dispatch($status, $reply);
+
+ if($request->ajax()) {
+ $response = ['code' => 200, 'msg' => 'Comment saved', 'username' => $profile->username, 'url' => $reply->url(), 'profile' => $profile->url(), 'comment' => $reply->caption];
+ } else {
+ $response = redirect($status->url());
+ }
+
+ return $response;
}
}
diff --git a/app/Http/Controllers/DiscoverController.php b/app/Http/Controllers/DiscoverController.php
new file mode 100644
index 000000000..8a64c9379
--- /dev/null
+++ b/app/Http/Controllers/DiscoverController.php
@@ -0,0 +1,31 @@
+middleware('auth');
+ }
+
+ public function home()
+ {
+ $following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
+ $people = Profile::inRandomOrder()->where('id', '!=', Auth::user()->profile->id)->whereNotIn('id', $following)->take(3)->get();
+ $posts = Status::whereHas('media')->where('profile_id', '!=', Auth::user()->profile->id)->whereNotIn('profile_id', $following)->orderBy('created_at', 'desc')->take('21')->get();
+ return view('discover.home', compact('people', 'posts'));
+ }
+
+ public function showTags(Request $request, $hashtag)
+ {
+ $tag = Hashtag::whereSlug($hashtag)->firstOrFail();
+ $posts = $tag->posts()->has('media')->orderBy('id','desc')->paginate(12);
+ $count = $tag->posts()->has('media')->orderBy('id','desc')->count();
+ return view('discover.tags.show', compact('tag', 'posts', 'count'));
+ }
+}
diff --git a/app/Http/Controllers/FederationController.php b/app/Http/Controllers/FederationController.php
new file mode 100644
index 000000000..29711945d
--- /dev/null
+++ b/app/Http/Controllers/FederationController.php
@@ -0,0 +1,138 @@
+authCheck();
+ return view('federation.remotefollow');
+ }
+
+ public function remoteFollowStore(Request $request)
+ {
+ $this->authCheck();
+ $this->validate($request, [
+ 'url' => 'required|string'
+ ]);
+
+ if(config('pixelfed.remote_follow_enabled') !== true) {
+ abort(403);
+ }
+
+ $follower = Auth::user()->profile;
+ $url = $request->input('url');
+
+ RemoteFollowPipeline::dispatch($follower, $url);
+
+ return redirect()->back();
+ }
+
+ public function nodeinfoWellKnown()
+ {
+ $res = [
+ 'links' => [
+ [
+ 'href' => config('pixelfed.nodeinfo.url'),
+ 'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0'
+ ]
+ ]
+ ];
+ return response()->json($res);
+ }
+
+ public function nodeinfo()
+ {
+ $res = [
+ 'metadata' => [
+ 'nodeName' => config('app.name'),
+ 'software' => [
+ 'homepage' => 'https://pixelfed.org',
+ 'github' => 'https://github.com/pixelfed',
+ 'follow' => 'https://mastodon.social/@pixelfed'
+ ],
+ /*
+ TODO: Custom Features for Trending
+ 'customFeatures' => [
+ 'trending' => [
+ 'description' => 'Trending API for federated discovery',
+ 'api' => [
+ 'url' => null,
+ 'docs' => null
+ ],
+ ],
+ ],
+ */
+ ],
+ 'openRegistrations' => config('pixelfed.open_registration'),
+ 'protocols' => [
+ 'activitypub'
+ ],
+ 'services' => [
+ 'inbound' => [],
+ 'outbound' => []
+ ],
+ 'software' => [
+ 'name' => 'pixelfed',
+ 'version' => config('pixelfed.version')
+ ],
+ 'usage' => [
+ 'localPosts' => \App\Status::whereLocal(true)->count(),
+ 'users' => [
+ 'total' => \App\User::count()
+ ]
+ ],
+ 'version' => '2.0'
+ ];
+
+ return response()->json($res);
+ }
+
+
+ public function webfinger(Request $request)
+ {
+ $this->validate($request, ['resource'=>'required']);
+ $resource = $request->input('resource');
+ $parsed = Nickname::normalizeProfileUrl($resource);
+ $username = $parsed['username'];
+ $user = Profile::whereUsername($username)->firstOrFail();
+ $webfinger = (new Webfinger($user))->generate();
+ return response()->json($webfinger);
+ }
+
+ public function userOutbox(Request $request, $username)
+ {
+ if(config('pixelfed.activitypub_enabled') == false) {
+ abort(403);
+ }
+
+ $user = Profile::whereNull('remote_url')->whereUsername($username)->firstOrFail();
+ $timeline = $user->statuses()->orderBy('created_at','desc')->paginate(10);
+ $fractal = new Fractal\Manager();
+ $resource = new Fractal\Resource\Item($user, new ProfileOutbox);
+ $res = $fractal->createData($resource)->toArray();
+ return response()->json($res['data']);
+ }
+
+}
diff --git a/app/Http/Controllers/FollowerController.php b/app/Http/Controllers/FollowerController.php
index fa35ca2bb..42c776b23 100644
--- a/app/Http/Controllers/FollowerController.php
+++ b/app/Http/Controllers/FollowerController.php
@@ -2,9 +2,41 @@
namespace App\Http\Controllers;
+use Auth;
+use App\{Follower, Profile};
use Illuminate\Http\Request;
+use App\Jobs\FollowPipeline\FollowPipeline;
class FollowerController extends Controller
{
- //
+ public function __construct()
+ {
+ $this->middleware('auth');
+ }
+
+ public function store(Request $request)
+ {
+ $this->validate($request, [
+ 'item' => 'required|integer',
+ ]);
+
+ $user = Auth::user()->profile;
+ $target = Profile::where('id', '!=', $user->id)->findOrFail($request->input('item'));
+
+ $isFollowing = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->count();
+
+ if($isFollowing == 0) {
+ $follower = new Follower;
+ $follower->profile_id = $user->id;
+ $follower->following_id = $target->id;
+ $follower->save();
+ FollowPipeline::dispatch($follower);
+ } else {
+ $follower = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->firstOrFail();
+ $follower->delete();
+ }
+
+
+ return redirect()->back();
+ }
}
diff --git a/app/Http/Controllers/HashtagController.php b/app/Http/Controllers/HashtagController.php
new file mode 100644
index 000000000..a4afae3aa
--- /dev/null
+++ b/app/Http/Controllers/HashtagController.php
@@ -0,0 +1,10 @@
+middleware('auth');
+ }
+
+ public function store(Request $request)
+ {
+ $this->validate($request, [
+ 'item' => 'required|integer',
+ ]);
+
+ $profile = Auth::user()->profile;
+ $status = Status::withCount('likes')->findOrFail($request->input('item'));
+
+ $count = $status->likes_count;
+
+ if($status->likes()->whereProfileId($profile->id)->count() !== 0) {
+ $like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail();
+ $like->delete();
+ $count--;
+ } else {
+ $like = new Like;
+ $like->profile_id = $profile->id;
+ $like->status_id = $status->id;
+ $like->save();
+ $count++;
+ }
+
+ LikePipeline::dispatch($like);
+
+ if($request->ajax()) {
+ $response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count];
+ } else {
+ $response = redirect($status->url());
+ }
+
+ return $response;
+ }
}
diff --git a/app/Http/Controllers/NotificationController.php b/app/Http/Controllers/NotificationController.php
new file mode 100644
index 000000000..7bd130ac7
--- /dev/null
+++ b/app/Http/Controllers/NotificationController.php
@@ -0,0 +1,10 @@
+firstOrFail();
- $timeline = $user->statuses()->orderBy('id','desc')->paginate(10);
- return view('profile.show', compact('user', 'timeline'));
+
+ $mimes = [
+ 'application/activity+json',
+ 'application/ld+json',
+ 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
+ ];
+
+ if(in_array($request->header('accept'), $mimes) && config('pixelfed.activitypub_enabled')) {
+ return $this->showActivityPub($request, $user);
+ }
+
+ // TODO: refactor this mess
+ $owner = Auth::check() && Auth::id() === $user->user_id;
+ $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
+ $is_admin = is_null($user->domain) ? $user->user->is_admin : false;
+ $timeline = $user->statuses()
+ ->whereHas('media')
+ ->whereNull('in_reply_to_id')
+ ->orderBy('id','desc')
+ ->withCount(['comments', 'likes'])
+ ->simplePaginate(21);
+
+ return view('profile.show', compact('user', 'owner', 'is_following', 'is_admin', 'timeline'));
+ }
+
+ public function showActivityPub(Request $request, $user)
+ {
+ $fractal = new Fractal\Manager();
+ $resource = new Fractal\Resource\Item($user, new ProfileTransformer);
+ $res = $fractal->createData($resource)->toArray();
+ return response()->json($res['data']);
+ }
+
+ public function showAtomFeed(Request $request, $user)
+ {
+ $profile = Profile::whereUsername($user)->firstOrFail();
+ $items = $profile->statuses()->orderBy('created_at', 'desc')->take(10)->get();
+ return response()->view('atom.user', compact('profile', 'items'))
+ ->header('Content-Type', 'application/atom+xml');
+ }
+
+ public function followers(Request $request, $username)
+ {
+ $profile = Profile::whereUsername($username)->firstOrFail();
+ // TODO: fix $profile/$user mismatch in profile & follower templates
+ $user = $profile;
+ $owner = Auth::check() && Auth::id() === $user->user_id;
+ $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
+ $followers = $profile->followers()->orderBy('created_at','desc')->simplePaginate(12);
+ $is_admin = is_null($user->domain) ? $user->user->is_admin : false;
+ return view('profile.followers', compact('user', 'profile', 'followers', 'owner', 'is_following', 'is_admin'));
+ }
+
+ public function following(Request $request, $username)
+ {
+ $profile = Profile::whereUsername($username)->firstOrFail();
+ // TODO: fix $profile/$user mismatch in profile & follower templates
+ $user = $profile;
+ $owner = Auth::check() && Auth::id() === $user->user_id;
+ $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
+ $following = $profile->following()->orderBy('created_at','desc')->simplePaginate(12);
+ $is_admin = is_null($user->domain) ? $user->user->is_admin : false;
+ return view('profile.following', compact('user', 'profile', 'following', 'owner', 'is_following', 'is_admin'));
+ }
+
+ public function savedBookmarks(Request $request, $username)
+ {
+ if(Auth::check() === false || $username !== Auth::user()->username) {
+ abort(403);
+ }
+ $user = Auth::user()->profile;
+ $owner = true;
+ $following = false;
+ $timeline = $user->bookmarks()->withCount(['likes','comments'])->orderBy('created_at','desc')->simplePaginate(10);
+ $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
+ $is_admin = is_null($user->domain) ? $user->user->is_admin : false;
+ return view('profile.show', compact('user', 'owner', 'following', 'timeline', 'is_following', 'is_admin'));
}
}
diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php
new file mode 100644
index 000000000..bcedd947e
--- /dev/null
+++ b/app/Http/Controllers/SearchController.php
@@ -0,0 +1,42 @@
+get();
+ $tags = $res->map(function($item, $key) {
+ return [
+ 'count' => $item->posts()->count(),
+ 'url' => $item->url(),
+ 'type' => 'hashtag',
+ 'value' => $item->name,
+ 'tokens' => explode('-', $item->name),
+ 'name' => null
+ ];
+ });
+ $res = Profile::where('username', 'like', '%'.$tag.'%')->get();
+ $profiles = $res->map(function($item, $key) {
+ return [
+ 'count' => 0,
+ 'url' => $item->url(),
+ 'type' => 'profile',
+ 'value' => $item->username,
+ 'tokens' => [$item->username],
+ 'name' => $item->name
+ ];
+ });
+ $tags = $tags->push($profiles[0]);
+ return $tags;
+ });
+
+ return response()->json($res);
+ }
+}
diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php
new file mode 100644
index 000000000..4beb45418
--- /dev/null
+++ b/app/Http/Controllers/SettingsController.php
@@ -0,0 +1,131 @@
+middleware('auth');
+ }
+
+ public function home()
+ {
+ return view('settings.home');
+ }
+
+ public function homeUpdate(Request $request)
+ {
+ $this->validate($request, [
+ 'name' => 'required|string|max:30',
+ 'bio' => 'nullable|string|max:125'
+ ]);
+
+ $changes = false;
+ $name = $request->input('name');
+ $bio = $request->input('bio');
+ $user = Auth::user();
+ $profile = $user->profile;
+
+ if($profile->name != $name) {
+ $changes = true;
+ $user->name = $name;
+ $profile->name = $name;
+ }
+
+ if($profile->bio != $bio) {
+ $changes = true;
+ $profile->bio = $bio;
+ }
+
+ if($changes === true) {
+ $user->save();
+ $profile->save();
+ return redirect('/settings/home')->with('status', 'Profile successfully updated!');
+ }
+
+ return redirect('/settings/home');
+ }
+
+ public function password()
+ {
+ return view('settings.password');
+ }
+
+ public function passwordUpdate(Request $request)
+ {
+ $this->validate($request, [
+ 'current' => 'required|string',
+ 'password' => 'required|string',
+ 'password_confirmation' => 'required|string',
+ ]);
+
+ $current = $request->input('current');
+ $new = $request->input('password');
+ $confirm = $request->input('password_confirmation');
+
+ $user = Auth::user();
+
+ if(password_verify($current, $user->password) && $new === $confirm) {
+ $user->password = bcrypt($new);
+ $user->save();
+
+ return redirect('/settings/home')->with('status', 'Password successfully updated!');
+ }
+ return redirect('/settings/home')->with('error', 'There was an error with your request!');
+ }
+
+ public function email()
+ {
+ return view('settings.email');
+ }
+
+ public function avatar()
+ {
+ return view('settings.avatar');
+ }
+
+ public function notifications()
+ {
+ return view('settings.notifications');
+ }
+
+ public function privacy()
+ {
+ return view('settings.privacy');
+ }
+
+ public function security()
+ {
+ return view('settings.security');
+ }
+
+ public function applications()
+ {
+ return view('settings.applications');
+ }
+
+ public function dataExport()
+ {
+ return view('settings.dataexport');
+ }
+
+ public function dataImport()
+ {
+ return view('settings.import.home');
+ }
+
+ public function dataImportInstagram()
+ {
+ return view('settings.import.ig');
+ }
+
+ public function developers()
+ {
+ return view('settings.developers');
+ }
+}
diff --git a/app/Http/Controllers/SiteController.php b/app/Http/Controllers/SiteController.php
new file mode 100644
index 000000000..31603176b
--- /dev/null
+++ b/app/Http/Controllers/SiteController.php
@@ -0,0 +1,18 @@
+back();
+ }
+ App::setLocale($locale);
+ return redirect()->back();
+ }
+}
diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php
index 3b6088e5d..67f204cdf 100644
--- a/app/Http/Controllers/StatusController.php
+++ b/app/Http/Controllers/StatusController.php
@@ -2,23 +2,24 @@
namespace App\Http\Controllers;
-use Auth;
+use Auth, Cache;
+use App\Jobs\StatusPipeline\{NewStatusPipeline, StatusDelete};
+use App\Jobs\ImageOptimizePipeline\ImageOptimize;
use Illuminate\Http\Request;
use App\{Media, Profile, Status, User};
use Vinkla\Hashids\Facades\Hashids;
class StatusController extends Controller
{
- public function show(Request $request, $username, $hashid)
+ public function show(Request $request, $username, int $id)
{
$user = Profile::whereUsername($username)->firstOrFail();
- $id = Hashids::decode($hashid);
- if(empty($id)) {
- abort(404);
- } else {
- $id = $id[0];
+ $status = Status::whereProfileId($user->id)
+ ->withCount(['likes', 'comments', 'media'])
+ ->findOrFail($id);
+ if(!$status->media_path && $status->in_reply_to_id) {
+ return redirect($status->url());
}
- $status = Status::whereProfileId($user->id)->findOrFail($id);
return view('status.show', compact('user', 'status'));
}
@@ -32,29 +33,73 @@ class StatusController extends Controller
$user = Auth::user();
$this->validate($request, [
- 'photo' => 'required|image|max:8000',
- 'caption' => 'string|max:150'
+ 'photo.*' => 'required|mimes:jpeg,png,bmp,gif|max:' . config('pixelfed.max_photo_size'),
+ 'caption' => 'string|max:' . config('pixelfed.max_caption_length'),
+ 'cw' => 'nullable|string',
+ 'filter_class' => 'nullable|string',
+ 'filter_name' => 'nullable|string',
]);
- $monthHash = hash('sha1',date('Y').date('m'));
+
+ if(count($request->file('photo')) > config('pixelfed.max_album_length')) {
+ return redirect()->back()->with('error', 'Too many files, max limit per post: ' . config('pixelfed.max_album_length'));
+ }
+
+ $cw = $request->filled('cw') && $request->cw == 'on' ? true : false;
+ $monthHash = hash('sha1', date('Y') . date('m'));
$userHash = hash('sha1', $user->id . (string) $user->created_at);
- $storagePath = "public/m/{$monthHash}/{$userHash}";
- $path = $request->photo->store($storagePath);
$profile = $user->profile;
$status = new Status;
$status->profile_id = $profile->id;
- $status->caption = $request->caption;
+ $status->caption = strip_tags($request->caption);
+ $status->is_nsfw = $cw;
+
$status->save();
- $media = new Media;
- $media->status_id = $status->id;
- $media->profile_id = $profile->id;
- $media->user_id = $user->id;
- $media->media_path = $path;
- $media->size = $request->file('photo')->getClientSize();
- $media->mime = $request->file('photo')->getClientMimeType();
- $media->save();
+ $photos = $request->file('photo');
+ $order = 1;
+ foreach ($photos as $k => $v) {
+ $storagePath = "public/m/{$monthHash}/{$userHash}";
+ $path = $v->store($storagePath);
+ $media = new Media;
+ $media->status_id = $status->id;
+ $media->profile_id = $profile->id;
+ $media->user_id = $user->id;
+ $media->media_path = $path;
+ $media->size = $v->getClientSize();
+ $media->mime = $v->getClientMimeType();
+ $media->filter_class = $request->input('filter_class');
+ $media->filter_name = $request->input('filter_name');
+ $media->order = $order;
+ $media->save();
+ ImageOptimize::dispatch($media);
+ $order++;
+ }
+
+ NewStatusPipeline::dispatch($status);
+
+ // TODO: Send to subscribers
return redirect($status->url());
}
+
+ public function delete(Request $request)
+ {
+ if(!Auth::check()) {
+ abort(403);
+ }
+
+ $this->validate($request, [
+ 'type' => 'required|string',
+ 'item' => 'required|integer|min:1'
+ ]);
+
+ $status = Status::findOrFail($request->input('item'));
+
+ if($status->profile_id === Auth::user()->profile->id || Auth::user()->is_admin == true) {
+ StatusDelete::dispatch($status);
+ }
+
+ return redirect(Auth::user()->url());
+ }
}
diff --git a/app/Http/Controllers/TimelineController.php b/app/Http/Controllers/TimelineController.php
index 6564f17b8..c49b98b3b 100644
--- a/app/Http/Controllers/TimelineController.php
+++ b/app/Http/Controllers/TimelineController.php
@@ -3,7 +3,8 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
-use App\{Status, User};
+use Auth;
+use App\{Follower, Status, User};
class TimelineController extends Controller
{
@@ -14,7 +15,27 @@ class TimelineController extends Controller
public function personal()
{
- $timeline = Status::orderBy('id','desc')->paginate(10);
+ // TODO: Use redis for timelines
+ $following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
+ $following->push(Auth::user()->profile->id);
+ $timeline = Status::whereHas('media')
+ ->whereNull('in_reply_to_id')
+ ->whereIn('profile_id', $following)
+ ->orderBy('id','desc')
+ ->withCount(['comments', 'likes'])
+ ->simplePaginate(10);
return view('timeline.personal', compact('timeline'));
}
+
+ public function local()
+ {
+ // TODO: Use redis for timelines
+ $timeline = Status::whereHas('media')
+ ->whereNull('in_reply_to_id')
+ ->orderBy('id','desc')
+ ->withCount(['comments', 'likes'])
+ ->simplePaginate(10);
+ return view('timeline.public', compact('timeline'));
+ }
+
}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index 52a072844..61e44822f 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -60,5 +60,6 @@ class Kernel extends HttpKernel
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+ 'validemail' => \App\Http\Middleware\EmailVerificationCheck::class,
];
}
diff --git a/app/Http/Middleware/EmailVerificationCheck.php b/app/Http/Middleware/EmailVerificationCheck.php
new file mode 100644
index 000000000..04ee1fb1b
--- /dev/null
+++ b/app/Http/Middleware/EmailVerificationCheck.php
@@ -0,0 +1,28 @@
+user() &&
+ config('pixelfed.enforce_email_verification') &&
+ is_null($request->user()->email_verified_at) &&
+ !$request->is('i/verify-email') && !$request->is('login') &&
+ !$request->is('i/confirm-email/*')
+ ) {
+ return redirect('/i/verify-email');
+ }
+ return $next($request);
+ }
+}
diff --git a/app/Jobs/AvatarPipeline/CreateAvatar.php b/app/Jobs/AvatarPipeline/CreateAvatar.php
index 2393b96a9..40e54deb2 100644
--- a/app/Jobs/AvatarPipeline/CreateAvatar.php
+++ b/app/Jobs/AvatarPipeline/CreateAvatar.php
@@ -2,7 +2,7 @@
namespace App\Jobs\AvatarPipeline;
-use App\{Avatar, User};
+use App\{Avatar, Profile};
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
@@ -18,15 +18,15 @@ class CreateAvatar implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- protected $user;
+ protected $profile;
/**
* Create a new job instance.
*
* @return void
*/
- public function __construct(User $user)
+ public function __construct(Profile $profile)
{
- $this->user = $user;
+ $this->profile = $profile;
}
/**
@@ -36,19 +36,25 @@ class CreateAvatar implements ShouldQueue
*/
public function handle()
{
- $username = $this->user->profile->username;
- $email = $this->user->email;
+ $profile = $this->profile;
+ $username = $profile->username;
$generator = new RingsGenerator();
$generator->setBackgroundColor(Color::parseHex('#FFFFFF'));
- $identicon = new Identicon(new HashPreprocessor('sha1'), $generator);
+ $identicon = new Identicon(new HashPreprocessor('sha256'), $generator);
- $hash = $username . str_random(12) . $email;
+ $hash = $username . str_random(12);
$icon = $identicon->getIcon($hash);
try {
- $prefix = $this->user->profile->id;
+
+ $baseDir = storage_path('app/public/avatars');
+ if(!is_dir($baseDir)) {
+ mkdir($baseDir);
+ }
+
+ $prefix = $profile->id;
$padded = str_pad($prefix, 12, 0, STR_PAD_LEFT);
$parts = str_split($padded, 3);
foreach($parts as $k => $part) {
@@ -92,7 +98,7 @@ class CreateAvatar implements ShouldQueue
}
$avatar = new Avatar;
- $avatar->profile_id = $this->user->profile->id;
+ $avatar->profile_id = $profile->id;
$avatar->media_path = $path;
$avatar->thumb_path = $path;
$avatar->change_count = 0;
diff --git a/app/Jobs/CommentPipeline/CommentPipeline.php b/app/Jobs/CommentPipeline/CommentPipeline.php
new file mode 100644
index 000000000..e220709d1
--- /dev/null
+++ b/app/Jobs/CommentPipeline/CommentPipeline.php
@@ -0,0 +1,73 @@
+status = $status;
+ $this->comment = $comment;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $status = $this->status;
+ $comment = $this->comment;
+
+ $target = $status->profile;
+ $actor = $comment->profile;
+
+ if($actor->id === $target->id) {
+ return true;
+ }
+
+ try {
+
+ $notification = new Notification;
+ $notification->profile_id = $target->id;
+ $notification->actor_id = $actor->id;
+ $notification->action = 'comment';
+ $notification->message = $comment->replyToText();
+ $notification->rendered = $comment->replyToHtml();
+ $notification->item_id = $comment->id;
+ $notification->item_type = "App\Status";
+ $notification->save();
+
+ Cache::forever('notification.' . $notification->id, $notification);
+
+ $redis = Redis::connection();
+
+ $nkey = config('cache.prefix').':user.' . $target->id . '.notifications';
+ $redis->lpush($nkey, $notification->id);
+
+ } catch (Exception $e) {
+ Log::error($e);
+ }
+
+ }
+}
diff --git a/app/Jobs/FollowPipeline/FollowPipeline.php b/app/Jobs/FollowPipeline/FollowPipeline.php
new file mode 100644
index 000000000..f7cfa6506
--- /dev/null
+++ b/app/Jobs/FollowPipeline/FollowPipeline.php
@@ -0,0 +1,64 @@
+follower = $follower;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $follower = $this->follower;
+ $actor = $follower->actor;
+ $target = $follower->target;
+
+ try {
+
+ $notification = new Notification;
+ $notification->profile_id = $target->id;
+ $notification->actor_id = $actor->id;
+ $notification->action = 'follow';
+ $notification->message = $follower->toText();
+ $notification->rendered = $follower->toHtml();
+ $notification->item_id = $target->id;
+ $notification->item_type = "App\Profile";
+ $notification->save();
+
+ Cache::forever('notification.' . $notification->id, $notification);
+
+ $redis = Redis::connection();
+
+ $nkey = config('cache.prefix').':user.' . $target->id . '.notifications';
+ $redis->lpush($nkey, $notification->id);
+
+ } catch (Exception $e) {
+ Log::error($e);
+ }
+ }
+}
diff --git a/app/Jobs/ImageOptimizePipeline/ImageUpdate.php b/app/Jobs/ImageOptimizePipeline/ImageUpdate.php
index fab0975ba..5b21ea7e9 100644
--- a/app/Jobs/ImageOptimizePipeline/ImageUpdate.php
+++ b/app/Jobs/ImageOptimizePipeline/ImageUpdate.php
@@ -38,7 +38,10 @@ class ImageUpdate implements ShouldQueue
$thumb = storage_path('app/'. $media->thumbnail_path);
try {
ImageOptimizer::optimize($thumb);
- ImageOptimizer::optimize($path);
+ if($media->mime !== 'image/gif')
+ {
+ ImageOptimizer::optimize($path);
+ }
} catch (Exception $e) {
return;
}
diff --git a/app/Jobs/LikePipeline/LikePipeline.php b/app/Jobs/LikePipeline/LikePipeline.php
new file mode 100644
index 000000000..9d53fd8b1
--- /dev/null
+++ b/app/Jobs/LikePipeline/LikePipeline.php
@@ -0,0 +1,73 @@
+like = $like;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $like = $this->like;
+
+ $status = $this->like->status;
+ $actor = $this->like->actor;
+
+ $exists = Notification::whereProfileId($status->profile_id)
+ ->whereActorId($actor->id)
+ ->whereAction('like')
+ ->whereItemId($status->id)
+ ->whereItemType('App\Status')
+ ->count();
+
+ if($actor->id === $status->profile_id || $exists !== 0) {
+ return true;
+ }
+
+ try {
+
+ $notification = new Notification;
+ $notification->profile_id = $status->profile_id;
+ $notification->actor_id = $actor->id;
+ $notification->action = 'like';
+ $notification->message = $like->toText();
+ $notification->rendered = $like->toHtml();
+ $notification->item_id = $status->id;
+ $notification->item_type = "App\Status";
+ $notification->save();
+
+ Cache::forever('notification.' . $notification->id, $notification);
+
+ $redis = Redis::connection();
+ $key = config('cache.prefix').':user.' . $status->profile_id . '.notifications';
+ $redis->lpush($key, $notification->id);
+
+ } catch (Exception $e) {
+ Log::error($e);
+ }
+ }
+}
diff --git a/app/Jobs/MentionPipeline/MentionPipeline.php b/app/Jobs/MentionPipeline/MentionPipeline.php
new file mode 100644
index 000000000..69b7ebbb2
--- /dev/null
+++ b/app/Jobs/MentionPipeline/MentionPipeline.php
@@ -0,0 +1,72 @@
+status = $status;
+ $this->mention = $mention;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+
+ $status = $this->status;
+ $mention = $this->mention;
+ $actor = $this->status->profile;
+ $target = $this->mention->profile_id;
+
+ $exists = Notification::whereProfileId($target)
+ ->whereActorId($actor->id)
+ ->whereAction('mention')
+ ->whereItemId($status->id)
+ ->whereItemType('App\Status')
+ ->count();
+
+ if($actor->id === $target || $exists !== 0) {
+ return true;
+ }
+
+ try {
+
+ $notification = new Notification;
+ $notification->profile_id = $target;
+ $notification->actor_id = $actor->id;
+ $notification->action = 'mention';
+ $notification->message = $mention->toText();
+ $notification->rendered = $mention->toHtml();
+ $notification->item_id = $status->id;
+ $notification->item_type = "App\Status";
+ $notification->save();
+
+ } catch (Exception $e) {
+
+ }
+
+ }
+}
diff --git a/app/Jobs/RemoteFollowPipeline/RemoteFollowImportRecent.php b/app/Jobs/RemoteFollowPipeline/RemoteFollowImportRecent.php
new file mode 100644
index 000000000..1cb6a7828
--- /dev/null
+++ b/app/Jobs/RemoteFollowPipeline/RemoteFollowImportRecent.php
@@ -0,0 +1,226 @@
+actor = $actorObject;
+ $this->profile = $profile;
+ $this->cursor = 1;
+ $this->mediaCount = 0;
+ $this->supported = [
+ 'image/jpg',
+ 'image/jpeg',
+ 'image/png',
+ 'image/gif'
+ ];
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $outbox = $this->fetchOutbox();
+ }
+
+ public function fetchOutbox($url = false)
+ {
+ Log::info(json_encode($url));
+ $url = ($url == false) ? $this->actor['outbox'] : $url;
+
+ $response = Zttp::withHeaders([
+ 'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org'
+ ])->get($url);
+
+ $this->outbox = $response->json();
+ $this->parseOutbox($this->outbox);
+ }
+
+ public function parseOutbox($outbox)
+ {
+ $types = ['OrderedCollection', 'OrderedCollectionPage'];
+
+ if(isset($outbox['totalItems']) && $outbox['totalItems'] < 1) {
+ // Skip remote fetch, not enough posts
+ Log::info('not enough items');
+ return;
+ }
+
+ if(isset($outbox['type']) && in_array($outbox['type'], $types)) {
+ Log::info('handle ordered collection');
+ $this->handleOrderedCollection();
+ }
+ }
+
+ public function handleOrderedCollection()
+ {
+ $outbox = $this->outbox;
+
+ if(!isset($outbox['next']) && !isset($outbox['first']['next']) && $this->cursor !== 1) {
+ $this->cursor = 40;
+ $outbox['next'] = false;
+ }
+
+ if($outbox['type'] == 'OrderedCollectionPage') {
+ $this->nextUrl = $outbox['next'];
+ }
+
+ if(isset($outbox['first']) && !is_array($outbox['first'])) {
+ // Mastodon detected
+ Log::info('Mastodon detected...');
+ $this->nextUrl = $outbox['first'];
+ return $this->fetchOutbox($this->nextUrl);
+ } else {
+ // Pleroma detected.
+ $this->nextUrl = isset($outbox['next']) ? $outbox['next'] : (isset($outbox['first']['next']) ? $outbox['first']['next'] : $outbox['next']);
+ Log::info('Checking ordered items...');
+ $orderedItems = isset($outbox['orderedItems']) ? $outbox['orderedItems'] : $outbox['first']['orderedItems'];
+ }
+
+
+ foreach($orderedItems as $item) {
+ Log::info('Parsing items...');
+ $parsed = $this->parseObject($item);
+ if($parsed !== 0) {
+ Log::info('Found media!');
+ $this->importActivity($item);
+ }
+ }
+
+ if($this->cursor < 40 && $this->mediaCount < 9) {
+ $this->cursor++;
+ $this->mediaCount++;
+ $this->fetchOutbox($this->nextUrl);
+ }
+
+ }
+
+ public function parseObject($parsed)
+ {
+ if($parsed['type'] !== 'Create') {
+ return 0;
+ }
+
+ $activity = $parsed['object'];
+
+ if(isset($activity['attachment']) && !empty($activity['attachment'])) {
+ return $this->detectSupportedMedia($activity['attachment']);
+ }
+ }
+
+ public function detectSupportedMedia($attachments)
+ {
+ $supported = $this->supported;
+ $count = 0;
+
+ foreach($attachments as $media) {
+ $mime = $media['mediaType'];
+ $count = in_array($mime, $supported) ? ($count + 1) : $count;
+ }
+
+ return $count;
+ }
+
+ public function importActivity($activity)
+ {
+ $profile = $this->profile;
+ $supported = $this->supported;
+ $attachments = $activity['object']['attachment'];
+ $caption = str_limit($activity['object']['content'], 125);
+
+ if(Status::whereUrl($activity['id'])->count() !== 0) {
+ return true;
+ }
+
+ $status = new Status;
+ $status->profile_id = $profile->id;
+ $status->url = $activity['id'];
+ $status->local = false;
+ $status->caption = strip_tags($caption);
+ $status->created_at = Carbon::parse($activity['published']);
+
+ $count = 0;
+
+ foreach($attachments as $media) {
+ Log::info($media['mediaType'] . ' - ' . $media['url']);
+ $url = $media['url'];
+ $mime = $media['mediaType'];
+ if(!in_array($mime, $supported)) {
+ Log::info('Invalid media, skipping. ' . $mime);
+ continue;
+ }
+ $count++;
+
+ if($count === 1) {
+ $status->save();
+ }
+ $this->importMedia($url, $mime, $status);
+ }
+ Log::info(count($attachments) . ' media found...');
+
+ if($count !== 0) {
+ NewStatusPipeline::dispatch($status, $status->media->first());
+ }
+ }
+
+ public function importMedia($url, $mime, $status)
+ {
+ $user = $this->profile;
+ $monthHash = hash('sha1', date('Y') . date('m'));
+ $userHash = hash('sha1', $user->id . (string) $user->created_at);
+ $storagePath = "public/m/{$monthHash}/{$userHash}";
+ try {
+ $info = pathinfo($url);
+ $img = file_get_contents($url);
+ $file = '/tmp/' . str_random(12) . $info['basename'];
+ file_put_contents($file, $img);
+ $path = Storage::putFile($storagePath, new File($file), 'public');
+
+ $media = new Media;
+ $media->status_id = $status->id;
+ $media->profile_id = $status->profile_id;
+ $media->user_id = null;
+ $media->media_path = $path;
+ $media->size = 0;
+ $media->mime = $mime;
+ $media->save();
+
+ return true;
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+}
diff --git a/app/Jobs/RemoteFollowPipeline/RemoteFollowPipeline.php b/app/Jobs/RemoteFollowPipeline/RemoteFollowPipeline.php
new file mode 100644
index 000000000..5854e90fe
--- /dev/null
+++ b/app/Jobs/RemoteFollowPipeline/RemoteFollowPipeline.php
@@ -0,0 +1,105 @@
+follower = $follower;
+ $this->url = $url;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $follower = $this->follower;
+ $url = $this->url;
+
+ if(Profile::whereRemoteUrl($url)->count() !== 0) {
+ return true;
+ }
+
+ $this->discover($url);
+ return true;
+ }
+
+ public function discover($url)
+ {
+ $context = new Context([
+ 'keys' => ['examplekey' => 'secret-key-here'],
+ 'algorithm' => 'hmac-sha256',
+ 'headers' => ['(request-target)', 'date'],
+ ]);
+
+ $handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context);
+ $client = new Client(['handler' => $handlerStack]);
+ $response = Zttp::withHeaders([
+ 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
+ 'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org'
+ ])->get($url);
+ $this->response = $response->json();
+
+ $this->storeProfile();
+ }
+
+ public function storeProfile()
+ {
+ $res = $this->response;
+ $domain = parse_url($res['url'], PHP_URL_HOST);
+ $username = $res['preferredUsername'];
+ $remoteUsername = "@{$username}@{$domain}";
+
+ $profile = new Profile;
+ $profile->user_id = null;
+ $profile->domain = $domain;
+ $profile->username = $remoteUsername;
+ $profile->name = $res['name'];
+ $profile->bio = str_limit($res['summary'], 125);
+ $profile->sharedInbox = $res['endpoints']['sharedInbox'];
+ $profile->remote_url = $res['url'];
+ $profile->save();
+
+ RemoteFollowImportRecent::dispatch($this->response, $profile);
+ CreateAvatar::dispatch($profile);
+ }
+
+ public function sendActivity()
+ {
+ $res = $this->response;
+ $url = $res['inbox'];
+
+ $activity = Zttp::withHeaders(['Content-Type' => 'application/activity+json'])->post($url, [
+ 'type' => 'Follow',
+ 'object' => $this->follower->url()
+ ]);
+ }
+}
diff --git a/app/Jobs/StatusPipeline/NewStatusPipeline.php b/app/Jobs/StatusPipeline/NewStatusPipeline.php
new file mode 100644
index 000000000..8939dd9ad
--- /dev/null
+++ b/app/Jobs/StatusPipeline/NewStatusPipeline.php
@@ -0,0 +1,47 @@
+status = $status;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $status = $this->status;
+
+ StatusEntityLexer::dispatch($status);
+ //StatusActivityPubDeliver::dispatch($status);
+
+ Cache::forever('post.' . $status->id, $status);
+
+ $redis = Redis::connection();
+ $redis->lpush(config('cache.prefix').':user.' . $status->profile_id . '.posts', $status->id);
+ }
+}
diff --git a/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php b/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php
new file mode 100644
index 000000000..acf660ca4
--- /dev/null
+++ b/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php
@@ -0,0 +1,38 @@
+status = $status;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $status = $this->status;
+
+ // todo: fanout on write
+ }
+}
diff --git a/app/Jobs/StatusPipeline/StatusDelete.php b/app/Jobs/StatusPipeline/StatusDelete.php
new file mode 100644
index 000000000..f48b200a3
--- /dev/null
+++ b/app/Jobs/StatusPipeline/StatusDelete.php
@@ -0,0 +1,79 @@
+status = $status;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $status = $this->status;
+ $this->unlinkRemoveMedia($status);
+ }
+
+ public function unlinkRemoveMedia($status)
+ {
+ if($status->media()->count() == 0) {
+ return;
+ }
+
+ foreach($status->media as $media) {
+ $thumbnail = storage_path("app/{$media->thumbnail_path}");
+ $photo = storage_path("app/{$media->media_path}");
+
+ try {
+ if(is_file($thumbnail)) {
+ unlink($thumbnail);
+ }
+ if(is_file($photo)) {
+ unlink($photo);
+ }
+ $media->delete();
+ } catch (Exception $e) {
+
+ }
+ }
+ $comments = Status::where('in_reply_to_id', $status->id)->get();
+ foreach($comments as $comment) {
+ $comment->in_reply_to_id = null;
+ $comment->save();
+ Notification::whereItemType('App\Status')
+ ->whereItemId($comment->id)
+ ->delete();
+ }
+
+ $status->likes()->delete();
+ Notification::whereItemType('App\Status')
+ ->whereItemId($status->id)
+ ->delete();
+ StatusHashtag::whereStatusId($status->id)->delete();
+ $status->delete();
+
+ return true;
+ }
+}
diff --git a/app/Jobs/StatusPipeline/StatusEntityLexer.php b/app/Jobs/StatusPipeline/StatusEntityLexer.php
new file mode 100644
index 000000000..c9dff4d59
--- /dev/null
+++ b/app/Jobs/StatusPipeline/StatusEntityLexer.php
@@ -0,0 +1,120 @@
+status = $status;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $status = $this->status;
+ $this->parseEntities();
+ }
+
+ public function parseEntities()
+ {
+ $this->extractEntities();
+ }
+
+ public function extractEntities()
+ {
+ $this->entities = Extractor::create()->extract($this->status->caption);
+ $this->autolinkStatus();
+ }
+
+ public function autolinkStatus()
+ {
+ $this->autolink = Autolink::create()->autolink($this->status->caption);
+ $this->storeEntities();
+ }
+
+ public function storeEntities()
+ {
+ $status = $this->status;
+ $this->storeHashtags();
+ $this->storeMentions();
+ $status->rendered = $this->autolink;
+ $status->entities = json_encode($this->entities);
+ $status->save();
+ }
+
+ public function storeHashtags()
+ {
+ $tags = array_unique($this->entities['hashtags']);
+ $status = $this->status;
+
+ foreach($tags as $tag) {
+ $slug = str_slug($tag);
+
+ $htag = Hashtag::firstOrCreate(
+ ['name' => $tag],
+ ['slug' => $slug]
+ );
+
+ StatusHashtag::firstOrCreate(
+ ['status_id' => $status->id],
+ ['hashtag_id' => $htag->id]
+ );
+ }
+ }
+
+ public function storeMentions()
+ {
+ $mentions = array_unique($this->entities['mentions']);
+ $status = $this->status;
+
+ foreach($mentions as $mention) {
+ $mentioned = Profile::whereUsername($mention)->first();
+
+ if(empty($mentioned) || !isset($mentioned->id)) {
+ continue;
+ }
+
+ $m = new Mention;
+ $m->status_id = $status->id;
+ $m->profile_id = $mentioned->id;
+ $m->save();
+
+ MentionPipeline::dispatch($status, $m);
+ }
+ }
+
+}
diff --git a/app/Like.php b/app/Like.php
index 32c99554e..c70b647bc 100644
--- a/app/Like.php
+++ b/app/Like.php
@@ -3,16 +3,40 @@
namespace App;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
class Like extends Model
{
+ use SoftDeletes;
+
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['deleted_at'];
+
public function actor()
{
- return $this->belongsTo(Profile::class);
+ return $this->belongsTo(Profile::class, 'profile_id', 'id');
}
public function status()
{
return $this->belongsTo(Status::class);
}
+
+ public function toText()
+ {
+ $actorName = $this->actor->username;
+ return "{$actorName} " . __('notification.likedPhoto');
+ }
+
+ public function toHtml()
+ {
+ $actorName = $this->actor->username;
+ $actorUrl = $this->actor->url();
+ return "{$actorName} " .
+ __('notification.likedPhoto');
+ }
}
diff --git a/app/Mail/ConfirmEmail.php b/app/Mail/ConfirmEmail.php
new file mode 100644
index 000000000..83191f5fe
--- /dev/null
+++ b/app/Mail/ConfirmEmail.php
@@ -0,0 +1,34 @@
+verify = $verify;
+ }
+
+ /**
+ * Build the message.
+ *
+ * @return $this
+ */
+ public function build()
+ {
+ return $this->markdown('emails.confirm_email')->with(['verify'=>$this->verify]);
+ }
+}
diff --git a/app/Media.php b/app/Media.php
index 8cc2ffd1e..7c9138965 100644
--- a/app/Media.php
+++ b/app/Media.php
@@ -2,11 +2,21 @@
namespace App;
-use Illuminate\Database\Eloquent\Model;
use Storage;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
class Media extends Model
{
+ use SoftDeletes;
+
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['deleted_at'];
+
public function url()
{
$path = $this->media_path;
diff --git a/app/Mention.php b/app/Mention.php
new file mode 100644
index 000000000..bc76bdc97
--- /dev/null
+++ b/app/Mention.php
@@ -0,0 +1,42 @@
+belongsTo(Profile::class, 'profile_id', 'id');
+ }
+
+ public function status()
+ {
+ return $this->belongsTo(Status::class, 'status_id', 'id');
+ }
+
+ public function toText()
+ {
+ $actorName = $this->status->profile->username;
+ return "{$actorName} " . __('notification.mentionedYou');
+ }
+
+ public function toHtml()
+ {
+ $actorName = $this->status->profile->username;
+ $actorUrl = $this->status->profile->url();
+ return "{$actorName} " .
+ __('notification.mentionedYou');
+ }
+}
diff --git a/app/Notification.php b/app/Notification.php
new file mode 100644
index 000000000..9aef5b197
--- /dev/null
+++ b/app/Notification.php
@@ -0,0 +1,39 @@
+belongsTo(Profile::class, 'actor_id', 'id');
+ }
+
+ public function profile()
+ {
+ return $this->belongsTo(Profile::class, 'profile_id', 'id');
+ }
+
+ public function item()
+ {
+ return $this->morphTo();
+ }
+
+ public function status()
+ {
+ return $this->belongsTo(Status::class, 'item_id', 'id');
+ }
+
+}
diff --git a/app/Observer/UserObserver.php b/app/Observer/UserObserver.php
index f2c523852..e91042830 100644
--- a/app/Observer/UserObserver.php
+++ b/app/Observer/UserObserver.php
@@ -34,7 +34,7 @@ class UserObserver
$profile->public_key = $pki_public;
$profile->save();
- CreateAvatar::dispatch($user);
+ CreateAvatar::dispatch($profile);
}
}
diff --git a/app/Profile.php b/app/Profile.php
index dbd666c9d..009ed2cbf 100644
--- a/app/Profile.php
+++ b/app/Profile.php
@@ -2,27 +2,42 @@
namespace App;
-use Illuminate\Database\Eloquent\Model;
use Storage;
+use App\Util\Lexer\PrettyNumber;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
class Profile extends Model
{
+ use SoftDeletes;
+
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['deleted_at'];
protected $hidden = [
'private_key',
];
protected $visible = ['id', 'username', 'name'];
+ public function user()
+ {
+ return $this->belongsTo(User::class);
+ }
+
public function url($suffix = '')
{
- return url('/@' . $this->username . $suffix);
+ return url($this->username . $suffix);
}
public function permalink($suffix = '')
{
return url('users/' . $this->username . $suffix);
}
-
+
public function emailUrl()
{
$domain = parse_url(config('app.url'), PHP_URL_HOST);
@@ -31,31 +46,67 @@ class Profile extends Model
public function statuses()
{
- return $this->hasMany(Status::class);
+ return $this->hasMany(Status::class);
+ }
+
+ public function followingCount($short = false)
+ {
+ $count = $this->following()->count();
+ if($short) {
+ return PrettyNumber::convert($count);
+ } else {
+ return $count;
+ }
+ }
+
+ public function followerCount($short = false)
+ {
+ $count = $this->followers()->count();
+ if($short) {
+ return PrettyNumber::convert($count);
+ } else {
+ return $count;
+ }
}
public function following()
{
- return $this->hasManyThrough(
- Profile::class,
- Follower::class,
- 'profile_id',
- 'id',
- 'id',
- 'id'
- );
+ return $this->belongsToMany(
+ Profile::class,
+ 'followers',
+ 'profile_id',
+ 'following_id'
+ );
}
public function followers()
{
- return $this->hasManyThrough(
- Profile::class,
- Follower::class,
- 'following_id',
- 'id',
- 'id',
- 'id'
- );
+ return $this->belongsToMany(
+ Profile::class,
+ 'followers',
+ 'following_id',
+ 'profile_id'
+ );
+ }
+
+ public function follows($profile)
+ {
+ return Follower::whereProfileId($this->id)->whereFollowingId($profile->id)->count();
+ }
+
+ public function followedBy($profile)
+ {
+ return Follower::whereProfileId($profile->id)->whereFollowingId($this->id)->count();
+ }
+
+ public function bookmarks()
+ {
+ return $this->belongsToMany(
+ Status::class,
+ 'bookmarks',
+ 'profile_id',
+ 'status_id'
+ );
}
public function likes()
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index adeb8fbe8..8a21a2a7d 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -6,6 +6,7 @@ use App\User;
use Auth, Horizon;
use App\Observers\UserObserver;
use Illuminate\Support\Facades\Blade;
+use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@@ -17,6 +18,8 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot()
{
+ Schema::defaultStringLength(191);
+
User::observe(UserObserver::class);
Horizon::auth(function ($request) {
@@ -37,7 +40,6 @@ class AppServiceProvider extends ServiceProvider
});
Blade::directive('prettySize', function($expression) {
-
$size = intval($expression);
$precision = 0;
$short = true;
@@ -48,6 +50,11 @@ class AppServiceProvider extends ServiceProvider
$res = round($size, $precision).$units[$i];
return "";
});
+
+ Blade::directive('maxFileSize', function() {
+ $value = config('pixelfed.max_photo_size');
+ return \App\Util\Lexer\PrettyNumber::size($value, true);
+ });
}
/**
diff --git a/app/Status.php b/app/Status.php
index 2c3f9760b..fe3fc26d2 100644
--- a/app/Status.php
+++ b/app/Status.php
@@ -2,12 +2,21 @@
namespace App;
-use Illuminate\Database\Eloquent\Model;
use Storage;
-use Vinkla\Hashids\Facades\Hashids;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
class Status extends Model
{
+ use SoftDeletes;
+
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['deleted_at'];
+
public function profile()
{
return $this->belongsTo(Profile::class);
@@ -25,6 +34,9 @@ class Status extends Model
public function thumb()
{
+ if($this->media->count() == 0 || $this->is_nsfw) {
+ return "data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
+ }
return url(Storage::url($this->firstMedia()->thumbnail_path));
}
@@ -32,13 +44,25 @@ class Status extends Model
{
$id = $this->id;
$username = $this->profile->username;
- return url(config('app.url') . "/p/@{$username}/{$id}");
+ $path = config('app.url') . "/p/{$username}/{$id}";
+ if(!is_null($this->in_reply_to_id)) {
+ $pid = $this->in_reply_to_id;
+ $path = config('app.url') . "/p/{$username}/{$pid}/c/{$id}";
+ }
+ return url($path);
+ }
+
+ public function editUrl()
+ {
+ return $this->url() . '/edit';
}
public function mediaUrl()
{
- $path = $this->firstMedia()->media_path;
- $url = Storage::url($path);
+ $media = $this->firstMedia();
+ $path = $media->media_path;
+ $hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at);
+ $url = Storage::url($path) . "?v={$hash}";
return url($url);
}
@@ -96,4 +120,17 @@ class Status extends Model
return $obj;
}
+ public function replyToText()
+ {
+ $actorName = $this->profile->username;
+ return "{$actorName} " . __('notification.commented');
+ }
+
+ public function replyToHtml()
+ {
+ $actorName = $this->profile->username;
+ $actorUrl = $this->profile->url();
+ return "{$actorName} " .
+ __('notification.commented');
+ }
}
diff --git a/app/StatusHashtag.php b/app/StatusHashtag.php
new file mode 100644
index 000000000..7ceac0564
--- /dev/null
+++ b/app/StatusHashtag.php
@@ -0,0 +1,10 @@
+statuses()->count();
+ $statuses = $profile->statuses()->has('media')->orderBy('id','desc')->take(20)->get()->map(function($i, $k) {
+ $item = [
+ 'id' => $i->url(),
+ // TODO: handle other types
+ 'type' => 'Create',
+ 'actor' => $i->profile->url(),
+ 'published' => $i->created_at->toISO8601String(),
+ 'to' => [
+ 'https://www.w3.org/ns/activitystreams#Public'
+ ],
+ 'cc' => [
+ $i->profile->permalink('/followers'),
+ ],
+ 'object' => [
+ 'id' => $i->url(),
+
+ // TODO: handle other types
+ 'type' => 'Note',
+
+ // XXX: CW Title
+ 'summary' => null,
+ 'content' => $i->rendered ?? $i->caption,
+ 'inReplyTo' => null,
+
+ // TODO: fix date format
+ 'published' => $i->created_at->toAtomString(),
+ 'url' => $i->url(),
+ 'attributedTo' => $i->profile->permalink(),
+ 'to' => [
+ // TODO: handle proper scope
+ 'https://www.w3.org/ns/activitystreams#Public'
+ ],
+ 'cc' => [
+ // TODO: add cc's
+ //"{$notice->getProfile()->getUrl()}/subscribers",
+ ],
+ 'sensitive' => null,
+ 'atomUri' => $i->url(),
+ 'inReplyToAtomUri' => null,
+ 'conversation' => $i->url(),
+ 'attachment' => [
+
+ // TODO: support more than 1 attachment
+ [
+ 'type' => 'Document',
+ 'mediaType' => $i->firstMedia()->mime,
+ 'url' => $i->firstMedia()->url(),
+ 'name' => null
+ ]
+ ],
+ 'tag' => []
+ ]
+ ];
+ return $item;
+ });
+
+ return [
+ '@context' => 'https://www.w3.org/ns/activitystreams',
+ 'id' => $profile->permalink('/outbox'),
+ 'type' => 'OrderedCollection',
+ 'totalItems' => $count,
+ 'orderedItems' => $statuses
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/app/User.php b/app/User.php
index e0ce404de..38edc3e9d 100644
--- a/app/User.php
+++ b/app/User.php
@@ -3,11 +3,19 @@
namespace App;
use Illuminate\Notifications\Notifiable;
+use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
- use Notifiable;
+ use Notifiable, SoftDeletes;
+
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['deleted_at'];
/**
* The attributes that are mass assignable.
@@ -34,6 +42,6 @@ class User extends Authenticatable
public function url()
{
- return url(config('app.url') . '/@' . $this->username);
+ return url(config('app.url') . '/' . $this->username);
}
}
diff --git a/app/Util/ActivityPub/DiscoverActor.php b/app/Util/ActivityPub/DiscoverActor.php
new file mode 100644
index 000000000..6c81b3d1a
--- /dev/null
+++ b/app/Util/ActivityPub/DiscoverActor.php
@@ -0,0 +1,49 @@
+url = $url;
+ }
+
+ public function fetch()
+ {
+ $res = Zttp::withHeaders([
+ 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
+ 'User-Agent' => 'PixelFedBot - https://pixelfed.org'
+ ])->get($this->url);
+ $this->response = $res->body();
+ return $this;
+ }
+
+ public function getResponse()
+ {
+ return json_decode($this->response, true);
+ }
+
+ public function getJsonResponse()
+ {
+ return $this->response;
+ }
+
+ public function discover()
+ {
+ $this->fetch();
+ $res = $this->getResponse();
+
+ if(empty($res) || !in_array('type', $res) || $res['type'] !== 'Person') {
+ throw new \Exception('Invalid Actor Object');
+ }
+
+ return $res;
+ }
+
+}
\ No newline at end of file
diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php
new file mode 100644
index 000000000..310c91b84
--- /dev/null
+++ b/app/Util/ActivityPub/Inbox.php
@@ -0,0 +1,88 @@
+request = $request;
+ $this->profile = $profile;
+ $this->payload = $payload;
+ }
+
+ public function handle()
+ {
+ $this->authenticatePayload();
+ }
+
+ public function authenticatePayload()
+ {
+ // todo
+
+ $this->handleVerb();
+ }
+
+ public function handleVerb()
+ {
+ $verb = $this->payload['type'];
+
+ switch ($verb) {
+ case 'Create':
+ $this->handleCreateActivity();
+ break;
+
+ case 'Follow':
+ $this->handleFollowActivity();
+ break;
+
+ default:
+ // TODO: decide how to handle invalid verbs.
+ break;
+ }
+ }
+
+ public function handleCreateActivity()
+ {
+ // todo
+ }
+
+ public function handleFollowActivity()
+ {
+ $actor = $this->payload['object'];
+ $target = $this->profile;
+
+ }
+
+ public function actorFirstOrCreate($actorUrl)
+ {
+ if(Profile::whereRemoteUrl($actorUrl)->count() !== 0) {
+ return Profile::whereRemoteUrl($actorUrl)->firstOrFail();
+ }
+
+ $res = (new DiscoverActor($url))->discover();
+
+ $domain = parse_url($res['url'], PHP_URL_HOST);
+ $username = $res['preferredUsername'];
+ $remoteUsername = "@{$username}@{$domain}";
+
+ $profile = new Profile;
+ $profile->user_id = null;
+ $profile->domain = $domain;
+ $profile->username = $remoteUsername;
+ $profile->name = $res['name'];
+ $profile->bio = str_limit($res['summary'], 125);
+ $profile->sharedInbox = $res['endpoints']['sharedInbox'];
+ $profile->remote_url = $res['url'];
+ $profile->save();
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/Util/Lexer/Autolink.php b/app/Util/Lexer/Autolink.php
new file mode 100755
index 000000000..eb899dfd8
--- /dev/null
+++ b/app/Util/Lexer/Autolink.php
@@ -0,0 +1,771 @@
+
+ * @author Nick Pope
+ * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ */
+
+namespace App\Util\Lexer;
+
+use App\Util\Lexer\Regex;
+use App\Util\Lexer\Extractor;
+use App\Util\Lexer\StringUtils;
+
+/**
+ * Twitter Autolink Class
+ *
+ * Parses tweets and generates HTML anchor tags around URLs, usernames,
+ * username/list pairs and hashtags.
+ *
+ * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
+ * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
+ * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
+ *
+ * @author Mike Cochrane
+ * @author Nick Pope
+ * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ */
+class Autolink extends Regex
+{
+
+ /**
+ * CSS class for auto-linked URLs.
+ *
+ * @var string
+ */
+ protected $class_url = '';
+
+ /**
+ * CSS class for auto-linked username URLs.
+ *
+ * @var string
+ */
+ protected $class_user = 'u-url mention';
+
+ /**
+ * CSS class for auto-linked list URLs.
+ *
+ * @var string
+ */
+ protected $class_list = 'u-url list-slug';
+
+ /**
+ * CSS class for auto-linked hashtag URLs.
+ *
+ * @var string
+ */
+ protected $class_hash = 'u-url hashtag';
+
+ /**
+ * CSS class for auto-linked cashtag URLs.
+ *
+ * @var string
+ */
+ protected $class_cash = 'u-url cashtag';
+
+ /**
+ * URL base for username links (the username without the @ will be appended).
+ *
+ * @var string
+ */
+ protected $url_base_user = null;
+
+ /**
+ * URL base for list links (the username/list without the @ will be appended).
+ *
+ * @var string
+ */
+ protected $url_base_list = null;
+
+ /**
+ * URL base for hashtag links (the hashtag without the # will be appended).
+ *
+ * @var string
+ */
+ protected $url_base_hash = null;
+
+ /**
+ * URL base for cashtag links (the hashtag without the $ will be appended).
+ *
+ * @var string
+ */
+ protected $url_base_cash = null;
+
+ /**
+ * Whether to include the value 'nofollow' in the 'rel' attribute.
+ *
+ * @var bool
+ */
+ protected $nofollow = true;
+
+ /**
+ * Whether to include the value 'noopener' in the 'rel' attribute.
+ *
+ * @var bool
+ */
+ protected $noopener = true;
+
+ /**
+ * Whether to include the value 'external' in the 'rel' attribute.
+ *
+ * Often this is used to be matched on in JavaScript for dynamically adding
+ * the 'target' attribute which is deprecated in HTML 4.01. In HTML 5 it has
+ * been undeprecated and thus the 'target' attribute can be used. If this is
+ * set to false then the 'target' attribute will be output.
+ *
+ * @var bool
+ */
+ protected $external = true;
+
+ /**
+ * The scope to open the link in.
+ *
+ * Support for the 'target' attribute was deprecated in HTML 4.01 but has
+ * since been reinstated in HTML 5. To output the 'target' attribute you
+ * must disable the adding of the string 'external' to the 'rel' attribute.
+ *
+ * @var string
+ */
+ protected $target = '_blank';
+
+ /**
+ * attribute for invisible span tag
+ *
+ * @var string
+ */
+ protected $invisibleTagAttrs = "style='position:absolute;left:-9999px;'";
+
+ /**
+ *
+ * @var Extractor
+ */
+ protected $extractor = null;
+
+ /**
+ * Provides fluent method chaining.
+ *
+ * @param string $tweet The tweet to be converted.
+ * @param bool $full_encode Whether to encode all special characters.
+ *
+ * @see __construct()
+ *
+ * @return Autolink
+ */
+ public static function create($tweet = null, $full_encode = false)
+ {
+ return new static($tweet, $full_encode);
+ }
+
+ /**
+ * Reads in a tweet to be parsed and converted to contain links.
+ *
+ * As the intent is to produce links and output the modified tweet to the
+ * user, we take this opportunity to ensure that we escape user input.
+ *
+ * @see htmlspecialchars()
+ *
+ * @param string $tweet The tweet to be converted.
+ * @param bool $escape Whether to escape the tweet (default: true).
+ * @param bool $full_encode Whether to encode all special characters.
+ */
+ public function __construct($tweet = null, $escape = true, $full_encode = false)
+ {
+ if ($escape && !empty($tweet)) {
+ if ($full_encode) {
+ parent::__construct(htmlentities($tweet, ENT_QUOTES, 'UTF-8', false));
+ } else {
+ parent::__construct(htmlspecialchars($tweet, ENT_QUOTES, 'UTF-8', false));
+ }
+ } else {
+ parent::__construct($tweet);
+ }
+ $this->extractor = Extractor::create();
+ $this->url_base_user = config('app.url') . '/';
+ $this->url_base_list = config('app.url') . '/';
+ $this->url_base_hash = config('app.url') . "/discover/tags/";
+ $this->url_base_cash = config('app.url') . '/search?q=%24';
+ }
+
+ /**
+ * CSS class for auto-linked URLs.
+ *
+ * @return string CSS class for URL links.
+ */
+ public function getURLClass()
+ {
+ return $this->class_url;
+ }
+
+ /**
+ * CSS class for auto-linked URLs.
+ *
+ * @param string $v CSS class for URL links.
+ *
+ * @return Autolink Fluid method chaining.
+ */
+ public function setURLClass($v)
+ {
+ $this->class_url = trim($v);
+ return $this;
+ }
+
+ /**
+ * CSS class for auto-linked username URLs.
+ *
+ * @return string CSS class for username links.
+ */
+ public function getUsernameClass()
+ {
+ return $this->class_user;
+ }
+
+ /**
+ * CSS class for auto-linked username URLs.
+ *
+ * @param string $v CSS class for username links.
+ *
+ * @return Autolink Fluid method chaining.
+ */
+ public function setUsernameClass($v)
+ {
+ $this->class_user = trim($v);
+ return $this;
+ }
+
+ /**
+ * CSS class for auto-linked username/list URLs.
+ *
+ * @return string CSS class for username/list links.
+ */
+ public function getListClass()
+ {
+ return $this->class_list;
+ }
+
+ /**
+ * CSS class for auto-linked username/list URLs.
+ *
+ * @param string $v CSS class for username/list links.
+ *
+ * @return Autolink Fluid method chaining.
+ */
+ public function setListClass($v)
+ {
+ $this->class_list = trim($v);
+ return $this;
+ }
+
+ /**
+ * CSS class for auto-linked hashtag URLs.
+ *
+ * @return string CSS class for hashtag links.
+ */
+ public function getHashtagClass()
+ {
+ return $this->class_hash;
+ }
+
+ /**
+ * CSS class for auto-linked hashtag URLs.
+ *
+ * @param string $v CSS class for hashtag links.
+ *
+ * @return Autolink Fluid method chaining.
+ */
+ public function setHashtagClass($v)
+ {
+ $this->class_hash = trim($v);
+ return $this;
+ }
+
+ /**
+ * CSS class for auto-linked cashtag URLs.
+ *
+ * @return string CSS class for cashtag links.
+ */
+ public function getCashtagClass()
+ {
+ return $this->class_cash;
+ }
+
+ /**
+ * CSS class for auto-linked cashtag URLs.
+ *
+ * @param string $v CSS class for cashtag links.
+ *
+ * @return Autolink Fluid method chaining.
+ */
+ public function setCashtagClass($v)
+ {
+ $this->class_cash = trim($v);
+ return $this;
+ }
+
+ /**
+ * Whether to include the value 'nofollow' in the 'rel' attribute.
+ *
+ * @return bool Whether to add 'nofollow' to the 'rel' attribute.
+ */
+ public function getNoFollow()
+ {
+ return $this->nofollow;
+ }
+
+ /**
+ * Whether to include the value 'nofollow' in the 'rel' attribute.
+ *
+ * @param bool $v The value to add to the 'target' attribute.
+ *
+ * @return Autolink Fluid method chaining.
+ */
+ public function setNoFollow($v)
+ {
+ $this->nofollow = $v;
+ return $this;
+ }
+
+ /**
+ * Whether to include the value 'external' in the 'rel' attribute.
+ *
+ * Often this is used to be matched on in JavaScript for dynamically adding
+ * the 'target' attribute which is deprecated in HTML 4.01. In HTML 5 it has
+ * been undeprecated and thus the 'target' attribute can be used. If this is
+ * set to false then the 'target' attribute will be output.
+ *
+ * @return bool Whether to add 'external' to the 'rel' attribute.
+ */
+ public function getExternal()
+ {
+ return $this->external;
+ }
+
+ /**
+ * Whether to include the value 'external' in the 'rel' attribute.
+ *
+ * Often this is used to be matched on in JavaScript for dynamically adding
+ * the 'target' attribute which is deprecated in HTML 4.01. In HTML 5 it has
+ * been undeprecated and thus the 'target' attribute can be used. If this is
+ * set to false then the 'target' attribute will be output.
+ *
+ * @param bool $v The value to add to the 'target' attribute.
+ *
+ * @return Autolink Fluid method chaining.
+ */
+ public function setExternal($v)
+ {
+ $this->external = $v;
+ return $this;
+ }
+
+ /**
+ * The scope to open the link in.
+ *
+ * Support for the 'target' attribute was deprecated in HTML 4.01 but has
+ * since been reinstated in HTML 5. To output the 'target' attribute you
+ * must disable the adding of the string 'external' to the 'rel' attribute.
+ *
+ * @return string The value to add to the 'target' attribute.
+ */
+ public function getTarget()
+ {
+ return $this->target;
+ }
+
+ /**
+ * The scope to open the link in.
+ *
+ * Support for the 'target' attribute was deprecated in HTML 4.01 but has
+ * since been reinstated in HTML 5. To output the 'target' attribute you
+ * must disable the adding of the string 'external' to the 'rel' attribute.
+ *
+ * @param string $v The value to add to the 'target' attribute.
+ *
+ * @return Autolink Fluid method chaining.
+ */
+ public function setTarget($v)
+ {
+ $this->target = trim($v);
+ return $this;
+ }
+
+ /**
+ * Autolink with entities
+ *
+ * @param string $tweet
+ * @param array $entities
+ * @return string
+ * @since 1.1.0
+ */
+ public function autoLinkEntities($tweet = null, $entities = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+
+ $text = '';
+ $beginIndex = 0;
+ foreach ($entities as $entity) {
+ if (isset($entity['screen_name'])) {
+ $text .= StringUtils::substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex + 1);
+ } else {
+ $text .= StringUtils::substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex);
+ }
+
+ if (isset($entity['url'])) {
+ $text .= $this->linkToUrl($entity);
+ } elseif (isset($entity['hashtag'])) {
+ $text .= $this->linkToHashtag($entity, $tweet);
+ } elseif (isset($entity['screen_name'])) {
+ $text .= $this->linkToMentionAndList($entity);
+ } elseif (isset($entity['cashtag'])) {
+ $text .= $this->linkToCashtag($entity, $tweet);
+ }
+ $beginIndex = $entity['indices'][1];
+ }
+ $text .= StringUtils::substr($tweet, $beginIndex, StringUtils::strlen($tweet));
+ return $text;
+ }
+
+ /**
+ * Auto-link hashtags, URLs, usernames and lists, with JSON entities.
+ *
+ * @param string The tweet to be converted
+ * @param mixed The entities info
+ * @return string that auto-link HTML added
+ * @since 1.1.0
+ */
+ public function autoLinkWithJson($tweet = null, $json = null)
+ {
+ // concatenate entities
+ $entities = array();
+ if (is_object($json)) {
+ $json = $this->object2array($json);
+ }
+ if (is_array($json)) {
+ foreach ($json as $key => $vals) {
+ $entities = array_merge($entities, $json[$key]);
+ }
+ }
+
+ // map JSON entity to twitter-text entity
+ foreach ($entities as $idx => $entity) {
+ if (!empty($entity['text'])) {
+ $entities[$idx]['hashtag'] = $entity['text'];
+ }
+ }
+
+ $entities = $this->extractor->removeOverlappingEntities($entities);
+ return $this->autoLinkEntities($tweet, $entities);
+ }
+
+ /**
+ * convert Object to Array
+ *
+ * @param mixed $obj
+ * @return array
+ */
+ protected function object2array($obj)
+ {
+ $array = (array) $obj;
+ foreach ($array as $key => $var) {
+ if (is_object($var) || is_array($var)) {
+ $array[$key] = $this->object2array($var);
+ }
+ }
+ return $array;
+ }
+
+ /**
+ * Auto-link hashtags, URLs, usernames and lists.
+ *
+ * @param string The tweet to be converted
+ * @return string that auto-link HTML added
+ * @since 1.1.0
+ */
+ public function autoLink($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $entities = $this->extractor->extractURLWithoutProtocol(false)->extractEntitiesWithIndices($tweet);
+ return $this->autoLinkEntities($tweet, $entities);
+ }
+
+ /**
+ * Auto-link the @username and @username/list references in the provided text. Links to @username references will
+ * have the usernameClass CSS classes added. Links to @username/list references will have the listClass CSS class
+ * added.
+ *
+ * @return string that auto-link HTML added
+ * @since 1.1.0
+ */
+ public function autoLinkUsernamesAndLists($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $entities = $this->extractor->extractMentionsOrListsWithIndices($tweet);
+ return $this->autoLinkEntities($tweet, $entities);
+ }
+
+ /**
+ * Auto-link #hashtag references in the provided Tweet text. The #hashtag links will have the hashtagClass CSS class
+ * added.
+ *
+ * @return string that auto-link HTML added
+ * @since 1.1.0
+ */
+ public function autoLinkHashtags($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $entities = $this->extractor->extractHashtagsWithIndices($tweet);
+ return $this->autoLinkEntities($tweet, $entities);
+ }
+
+ /**
+ * Auto-link URLs in the Tweet text provided.
+ *
+ * This only auto-links URLs with protocol.
+ *
+ * @return string that auto-link HTML added
+ * @since 1.1.0
+ */
+ public function autoLinkURLs($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $entities = $this->extractor->extractURLWithoutProtocol(false)->extractURLsWithIndices($tweet);
+ return $this->autoLinkEntities($tweet, $entities);
+ }
+
+ /**
+ * Auto-link $cashtag references in the provided Tweet text. The $cashtag links will have the cashtagClass CSS class
+ * added.
+ *
+ * @return string that auto-link HTML added
+ * @since 1.1.0
+ */
+ public function autoLinkCashtags($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $entities = $this->extractor->extractCashtagsWithIndices($tweet);
+ return $this->autoLinkEntities($tweet, $entities);
+ }
+
+ public function linkToUrl($entity)
+ {
+ if (!empty($this->class_url)) {
+ $attributes['class'] = $this->class_url;
+ }
+ $attributes['href'] = $entity['url'];
+ $linkText = $this->escapeHTML($entity['url']);
+
+ if (!empty($entity['display_url']) && !empty($entity['expanded_url'])) {
+ // Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste
+ // should contain the full original URL (expanded_url), not the display URL.
+ //
+ // Method: Whenever possible, we actually emit HTML that contains expanded_url, and use
+ // font-size:0 to hide those parts that should not be displayed (because they are not part of display_url).
+ // Elements with font-size:0 get copied even though they are not visible.
+ // Note that display:none doesn't work here. Elements with display:none don't get copied.
+ //
+ // Additionally, we want to *display* ellipses, but we don't want them copied. To make this happen we
+ // wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on
+ // everything with the tco-ellipsis class.
+ //
+ // As an example: The user tweets "hi http://longdomainname.com/foo"
+ // This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo"
+ // This will get rendered as:
+ //
+ // …
+ //
+ // http://longdomai
+ //
+ //
+ // nname.com/foo
+ //
+ //
+ //
+ // …
+ //
+ //
+ // Exception: pic.socialhub.dev images, for which expandedUrl = "https://socialhub.dev/#!/username/status/1234/photo/1
+ // For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts.
+ // For a pic.socialhub.dev URL, the only elided part will be the "https://", so this is fine.
+ $displayURL = $entity['display_url'];
+ $expandedURL = $entity['expanded_url'];
+ $displayURLSansEllipses = preg_replace('/…/u', '', $displayURL);
+ $diplayURLIndexInExpandedURL = mb_strpos($expandedURL, $displayURLSansEllipses);
+
+ if ($diplayURLIndexInExpandedURL !== false) {
+ $beforeDisplayURL = mb_substr($expandedURL, 0, $diplayURLIndexInExpandedURL);
+ $afterDisplayURL = mb_substr($expandedURL, $diplayURLIndexInExpandedURL + mb_strlen($displayURLSansEllipses));
+ $precedingEllipsis = (preg_match('/\A…/u', $displayURL)) ? '…' : '';
+ $followingEllipsis = (preg_match('/…\z/u', $displayURL)) ? '…' : '';
+
+ $invisibleSpan = "invisibleTagAttrs}>";
+
+ $linkText = "{$precedingEllipsis}{$invisibleSpan} ";
+ $linkText .= "{$invisibleSpan}{$this->escapeHTML($beforeDisplayURL)}";
+ $linkText .= "{$this->escapeHTML($displayURLSansEllipses)}";
+ $linkText .= "{$invisibleSpan}{$this->escapeHTML($afterDisplayURL)}";
+ $linkText .= "{$invisibleSpan} {$followingEllipsis}";
+ } else {
+ $linkText = $entity['display_url'];
+ }
+ $attributes['title'] = $entity['expanded_url'];
+ } elseif (!empty($entity['display_url'])) {
+ $linkText = $entity['display_url'];
+ }
+
+ return $this->linkToText($entity, $linkText, $attributes);
+ }
+
+ /**
+ *
+ * @param array $entity
+ * @param string $tweet
+ * @return string
+ * @since 1.1.0
+ */
+ public function linkToHashtag($entity, $tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $this->target = false;
+ $attributes = array();
+ $class = array();
+ $hash = StringUtils::substr($tweet, $entity['indices'][0], 1);
+ $linkText = $hash . $entity['hashtag'];
+
+ $attributes['href'] = $this->url_base_hash . $entity['hashtag'] . '?src=hash';
+ $attributes['title'] = '#' . $entity['hashtag'];
+ if (!empty($this->class_hash)) {
+ $class[] = $this->class_hash;
+ }
+ if (preg_match(self::$patterns['rtl_chars'], $linkText)) {
+ $class[] = 'rtl';
+ }
+ if (!empty($class)) {
+ $attributes['class'] = join(' ', $class);
+ }
+
+ return $this->linkToText($entity, $linkText, $attributes);
+ }
+
+ /**
+ *
+ * @param array $entity
+ * @return string
+ * @since 1.1.0
+ */
+ public function linkToMentionAndList($entity)
+ {
+ $attributes = array();
+
+ if (!empty($entity['list_slug'])) {
+ # Replace the list and username
+ $linkText = $entity['screen_name'] . $entity['list_slug'];
+ $class = $this->class_list;
+ $url = $this->url_base_list . $linkText;
+ } else {
+ # Replace the username
+ $linkText = $entity['screen_name'];
+ $class = $this->class_user;
+ $url = $this->url_base_user . $linkText;
+ }
+ if (!empty($class)) {
+ $attributes['class'] = $class;
+ }
+ $attributes['href'] = $url;
+
+ return $this->linkToText($entity, $linkText, $attributes);
+ }
+
+ /**
+ *
+ * @param array $entity
+ * @param string $tweet
+ * @return string
+ * @since 1.1.0
+ */
+ public function linkToCashtag($entity, $tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $attributes = array();
+ $doller = StringUtils::substr($tweet, $entity['indices'][0], 1);
+ $linkText = $doller . $entity['cashtag'];
+ $attributes['href'] = $this->url_base_cash . $entity['cashtag'];
+ $attributes['title'] = $linkText;
+ if (!empty($this->class_cash)) {
+ $attributes['class'] = $this->class_cash;
+ }
+
+ return $this->linkToText($entity, $linkText, $attributes);
+ }
+
+ /**
+ *
+ * @param array $entity
+ * @param string $text
+ * @param array $attributes
+ * @return string
+ * @since 1.1.0
+ */
+ public function linkToText(array $entity, $text, $attributes = array())
+ {
+ $rel = array();
+ if ($this->external) {
+ $rel[] = 'external';
+ }
+ if ($this->nofollow) {
+ $rel[] = 'nofollow';
+ }
+ if ($this->noopener) {
+ $rel[] = 'noopener';
+ }
+ if (!empty($rel)) {
+ $attributes['rel'] = join(' ', $rel);
+ }
+ if ($this->target) {
+ $attributes['target'] = $this->target;
+ }
+ $link = ' $val) {
+ $link .= ' ' . $key . '="' . $this->escapeHTML($val) . '"';
+ }
+ $link .= '>' . $text . '';
+ return $link;
+ }
+
+ /**
+ * html escape
+ *
+ * @param string $text
+ * @return string
+ */
+ protected function escapeHTML($text)
+ {
+ return htmlspecialchars($text, ENT_QUOTES, 'UTF-8', false);
+ }
+}
diff --git a/app/Util/Lexer/Extractor.php b/app/Util/Lexer/Extractor.php
new file mode 100755
index 000000000..5a066985e
--- /dev/null
+++ b/app/Util/Lexer/Extractor.php
@@ -0,0 +1,548 @@
+
+ * @author Nick Pope
+ * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ */
+
+namespace App\Util\Lexer;
+
+use App\Util\Lexer\Regex;
+use App\Util\Lexer\StringUtils;
+
+/**
+ * Twitter Extractor Class
+ *
+ * Parses tweets and extracts URLs, usernames, username/list pairs and
+ * hashtags.
+ *
+ * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
+ * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
+ * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
+ *
+ * @author Mike Cochrane
+ * @author Nick Pope
+ * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ */
+class Extractor extends Regex
+{
+
+ /**
+ * @var boolean
+ */
+ protected $extractURLWithoutProtocol = true;
+
+ /**
+ * Provides fluent method chaining.
+ *
+ * @param string $tweet The tweet to be converted.
+ *
+ * @see __construct()
+ *
+ * @return Extractor
+ */
+ public static function create($tweet = null)
+ {
+ return new self($tweet);
+ }
+
+ /**
+ * Reads in a tweet to be parsed and extracts elements from it.
+ *
+ * Extracts various parts of a tweet including URLs, usernames, hashtags...
+ *
+ * @param string $tweet The tweet to extract.
+ */
+ public function __construct($tweet = null)
+ {
+ parent::__construct($tweet);
+ }
+
+ /**
+ * Extracts all parts of a tweet and returns an associative array containing
+ * the extracted elements.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array The elements in the tweet.
+ */
+ public function extract($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ return array(
+ 'hashtags' => $this->extractHashtags($tweet),
+ 'urls' => $this->extractURLs($tweet),
+ 'mentions' => $this->extractMentionedUsernames($tweet),
+ 'replyto' => $this->extractRepliedUsernames($tweet),
+ 'hashtags_with_indices' => $this->extractHashtagsWithIndices($tweet),
+ 'urls_with_indices' => $this->extractURLsWithIndices($tweet),
+ 'mentions_with_indices' => $this->extractMentionedUsernamesWithIndices($tweet),
+ );
+ }
+
+ /**
+ * Extract URLs, @mentions, lists and #hashtag from a given text/tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array list of extracted entities
+ */
+ public function extractEntitiesWithIndices($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $entities = array();
+ $entities = array_merge($entities, $this->extractURLsWithIndices($tweet));
+ $entities = array_merge($entities, $this->extractHashtagsWithIndices($tweet, false));
+ $entities = array_merge($entities, $this->extractMentionsOrListsWithIndices($tweet));
+ $entities = array_merge($entities, $this->extractCashtagsWithIndices($tweet));
+ $entities = $this->removeOverlappingEntities($entities);
+ return $entities;
+ }
+
+ /**
+ * Extracts all the hashtags from the tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array The hashtag elements in the tweet.
+ */
+ public function extractHashtags($tweet = null)
+ {
+ $hashtagsOnly = array();
+ $hashtagsWithIndices = $this->extractHashtagsWithIndices($tweet);
+
+ foreach ($hashtagsWithIndices as $hashtagWithIndex) {
+ $hashtagsOnly[] = $hashtagWithIndex['hashtag'];
+ }
+ return $hashtagsOnly;
+ }
+
+ /**
+ * Extracts all the cashtags from the tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array The cashtag elements in the tweet.
+ */
+ public function extractCashtags($tweet = null)
+ {
+ $cashtagsOnly = array();
+ $cashtagsWithIndices = $this->extractCashtagsWithIndices($tweet);
+
+ foreach ($cashtagsWithIndices as $cashtagWithIndex) {
+ $cashtagsOnly[] = $cashtagWithIndex['cashtag'];
+ }
+ return $cashtagsOnly;
+ }
+
+ /**
+ * Extracts all the URLs from the tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array The URL elements in the tweet.
+ */
+ public function extractURLs($tweet = null)
+ {
+ $urlsOnly = array();
+ $urlsWithIndices = $this->extractURLsWithIndices($tweet);
+
+ foreach ($urlsWithIndices as $urlWithIndex) {
+ $urlsOnly[] = $urlWithIndex['url'];
+ }
+ return $urlsOnly;
+ }
+
+ /**
+ * Extract all the usernames from the tweet.
+ *
+ * A mention is an occurrence of a username anywhere in a tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array The usernames elements in the tweet.
+ */
+ public function extractMentionedScreennames($tweet = null)
+ {
+ $usernamesOnly = array();
+ $mentionsWithIndices = $this->extractMentionsOrListsWithIndices($tweet);
+
+ foreach ($mentionsWithIndices as $mentionWithIndex) {
+ $screen_name = mb_strtolower($mentionWithIndex['screen_name']);
+ if (empty($screen_name) OR in_array($screen_name, $usernamesOnly)) {
+ continue;
+ }
+ $usernamesOnly[] = $screen_name;
+ }
+ return $usernamesOnly;
+ }
+
+ /**
+ * Extract all the usernames from the tweet.
+ *
+ * A mention is an occurrence of a username anywhere in a tweet.
+ *
+ * @return array The usernames elements in the tweet.
+ * @deprecated since version 1.1.0
+ */
+ public function extractMentionedUsernames($tweet)
+ {
+ $this->tweet = $tweet;
+ return $this->extractMentionedScreennames($tweet);
+ }
+
+ /**
+ * Extract all the usernames replied to from the tweet.
+ *
+ * A reply is an occurrence of a username at the beginning of a tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array The usernames replied to in a tweet.
+ */
+ public function extractReplyScreenname($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $matched = preg_match(self::$patterns['valid_reply'], $tweet, $matches);
+ # Check username ending in
+ if ($matched && preg_match(self::$patterns['end_mention_match'], $matches[2])) {
+ $matched = false;
+ }
+ return $matched ? $matches[1] : null;
+ }
+
+ /**
+ * Extract all the usernames replied to from the tweet.
+ *
+ * A reply is an occurrence of a username at the beginning of a tweet.
+ *
+ * @return array The usernames replied to in a tweet.
+ * @deprecated since version 1.1.0
+ */
+ public function extractRepliedUsernames()
+ {
+ return $this->extractReplyScreenname();
+ }
+
+ /**
+ * Extracts all the hashtags and the indices they occur at from the tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @param boolean $checkUrlOverlap if true, check if extracted hashtags overlap URLs and remove overlapping ones
+ * @return array The hashtag elements in the tweet.
+ */
+ public function extractHashtagsWithIndices($tweet = null, $checkUrlOverlap = true)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+
+ if (!preg_match('/[##]/iu', $tweet)) {
+ return array();
+ }
+
+ preg_match_all(self::$patterns['valid_hashtag'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
+ $tags = array();
+
+ foreach ($matches as $match) {
+ list($all, $before, $hash, $hashtag, $outer) = array_pad($match, 3, array('', 0));
+ $start_position = $hash[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $hash[1])) : $hash[1];
+ $end_position = $start_position + StringUtils::strlen($hash[0] . $hashtag[0]);
+
+ if (preg_match(self::$patterns['end_hashtag_match'], $outer[0])) {
+ continue;
+ }
+
+ $tags[] = array(
+ 'hashtag' => $hashtag[0],
+ 'indices' => array($start_position, $end_position)
+ );
+ }
+
+ if (!$checkUrlOverlap) {
+ return $tags;
+ }
+
+ # check url overlap
+ $urls = $this->extractURLsWithIndices($tweet);
+ $entities = $this->removeOverlappingEntities(array_merge($tags, $urls));
+
+ $validTags = array();
+ foreach ($entities as $entity) {
+ if (empty($entity['hashtag'])) {
+ continue;
+ }
+ $validTags[] = $entity;
+ }
+
+ return $validTags;
+ }
+
+ /**
+ * Extracts all the cashtags and the indices they occur at from the tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array The cashtag elements in the tweet.
+ */
+ public function extractCashtagsWithIndices($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+
+ if (!preg_match('/\$/iu', $tweet)) {
+ return array();
+ }
+
+ preg_match_all(self::$patterns['valid_cashtag'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
+ $tags = array();
+
+ foreach ($matches as $match) {
+ list($all, $before, $dollar, $cash_text, $outer) = array_pad($match, 3, array('', 0));
+ $start_position = $dollar[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $dollar[1])) : $dollar[1];
+ $end_position = $start_position + StringUtils::strlen($dollar[0] . $cash_text[0]);
+
+ if (preg_match(self::$patterns['end_hashtag_match'], $outer[0])) {
+ continue;
+ }
+
+ $tags[] = array(
+ 'cashtag' => $cash_text[0],
+ 'indices' => array($start_position, $end_position)
+ );
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Extracts all the URLs and the indices they occur at from the tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array The URLs elements in the tweet.
+ */
+ public function extractURLsWithIndices($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+
+ $needle = $this->extractURLWithoutProtocol() ? '.' : ':';
+ if (strpos($tweet, $needle) === false) {
+ return array();
+ }
+
+ $urls = array();
+ preg_match_all(self::$patterns['valid_url'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
+
+ foreach ($matches as $match) {
+ list($all, $before, $url, $protocol, $domain, $port, $path, $query) = array_pad($match, 8, array(''));
+ $start_position = $url[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $url[1])) : $url[1];
+ $end_position = $start_position + StringUtils::strlen($url[0]);
+
+ $all = $all[0];
+ $before = $before[0];
+ $url = $url[0];
+ $protocol = $protocol[0];
+ $domain = $domain[0];
+ $port = $port[0];
+ $path = $path[0];
+ $query = $query[0];
+
+ // If protocol is missing and domain contains non-ASCII characters,
+ // extract ASCII-only domains.
+ if (empty($protocol)) {
+ if (!$this->extractURLWithoutProtocol || preg_match(self::$patterns['invalid_url_without_protocol_preceding_chars'], $before)) {
+ continue;
+ }
+
+ $last_url = null;
+ $ascii_end_position = 0;
+
+ if (preg_match(self::$patterns['valid_ascii_domain'], $domain, $asciiDomain)) {
+ $asciiDomain[0] = preg_replace('/' . preg_quote($domain, '/') . '/u', $asciiDomain[0], $url);
+ $ascii_start_position = StringUtils::strpos($domain, $asciiDomain[0], $ascii_end_position);
+ $ascii_end_position = $ascii_start_position + StringUtils::strlen($asciiDomain[0]);
+ $last_url = array(
+ 'url' => $asciiDomain[0],
+ 'indices' => array($start_position + $ascii_start_position, $start_position + $ascii_end_position),
+ );
+ if (!empty($path)
+ || preg_match(self::$patterns['valid_special_short_domain'], $asciiDomain[0])
+ || !preg_match(self::$patterns['invalid_short_domain'], $asciiDomain[0])) {
+ $urls[] = $last_url;
+ }
+ }
+
+ // no ASCII-only domain found. Skip the entire URL
+ if (empty($last_url)) {
+ continue;
+ }
+
+ // $last_url only contains domain. Need to add path and query if they exist.
+ if (!empty($path)) {
+ // last_url was not added. Add it to urls here.
+ $last_url['url'] = preg_replace('/' . preg_quote($domain, '/') . '/u', $last_url['url'], $url);
+ $last_url['indices'][1] = $end_position;
+ }
+ } else {
+ // In the case of t.co URLs, don't allow additional path characters
+ if (preg_match(self::$patterns['valid_tco_url'], $url, $tcoUrlMatches)) {
+ $url = $tcoUrlMatches[0];
+ $end_position = $start_position + StringUtils::strlen($url);
+ }
+ $urls[] = array(
+ 'url' => $url,
+ 'indices' => array($start_position, $end_position),
+ );
+ }
+ }
+
+ return $urls;
+ }
+
+ /**
+ * Extracts all the usernames and the indices they occur at from the tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array The username elements in the tweet.
+ */
+ public function extractMentionedScreennamesWithIndices($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+
+ $usernamesOnly = array();
+ $mentions = $this->extractMentionsOrListsWithIndices($tweet);
+ foreach ($mentions as $mention) {
+ if (isset($mention['list_slug'])) {
+ unset($mention['list_slug']);
+ }
+ $usernamesOnly[] = $mention;
+ }
+ return $usernamesOnly;
+ }
+
+ /**
+ * Extracts all the usernames and the indices they occur at from the tweet.
+ *
+ * @return array The username elements in the tweet.
+ * @deprecated since version 1.1.0
+ */
+ public function extractMentionedUsernamesWithIndices()
+ {
+ return $this->extractMentionedScreennamesWithIndices();
+ }
+
+ /**
+ * Extracts all the usernames and the indices they occur at from the tweet.
+ *
+ * @param string $tweet The tweet to extract.
+ * @return array The username elements in the tweet.
+ */
+ public function extractMentionsOrListsWithIndices($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+
+ if (!preg_match('/[@@]/iu', $tweet)) {
+ return array();
+ }
+
+ preg_match_all(self::$patterns['valid_mentions_or_lists'], $tweet, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
+ $results = array();
+
+ foreach ($matches as $match) {
+ list($all, $before, $at, $username, $list_slug, $outer) = array_pad($match, 6, array('', 0));
+ $start_position = $at[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $at[1])) : $at[1];
+ $end_position = $start_position + StringUtils::strlen($at[0]) + StringUtils::strlen($username[0]);
+ $entity = array(
+ 'screen_name' => $username[0],
+ 'list_slug' => $list_slug[0],
+ 'indices' => array($start_position, $end_position),
+ );
+
+ if (preg_match(self::$patterns['end_mention_match'], $outer[0])) {
+ continue;
+ }
+
+ if (!empty($list_slug[0])) {
+ $entity['indices'][1] = $end_position + StringUtils::strlen($list_slug[0]);
+ }
+
+ $results[] = $entity;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Extracts all the usernames and the indices they occur at from the tweet.
+ *
+ * @return array The username elements in the tweet.
+ * @deprecated since version 1.1.0
+ */
+ public function extractMentionedUsernamesOrListsWithIndices()
+ {
+ return $this->extractMentionsOrListsWithIndices();
+ }
+
+ /**
+ * setter/getter for extractURLWithoutProtocol
+ *
+ * @param boolean $flag
+ * @return Extractor
+ */
+ public function extractURLWithoutProtocol($flag = null)
+ {
+ if (is_null($flag)) {
+ return $this->extractURLWithoutProtocol;
+ }
+ $this->extractURLWithoutProtocol = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Remove overlapping entities.
+ * This returns a new array with no overlapping entities.
+ *
+ * @param array $entities
+ * @return array
+ */
+ public function removeOverlappingEntities($entities)
+ {
+ $result = array();
+ usort($entities, array($this, 'sortEntites'));
+
+ $prev = null;
+ foreach ($entities as $entity) {
+ if (isset($prev) && $entity['indices'][0] < $prev['indices'][1]) {
+ continue;
+ }
+ $prev = $entity;
+ $result[] = $entity;
+ }
+ return $result;
+ }
+
+ /**
+ * sort by entity start index
+ *
+ * @param array $a
+ * @param array $b
+ * @return int
+ */
+ protected function sortEntites($a, $b)
+ {
+ if ($a['indices'][0] == $b['indices'][0]) {
+ return 0;
+ }
+ return ($a['indices'][0] < $b['indices'][0]) ? -1 : 1;
+ }
+}
diff --git a/app/Util/Lexer/Hashtag.php b/app/Util/Lexer/Hashtag.php
index 9f19ed897..3b27f0dd7 100644
--- a/app/Util/Lexer/Hashtag.php
+++ b/app/Util/Lexer/Hashtag.php
@@ -7,7 +7,7 @@ class Hashtag {
public static function getHashtags($status)
{
$hashtags = false;
- preg_match_all("/(#\w+)/u", $status, $matches);
+ preg_match_all("/(?
+ * @copyright Copyright © 2010, Nick Pope
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ */
+
+namespace App\Util\Lexer;
+
+use App\Util\Lexer\Regex;
+use App\Util\Lexer\StringUtils;
+
+/**
+ * Twitter HitHighlighter Class
+ *
+ * Performs "hit highlighting" on tweets that have been auto-linked already.
+ * Useful with the results returned from the search API.
+ *
+ * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
+ * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
+ * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
+ *
+ * @author Nick Pope
+ * @copyright Copyright © 2010, Nick Pope
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ */
+class HitHighlighter extends Regex
+{
+
+ /**
+ * The tag to surround hits with.
+ *
+ * @var string
+ */
+ protected $tag = 'em';
+
+ /**
+ * Provides fluent method chaining.
+ *
+ * @param string $tweet The tweet to be hit highlighted.
+ * @param bool $full_encode Whether to encode all special characters.
+ *
+ * @see __construct()
+ *
+ * @return HitHighlighter
+ */
+ public static function create($tweet = null, $full_encode = false)
+ {
+ return new self($tweet, $full_encode);
+ }
+
+ /**
+ * Reads in a tweet to be parsed and hit highlighted.
+ *
+ * We take this opportunity to ensure that we escape user input.
+ *
+ * @see htmlspecialchars()
+ *
+ * @param string $tweet The tweet to be hit highlighted.
+ * @param bool $escape Whether to escape the tweet (default: true).
+ * @param bool $full_encode Whether to encode all special characters.
+ */
+ public function __construct($tweet = null, $escape = true, $full_encode = false)
+ {
+ if (!empty($tweet) && $escape) {
+ if ($full_encode) {
+ parent::__construct(htmlentities($tweet, ENT_QUOTES, 'UTF-8', false));
+ } else {
+ parent::__construct(htmlspecialchars($tweet, ENT_QUOTES, 'UTF-8', false));
+ }
+ } else {
+ parent::__construct($tweet);
+ }
+ }
+
+ /**
+ * Set the highlighting tag to surround hits with. The default tag is 'em'.
+ *
+ * @return string The tag name.
+ */
+ public function getTag()
+ {
+ return $this->tag;
+ }
+
+ /**
+ * Set the highlighting tag to surround hits with. The default tag is 'em'.
+ *
+ * @param string $v The tag name.
+ *
+ * @return HitHighlighter Fluid method chaining.
+ */
+ public function setTag($v)
+ {
+ $this->tag = $v;
+ return $this;
+ }
+
+ /**
+ * Hit highlights the tweet.
+ *
+ * @param string $tweet The tweet to be hit highlighted.
+ * @param array $hits An array containing the start and end index pairs
+ * for the highlighting.
+ * @param bool $escape Whether to escape the tweet (default: true).
+ * @param bool $full_encode Whether to encode all special characters.
+ *
+ * @return string The hit highlighted tweet.
+ */
+ public function highlight($tweet = null, array $hits = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ if (empty($hits)) {
+ return $tweet;
+ }
+ $highlightTweet = '';
+ $tags = array('<' . $this->tag . '>', '' . $this->tag . '>');
+ # Check whether we can simply replace or whether we need to chunk...
+ if (strpos($tweet, '<') === false) {
+ $ti = 0; // tag increment (for added tags)
+ $highlightTweet = $tweet;
+ foreach ($hits as $hit) {
+ $highlightTweet = StringUtils::substrReplace($highlightTweet, $tags[0], $hit[0] + $ti, 0);
+ $ti += StringUtils::strlen($tags[0]);
+ $highlightTweet = StringUtils::substrReplace($highlightTweet, $tags[1], $hit[1] + $ti, 0);
+ $ti += StringUtils::strlen($tags[1]);
+ }
+ } else {
+ $chunks = preg_split('/[<>]/iu', $tweet);
+ $chunk = $chunks[0];
+ $chunk_index = 0;
+ $chunk_cursor = 0;
+ $offset = 0;
+ $start_in_chunk = false;
+ # Flatten the multidimensional hits array:
+ $hits_flat = array();
+ foreach ($hits as $hit) {
+ $hits_flat = array_merge($hits_flat, $hit);
+ }
+ # Loop over the hit indices:
+ for ($index = 0; $index < count($hits_flat); $index++) {
+ $hit = $hits_flat[$index];
+ $tag = $tags[$index % 2];
+ $placed = false;
+ while ($chunk !== null && $hit >= ($i = $offset + StringUtils::strlen($chunk))) {
+ $highlightTweet .= StringUtils::substr($chunk, $chunk_cursor);
+ if ($start_in_chunk && $hit === $i) {
+ $highlightTweet .= $tag;
+ $placed = true;
+ }
+ if (isset($chunks[$chunk_index + 1])) {
+ $highlightTweet .= '<' . $chunks[$chunk_index + 1] . '>';
+ }
+ $offset += StringUtils::strlen($chunk);
+ $chunk_cursor = 0;
+ $chunk_index += 2;
+ $chunk = (isset($chunks[$chunk_index]) ? $chunks[$chunk_index] : null);
+ $start_in_chunk = false;
+ }
+ if (!$placed && $chunk !== null) {
+ $hit_spot = $hit - $offset;
+ $highlightTweet .= StringUtils::substr($chunk, $chunk_cursor, $hit_spot - $chunk_cursor) . $tag;
+ $chunk_cursor = $hit_spot;
+ $start_in_chunk = ($index % 2 === 0);
+ $placed = true;
+ }
+ # Ultimate fallback - hits that run off the end get a closing tag:
+ if (!$placed) {
+ $highlightTweet .= $tag;
+ }
+ }
+ if ($chunk !== null) {
+ if ($chunk_cursor < StringUtils::strlen($chunk)) {
+ $highlightTweet .= StringUtils::substr($chunk, $chunk_cursor);
+ }
+ for ($index = $chunk_index + 1; $index < count($chunks); $index++) {
+ $highlightTweet .= ($index % 2 === 0 ? $chunks[$index] : '<' . $chunks[$index] . '>');
+ }
+ }
+ }
+ return $highlightTweet;
+ }
+
+ /**
+ * Hit highlights the tweet.
+ *
+ * @param array $hits An array containing the start and end index pairs
+ * for the highlighting.
+ *
+ * @return string The hit highlighted tweet.
+ * @deprecated since version 1.1.0
+ */
+ public function addHitHighlighting(array $hits)
+ {
+ return $this->highlight($this->tweet, $hits);
+ }
+}
diff --git a/app/Util/Lexer/LooseAutolink.php b/app/Util/Lexer/LooseAutolink.php
new file mode 100755
index 000000000..979b0d0b0
--- /dev/null
+++ b/app/Util/Lexer/LooseAutolink.php
@@ -0,0 +1,348 @@
+
+ * @author Nick Pope
+ * @author Takashi Nojima
+ * @copyright Copyright 2014 Mike Cochrane, Nick Pope, Takashi Nojima
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ */
+
+namespace App\Util\Lexer;
+
+use App\Util\Lexer\Autolink;
+
+/**
+ * Twitter LooseAutolink Class
+ *
+ * Parses tweets and generates HTML anchor tags around URLs, usernames,
+ * username/list pairs and hashtags.
+ *
+ * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
+ * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
+ * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
+ *
+ * @author Mike Cochrane
+ * @author Nick Pope
+ * @author Takashi Nojima
+ * @copyright Copyright 2014 Mike Cochrane, Nick Pope, Takashi Nojima
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ * @since 1.8.0
+ * @deprecated since version 1.9.0
+ */
+class LooseAutolink extends Autolink
+{
+
+ /**
+ * Auto-link hashtags, URLs, usernames and lists.
+ *
+ * @param string The tweet to be converted
+ * @return string that auto-link HTML added
+ * @deprecated since version 1.9.0
+ */
+ public function autoLink($tweet = null)
+ {
+ if (!is_null($tweet)) {
+ $this->tweet = $tweet;
+ }
+ return $this->addLinks();
+ }
+
+ /**
+ * Auto-link the @username and @username/list references in the provided text. Links to @username references will
+ * have the usernameClass CSS classes added. Links to @username/list references will have the listClass CSS class
+ * added.
+ *
+ * @return string that auto-link HTML added
+ */
+ public function autoLinkUsernamesAndLists($tweet = null)
+ {
+ if (!is_null($tweet)) {
+ $this->tweet = $tweet;
+ }
+ return $this->addLinksToUsernamesAndLists();
+ }
+
+ /**
+ * Auto-link #hashtag references in the provided Tweet text. The #hashtag links will have the hashtagClass CSS class
+ * added.
+ *
+ * @return string that auto-link HTML added
+ */
+ public function autoLinkHashtags($tweet = null)
+ {
+ if (!is_null($tweet)) {
+ $this->tweet = $tweet;
+ }
+ return $this->addLinksToHashtags();
+ }
+
+ /**
+ * Auto-link URLs in the Tweet text provided.
+ *
+ * This only auto-links URLs with protocol.
+ *
+ * @return string that auto-link HTML added
+ */
+ public function autoLinkURLs($tweet = null)
+ {
+ if (!is_null($tweet)) {
+ $this->tweet = $tweet;
+ }
+ return $this->addLinksToURLs();
+ }
+
+ /**
+ * Auto-link $cashtag references in the provided Tweet text. The $cashtag links will have the cashtagClass CSS class
+ * added.
+ *
+ * @return string that auto-link HTML added
+ */
+ public function autoLinkCashtags($tweet = null)
+ {
+ if (!is_null($tweet)) {
+ $this->tweet = $tweet;
+ }
+ return $this->addLinksToCashtags();
+ }
+
+ /**
+ * Adds links to all elements in the tweet.
+ *
+ * @return string The modified tweet.
+ * @deprecated since version 1.9.0
+ */
+ public function addLinks()
+ {
+ $original = $this->tweet;
+ $this->tweet = $this->addLinksToURLs();
+ $this->tweet = $this->addLinksToHashtags();
+ $this->tweet = $this->addLinksToCashtags();
+ $this->tweet = $this->addLinksToUsernamesAndLists();
+ $modified = $this->tweet;
+ $this->tweet = $original;
+ return $modified;
+ }
+
+ /**
+ * Adds links to hashtag elements in the tweet.
+ *
+ * @return string The modified tweet.
+ */
+ public function addLinksToHashtags()
+ {
+ return preg_replace_callback(
+ self::$patterns['valid_hashtag'],
+ array($this, '_addLinksToHashtags'),
+ $this->tweet
+ );
+ }
+
+ /**
+ * Adds links to cashtag elements in the tweet.
+ *
+ * @return string The modified tweet.
+ */
+ public function addLinksToCashtags()
+ {
+ return preg_replace_callback(
+ self::$patterns['valid_cashtag'],
+ array($this, '_addLinksToCashtags'),
+ $this->tweet
+ );
+ }
+
+ /**
+ * Adds links to URL elements in the tweet.
+ *
+ * @return string The modified tweet
+ */
+ public function addLinksToURLs()
+ {
+ return preg_replace_callback(self::$patterns['valid_url'], array($this, '_addLinksToURLs'), $this->tweet);
+ }
+
+ /**
+ * Adds links to username/list elements in the tweet.
+ *
+ * @return string The modified tweet.
+ */
+ public function addLinksToUsernamesAndLists()
+ {
+ return preg_replace_callback(
+ self::$patterns['valid_mentions_or_lists'],
+ array($this, '_addLinksToUsernamesAndLists'),
+ $this->tweet
+ );
+ }
+
+ /**
+ * Wraps a tweet element in an HTML anchor tag using the provided URL.
+ *
+ * This is a helper function to perform the generation of the link.
+ *
+ * @param string $url The URL to use as the href.
+ * @param string $class The CSS class(es) to apply (space separated).
+ * @param string $element The tweet element to wrap.
+ *
+ * @return string The tweet element with a link applied.
+ * @deprecated since version 1.1.0
+ */
+ protected function wrap($url, $class, $element)
+ {
+ $link = 'external) {
+ $rel[] = 'external';
+ }
+ if ($this->nofollow) {
+ $rel[] = 'nofollow';
+ }
+ if (!empty($rel)) {
+ $link .= ' rel="' . implode(' ', $rel) . '"';
+ }
+ if ($this->target) {
+ $link .= ' target="' . $this->target . '"';
+ }
+ $link .= '>' . $element . '';
+ return $link;
+ }
+
+ /**
+ * Wraps a tweet element in an HTML anchor tag using the provided URL.
+ *
+ * This is a helper function to perform the generation of the hashtag link.
+ *
+ * @param string $url The URL to use as the href.
+ * @param string $class The CSS class(es) to apply (space separated).
+ * @param string $element The tweet element to wrap.
+ *
+ * @return string The tweet element with a link applied.
+ */
+ protected function wrapHash($url, $class, $element)
+ {
+ $title = preg_replace('/#/u', '#', $element);
+ $link = 'external) {
+ $rel[] = 'external';
+ }
+ if ($this->nofollow) {
+ $rel[] = 'nofollow';
+ }
+ if (!empty($rel)) {
+ $link .= ' rel="' . implode(' ', $rel) . '"';
+ }
+ if ($this->target) {
+ $link .= ' target="' . $this->target . '"';
+ }
+ $link .= '>' . $element . '';
+ return $link;
+ }
+
+ /**
+ * Callback used by the method that adds links to hashtags.
+ *
+ * @see addLinksToHashtags()
+ * @param array $matches The regular expression matches.
+ * @return string The link-wrapped hashtag.
+ */
+ protected function _addLinksToHashtags($matches)
+ {
+ list($all, $before, $hash, $tag, $after) = array_pad($matches, 5, '');
+ if (preg_match(self::$patterns['end_hashtag_match'], $after)
+ || (!preg_match('!\A["\']!', $before) && preg_match('!\A["\']!', $after)) || preg_match('!\A!', $after)) {
+ return $all;
+ }
+ $replacement = $before;
+ $element = $hash . $tag;
+ $url = $this->url_base_hash . $tag;
+ $class_hash = $this->class_hash;
+ if (preg_match(self::$patterns['rtl_chars'], $element)) {
+ $class_hash .= ' rtl';
+ }
+ $replacement .= $this->wrapHash($url, $class_hash, $element);
+ return $replacement;
+ }
+
+ /**
+ * Callback used by the method that adds links to cashtags.
+ *
+ * @see addLinksToCashtags()
+ * @param array $matches The regular expression matches.
+ * @return string The link-wrapped cashtag.
+ */
+ protected function _addLinksToCashtags($matches)
+ {
+ list($all, $before, $cash, $tag, $after) = array_pad($matches, 5, '');
+ if (preg_match(self::$patterns['end_cashtag_match'], $after)
+ || (!preg_match('!\A["\']!', $before) && preg_match('!\A["\']!', $after)) || preg_match('!\A!', $after)) {
+ return $all;
+ }
+ $replacement = $before;
+ $element = $cash . $tag;
+ $url = $this->url_base_cash . $tag;
+ $replacement .= $this->wrapHash($url, $this->class_cash, $element);
+ return $replacement;
+ }
+
+ /**
+ * Callback used by the method that adds links to URLs.
+ *
+ * @see addLinksToURLs()
+ * @param array $matches The regular expression matches.
+ * @return string The link-wrapped URL.
+ */
+ protected function _addLinksToURLs($matches)
+ {
+ list($all, $before, $url, $protocol, $domain, $path, $query) = array_pad($matches, 7, '');
+ $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8', false);
+ if (!$protocol) {
+ return $all;
+ }
+ return $before . $this->wrap($url, $this->class_url, $url);
+ }
+
+ /**
+ * Callback used by the method that adds links to username/list pairs.
+ *
+ * @see addLinksToUsernamesAndLists()
+ * @param array $matches The regular expression matches.
+ * @return string The link-wrapped username/list pair.
+ */
+ protected function _addLinksToUsernamesAndLists($matches)
+ {
+ list($all, $before, $at, $username, $slash_listname, $after) = array_pad($matches, 6, '');
+ # If $after is not empty, there is an invalid character.
+ if (!empty($slash_listname)) {
+ # Replace the list and username
+ $element = $username . $slash_listname;
+ $class = $this->class_list;
+ $url = $this->url_base_list . $element;
+ } else {
+ if (preg_match(self::$patterns['end_mention_match'], $after)) {
+ return $all;
+ }
+ # Replace the username
+ $element = $username;
+ $class = $this->class_user;
+ $url = $this->url_base_user . $element;
+ }
+ # XXX: Due to use of preg_replace_callback() for multiple replacements in a
+ # single tweet and also as only the match is replaced and we have to
+ # use a look-ahead for $after because there is no equivalent for the
+ # $' (dollar apostrophe) global from Ruby, we MUST NOT append $after.
+ return $before . $at . $this->wrap($url, $class, $element);
+ }
+}
diff --git a/app/Util/Lexer/PrettyNumber.php b/app/Util/Lexer/PrettyNumber.php
new file mode 100644
index 000000000..23d7ab267
--- /dev/null
+++ b/app/Util/Lexer/PrettyNumber.php
@@ -0,0 +1,36 @@
+ "T", 9 => "B", 6 => "M", 3 => "K", 0 => "");
+ foreach($abbrevs as $exponent => $abbrev) {
+ if($expression >= pow(10, $exponent)) {
+ $display_num = $expression / pow(10, $exponent);
+ $num = number_format($display_num,0) . $abbrev;
+ return $num;
+ }
+ }
+ return $expression;
+ }
+
+ public static function size($expression, $kb = false)
+ {
+ if($kb) {
+ $expression = $expression * 1024;
+ }
+ $size = intval($expression);
+ $precision = 0;
+ $short = true;
+ $units = $short ?
+ ['B','k','M','G','T','P','E','Z','Y'] :
+ ['B','kB','MB','GB','TB','PB','EB','ZB','YB'];
+ for($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {}
+ $res = round($size, $precision).$units[$i];
+ return $res;
+ }
+
+}
\ No newline at end of file
diff --git a/app/Util/Lexer/Regex.php b/app/Util/Lexer/Regex.php
new file mode 100755
index 000000000..7c1f0627b
--- /dev/null
+++ b/app/Util/Lexer/Regex.php
@@ -0,0 +1,337 @@
+
+ * @author Nick Pope
+ * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ */
+
+namespace App\Util\Lexer;
+
+/**
+ * Twitter Regex Abstract Class
+ *
+ * Used by subclasses that need to parse tweets.
+ *
+ * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
+ * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
+ * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
+ *
+ * @author Mike Cochrane
+ * @author Nick Pope
+ * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter
+ */
+abstract class Regex
+{
+
+ /**
+ * Contains all generated regular expressions.
+ *
+ * @var string The regex patterns.
+ */
+ protected static $patterns = array();
+
+ /**
+ * The tweet to be used in parsing. This should be populated by the
+ * constructor of all subclasses.
+ *
+ * @var string
+ */
+ protected $tweet = '';
+
+ /**
+ * This constructor is used to populate some variables.
+ *
+ * @param string $tweet The tweet to parse.
+ */
+ protected function __construct($tweet = null)
+ {
+ $this->tweet = $tweet;
+ }
+
+ /**
+ * Emulate a static initialiser while PHP doesn't have one.
+ */
+ public static function __static()
+ {
+ # Check whether we have initialized the regular expressions:
+ static $initialized = false;
+ if ($initialized) {
+ return;
+ }
+ # Get a shorter reference to the regular expression array:
+ $re = & self::$patterns;
+ # Initialise local storage arrays:
+ $tmp = array();
+
+ # Expression to match whitespace characters.
+ #
+ # 0x0009-0x000D Cc # ..
+ # 0x0020 Zs # SPACE
+ # 0x0085 Cc #
+ # 0x00A0 Zs # NO-BREAK SPACE
+ # 0x1680 Zs # OGHAM SPACE MARK
+ # 0x180E Zs # MONGOLIAN VOWEL SEPARATOR
+ # 0x2000-0x200A Zs # EN QUAD..HAIR SPACE
+ # 0x2028 Zl # LINE SEPARATOR
+ # 0x2029 Zp # PARAGRAPH SEPARATOR
+ # 0x202F Zs # NARROW NO-BREAK SPACE
+ # 0x205F Zs # MEDIUM MATHEMATICAL SPACE
+ # 0x3000 Zs # IDEOGRAPHIC SPACE
+ $tmp['spaces'] = '\x{0009}-\x{000D}\x{0020}\x{0085}\x{00a0}\x{1680}\x{180E}\x{2000}-\x{200a}\x{2028}\x{2029}\x{202f}\x{205f}\x{3000}';
+
+ # Invalid Characters:
+ # 0xFFFE,0xFEFF # BOM
+ # 0xFFFF # Special
+ # 0x202A-0x202E # Directional change
+ $tmp['invalid_characters'] = '\x{202a}-\x{202e}\x{feff}\x{fffe}\x{ffff}';
+
+ # Expression to match at and hash sign characters:
+ $tmp['at_signs'] = '@@';
+ $tmp['hash_signs'] = '##';
+
+ # Expression to match latin accented characters.
+ #
+ # 0x00C0-0x00D6
+ # 0x00D8-0x00F6
+ # 0x00F8-0x00FF
+ # 0x0100-0x024f
+ # 0x0253-0x0254
+ # 0x0256-0x0257
+ # 0x0259
+ # 0x025b
+ # 0x0263
+ # 0x0268
+ # 0x026f
+ # 0x0272
+ # 0x0289
+ # 0x028b
+ # 0x02bb
+ # 0x0300-0x036f
+ # 0x1e00-0x1eff
+ #
+ # Excludes 0x00D7 - multiplication sign (confusable with 'x').
+ # Excludes 0x00F7 - division sign.
+ $tmp['latin_accents'] = '\x{00c0}-\x{00d6}\x{00d8}-\x{00f6}\x{00f8}-\x{00ff}';
+ $tmp['latin_accents'] .= '\x{0100}-\x{024f}\x{0253}-\x{0254}\x{0256}-\x{0257}';
+ $tmp['latin_accents'] .= '\x{0259}\x{025b}\x{0263}\x{0268}\x{026f}\x{0272}\x{0289}\x{028b}\x{02bb}\x{0300}-\x{036f}\x{1e00}-\x{1eff}';
+
+ # Expression to match RTL characters.
+ #
+ # 0x0600-0x06FF Arabic
+ # 0x0750-0x077F Arabic Supplement
+ # 0x08A0-0x08FF Arabic Extended-A
+ # 0x0590-0x05FF Hebrew
+ # 0xFB50-0xFDFF Arabic Presentation Forms-A
+ # 0xFE70-0xFEFF Arabic Presentation Forms-B
+ $tmp['rtl_chars'] = '\x{0600}-\x{06ff}\x{0750}-\x{077f}\x{08a0}-\x{08ff}\x{0590}-\x{05ff}\x{fb50}-\x{fdff}\x{fe70}-\x{feff}';
+
+ $tmp['hashtag_letters'] = '\p{L}\p{M}';
+ $tmp['hashtag_numerals'] = '\p{Nd}';
+ # Hashtag special chars
+ #
+ # _ underscore
+ # 0x200c ZERO WIDTH NON-JOINER (ZWNJ)
+ # 0x200d ZERO WIDTH JOINER (ZWJ)
+ # 0xa67e CYRILLIC KAVYKA
+ # 0x05be HEBREW PUNCTUATION MAQAF
+ # 0x05f3 HEBREW PUNCTUATION GERESH
+ # 0x05f4 HEBREW PUNCTUATION GERSHAYIM
+ # 0xff5e FULLWIDTH TILDE
+ # 0x301c WAVE DASH
+ # 0x309b KATAKANA-HIRAGANA VOICED SOUND MARK
+ # 0x309c KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+ # 0x30a0 KATAKANA-HIRAGANA DOUBLE HYPHEN
+ # 0x30fb KATAKANA MIDDLE DOT
+ # 0x3003 DITTO MARK
+ # 0x0f0b TIBETAN MARK INTERSYLLABIC TSHEG
+ # 0x0f0c TIBETAN MARK DELIMITER TSHEG BSTAR
+ # 0x00b7 MIDDLE DOT
+ $tmp['hashtag_special_chars'] = '_\x{200c}\x{200d}\x{a67e}\x{05be}\x{05f3}\x{05f4}\x{ff5e}\x{301c}\x{309b}\x{309c}\x{30a0}\x{30fb}\x{3003}\x{0f0b}\x{0f0c}\x{00b7}';
+ $tmp['hashtag_letters_numerals_set'] = '[' . $tmp['hashtag_letters'] . $tmp['hashtag_numerals'] . $tmp['hashtag_special_chars'] . ']';
+ $tmp['hashtag_letters_set'] = '[' . $tmp['hashtag_letters'] . ']';
+ $tmp['hashtag_boundary'] = '(?:\A|\x{fe0e}|\x{fe0f}|[^&' . $tmp['hashtag_letters'] . $tmp['hashtag_numerals'] . $tmp['hashtag_special_chars'] . '])';
+ $tmp['hashtag'] = '(' . $tmp['hashtag_boundary'] . ')(#|\x{ff03})(?!\x{fe0f}|\x{20e3})(' . $tmp['hashtag_letters_numerals_set'] . '*' . $tmp['hashtag_letters_set'] . $tmp['hashtag_letters_numerals_set'] . '*)';
+
+ $re['valid_hashtag'] = '/' . $tmp['hashtag'] . '(?=(.*|$))/iu';
+ $re['end_hashtag_match'] = '/\A(?:[' . $tmp['hash_signs'] . ']|:\/\/)/u';
+
+ # XXX: PHP doesn't have Ruby's $' (dollar apostrophe) so we have to capture
+ # $after in the following regular expression. Note that we only use a
+ # look-ahead capture here and don't append $after when we return.
+ $tmp['valid_mention_preceding_chars'] = '([^a-zA-Z0-9_!#\$%&*@@\/]|^|(?:^|[^a-z0-9_+~.-])RT:?)';
+ $re['valid_mentions_or_lists'] = '/' . $tmp['valid_mention_preceding_chars'] . '([' . $tmp['at_signs'] . '])([a-z0-9_]{1,20})(\/[a-z][a-z0-9_\-]{0,24})?(?=(.*|$))/iu';
+ $re['valid_reply'] = '/^(?:[' . $tmp['spaces'] . '])*[' . $tmp['at_signs'] . ']([a-z0-9_]{1,20})(?=(.*|$))/iu';
+ $re['end_mention_match'] = '/\A(?:[' . $tmp['at_signs'] . ']|[' . $tmp['latin_accents'] . ']|:\/\/)/iu';
+
+ # URL related hash regex collection
+
+ $tmp['valid_url_preceding_chars'] = '(?:[^A-Z0-9_@@\$##\.' . $tmp['invalid_characters'] . ']|^)';
+
+ $tmp['domain_valid_chars'] = '0-9a-z' . $tmp['latin_accents'];
+ $tmp['valid_subdomain'] = '(?>(?:[' . $tmp['domain_valid_chars'] . '][' . $tmp['domain_valid_chars'] . '\-_]*)?[' . $tmp['domain_valid_chars'] . ']\.)';
+ $tmp['valid_domain_name'] = '(?:(?:[' . $tmp['domain_valid_chars'] . '][' . $tmp['domain_valid_chars'] . '\-]*)?[' . $tmp['domain_valid_chars'] . ']\.)';
+ $tmp['domain_valid_unicode_chars'] = '[^\p{P}\p{Z}\p{C}' . $tmp['invalid_characters'] . $tmp['spaces'] . ']';
+
+ $gTLD = 'abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amsterdam|android|apartments|app|aquarelle|archi|army|arpa|asia|associates|attorney|auction|audio|auto|autos|axa|azure|band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva|bcn|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz|black|blackfriday|bloomberg|blue|bmw|bnl|bnpparibas|boats|bond|boo|boots|boutique|bradesco|bridgestone|broker|brother|brussels|budapest|build|builders|business|buzz|bzh|cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|caravan|cards|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center|ceo|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cisco|citic|city|claims|cleaning|click|clinic|clothing|cloud|club|coach|codes|coffee|college|cologne|com|commbank|community|company|computer|condos|construction|consulting|contractors|cooking|cool|coop|corsica|country|coupons|courses|credit|creditcard|cricket|crown|crs|cruises|cuisinella|cymru|cyou|dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|delta|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert|exposed|express|fage|fail|faith|family|fan|fans|farm|fashion|feedback|film|finance|financial|firmdale|fish|fishing|fit|fitness|flights|florist|flowers|flsmidth|fly|foo|football|forex|forsale|forum|foundation|frl|frogans|fund|furniture|futbol|fyi|gal|gallery|game|garden|gbiz|gdn|gent|genting|ggee|gift|gifts|gives|giving|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov|graphics|gratis|green|gripe|group|guge|guide|guitars|guru|hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hsbc|ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink|institute|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau|iwc|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|kaufen|kddi|kim|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto|lacaixa|lancaster|land|lasalle|lat|latrobe|law|lawyer|lds|lease|leclerc|legal|lexus|lgbt|liaison|lidl|life|lighting|limited|limo|link|live|lixil|loan|loans|lol|london|lotte|lotto|love|ltda|lupin|luxe|luxury|madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba|media|meet|melbourne|meme|memorial|men|menu|miami|microsoft|mil|mini|mma|mobi|moda|moe|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar|mtn|mtpc|museum|nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka|otsuka|ovh|page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography|photos|physio|piaget|pics|pictet|pictures|pink|pizza|place|play|plumbing|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties|property|pub|qpon|quebec|racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip|rocks|rodeo|rsvp|ruhr|run|ryukyu|saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sarl|saxo|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat|seek|sener|services|sew|sex|sexy|shiksha|shoes|show|shriram|singles|site|ski|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space|spiegel|spreadbetting|srl|starhub|statoil|studio|study|style|sucks|supplies|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica|temasek|tennis|thd|theater|tickets|tienda|tips|tires|tirol|today|tokyo|tools|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust|tui|ubs|university|uno|uol|vacations|vegas|ventures|vermögensberater|vermögensberatung|versicherung|vet|viajes|video|villas|vin|vision|vista|vistaprint|vlaanderen|vodka|vote|voting|voto|voyage|wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki|williamhill|win|windows|wine|wme|work|works|world|wtc|wtf|xbox|xerox|xin|xperia|xxx|xyz|yachts|yandex|yodobashi|yoga|yokohama|youtube|zip|zone|zuerich|дети|ком|москва|онлайн|орг|рус|сайт|קום|بازار|شبكة|كوم|موقع|कॉम|नेट|संगठन|คอม|みんな|グーグル|コム|世界|中信|中文网|企业|佛山|信息|健康|八卦|公司|公益|商城|商店|商标|在线|大拿|娱乐|工行|广东|慈善|我爱你|手机|政务|政府|新闻|时尚|机构|淡马锡|游戏|点看|移动|组织机构|网址|网店|网络|谷歌|集团|飞利浦|餐厅|닷넷|닷컴|삼성|onion';
+ $ccTLD = '한국|香港|澳門|新加坡|台灣|台湾|中國|中国|გე|ไทย|ලංකා|ഭാരതം|ಭಾರತ|భారత్|சிங்கப்பூர்|இலங்கை|இந்தியா|ଭାରତ|ભારત|ਭਾਰਤ|ভাৰত|ভারত|বাংলা|भारोत|भारतम्|भारत|ڀارت|پاکستان|مليسيا|مصر|قطر|فلسطين|عمان|عراق|سورية|سودان|تونس|بھارت|بارت|ایران|امارات|المغرب|السعودية|الجزائر|الاردن|հայ|қаз|укр|срб|рф|мон|мкд|ею|бел|бг|ελ|zw|zm|za|yt|ye|ws|wf|vu|vn|vi|vg|ve|vc|va|uz|uy|us|um|uk|ug|ua|tz|tw|tv|tt|tr|tp|to|tn|tm|tl|tk|tj|th|tg|tf|td|tc|sz|sy|sx|sv|su|st|ss|sr|so|sn|sm|sl|sk|sj|si|sh|sg|se|sd|sc|sb|sa|rw|ru|rs|ro|re|qa|py|pw|pt|ps|pr|pn|pm|pl|pk|ph|pg|pf|pe|pa|om|nz|nu|nr|np|no|nl|ni|ng|nf|ne|nc|na|mz|my|mx|mw|mv|mu|mt|ms|mr|mq|mp|mo|mn|mm|ml|mk|mh|mg|mf|me|md|mc|ma|ly|lv|lu|lt|ls|lr|lk|li|lc|lb|la|kz|ky|kw|kr|kp|kn|km|ki|kh|kg|ke|jp|jo|jm|je|it|is|ir|iq|io|in|im|il|ie|id|hu|ht|hr|hn|hm|hk|gy|gw|gu|gt|gs|gr|gq|gp|gn|gm|gl|gi|gh|gg|gf|ge|gd|gb|ga|fr|fo|fm|fk|fj|fi|eu|et|es|er|eh|eg|ee|ec|dz|do|dm|dk|dj|de|cz|cy|cx|cw|cv|cu|cr|co|cn|cm|cl|ck|ci|ch|cg|cf|cd|cc|ca|bz|by|bw|bv|bt|bs|br|bq|bo|bn|bm|bl|bj|bi|bh|bg|bf|be|bd|bb|ba|az|ax|aw|au|at|as|ar|aq|ao|an|am|al|ai|ag|af|ae|ad|ac';
+
+ $tmp['valid_gTLD'] = '(?:(?:' . $gTLD . ')(?=[^0-9a-z@]|$))';
+ $tmp['valid_ccTLD'] = '(?:(?:' . $ccTLD . ')(?=[^0-9a-z@]|$))';
+ $tmp['valid_special_ccTLD'] = '(?:(?:' . 'co|tv' . ')(?=[^0-9a-z@]|$))';
+ $tmp['valid_punycode'] = '(?:xn--[0-9a-z]+)';
+
+ $tmp['valid_domain'] = '(?:' // subdomains + domain + TLD
+ . $tmp['valid_subdomain'] . '+' . $tmp['valid_domain_name'] // e.g. www.twitter.com, foo.co.jp, bar.co.uk
+ . '(?:' . $tmp['valid_gTLD'] . '|' . $tmp['valid_ccTLD'] . '|' . $tmp['valid_punycode'] . '))'
+ . '|(?:' // domain + gTLD | some ccTLD
+ . $tmp['valid_domain_name'] // e.g. twitter.com
+ . '(?:' . $tmp['valid_gTLD'] . '|' . $tmp['valid_punycode'] . '|' . $tmp['valid_special_ccTLD'] . ')'
+ . ')'
+ . '|(?:(?:(?<=http:\/\/)|(?<=https:\/\/))'
+ . '(?:'
+ . '(?:' . $tmp['valid_domain_name'] . $tmp['valid_ccTLD'] . ')' // protocol + domain + ccTLD
+ . '|(?:' // protocol + unicode domain + TLD
+ . $tmp['domain_valid_unicode_chars'] . '+\.'
+ . '(?:' . $tmp['valid_gTLD'] . '|' . $tmp['valid_ccTLD'] . ')'
+ . ')'
+ . ')'
+ . ')'
+ . '|(?:' // domain + ccTLD + '/'
+ . $tmp['valid_domain_name'] . $tmp['valid_ccTLD'] . '(?=\/)' // e.g. t.co/
+ . ')';
+ # Used by the extractor:
+ $re['valid_ascii_domain'] = '/' . $tmp['valid_subdomain'] . '*' . $tmp['valid_domain_name'] . '(?:' . $tmp['valid_gTLD'] . '|' . $tmp['valid_ccTLD'] . '|' . $tmp['valid_punycode'] . ')/iu';
+
+ # Used by the extractor for stricter t.co URL extraction:
+ $re['valid_tco_url'] = '/^https?:\/\/t\.co\/[a-z0-9]+/iu';
+
+ # Used by the extractor to filter out unwanted URLs:
+ $re['invalid_short_domain'] = '/\A' . $tmp['valid_domain_name'] . $tmp['valid_ccTLD'] . '\Z/iu';
+ $re['valid_special_short_domain'] = '/\A' . $tmp['valid_domain_name'] . $tmp['valid_special_ccTLD'] . '\Z/iu';
+ $re['invalid_url_without_protocol_preceding_chars'] = '/[\-_.\/]\z/iu';
+
+ $tmp['valid_port_number'] = '[0-9]+';
+
+ $tmp['valid_general_url_path_chars'] = '[a-z\p{Cyrillic}0-9!\*;:=\+\,\.\$\/%#\[\]\-_~&|@' . $tmp['latin_accents'] . ']';
+ # Allow URL paths to contain up to two nested levels of balanced parentheses:
+ # 1. Used in Wikipedia URLs, e.g. /Primer_(film)
+ # 2. Used in IIS sessions, e.g. /S(dfd346)/
+ # 3. Used in Rdio URLs like /track/We_Up_(Album_Version_(Edited))/
+ $tmp['valid_url_balanced_parens'] = '(?:\('
+ . '(?:' . $tmp['valid_general_url_path_chars'] . '+'
+ . '|'
+ // allow one nested level of balanced parentheses
+ . '(?:'
+ . $tmp['valid_general_url_path_chars'] . '*'
+ . '\(' . $tmp['valid_general_url_path_chars'] . '+' . '\)'
+ . $tmp['valid_general_url_path_chars'] . '*'
+ . ')'
+ . ')'
+ . '\))';
+ # Valid end-of-path characters (so /foo. does not gobble the period).
+ # 1. Allow = for empty URL parameters and other URL-join artifacts.
+ $tmp['valid_url_path_ending_chars'] = '[a-z\p{Cyrillic}0-9=_#\/\+\-' . $tmp['latin_accents'] . ']|(?:' . $tmp['valid_url_balanced_parens'] . ')';
+ $tmp['valid_url_path'] = '(?:(?:'
+ . $tmp['valid_general_url_path_chars'] . '*(?:'
+ . $tmp['valid_url_balanced_parens'] . ' '
+ . $tmp['valid_general_url_path_chars'] . '*)*'
+ . $tmp['valid_url_path_ending_chars'] . ')|(?:@'
+ . $tmp['valid_general_url_path_chars'] . '+\/))';
+
+ $tmp['valid_url_query_chars'] = '[a-z0-9!?\*\'\(\);:&=\+\$\/%#\[\]\-_\.,~|@]';
+ $tmp['valid_url_query_ending_chars'] = '[a-z0-9_&=#\/\-]';
+
+ $re['valid_url'] = '/(?:' # $1 Complete match (preg_match() already matches everything.)
+ . '(' . $tmp['valid_url_preceding_chars'] . ')' # $2 Preceding characters
+ . '(' # $3 Complete URL
+ . '(https?:\/\/)?' # $4 Protocol (optional)
+ . '(' . $tmp['valid_domain'] . ')' # $5 Domain(s)
+ . '(?::(' . $tmp['valid_port_number'] . '))?' # $6 Port number (optional)
+ . '(\/' . $tmp['valid_url_path'] . '*)?' # $7 URL Path
+ . '(\?' . $tmp['valid_url_query_chars'] . '*' . $tmp['valid_url_query_ending_chars'] . ')?' # $8 Query String
+ . ')'
+ . ')/iux';
+
+ $tmp['cash_signs'] = '\$';
+ $tmp['cashtag'] = '[a-z]{1,6}(?:[._][a-z]{1,2})?';
+ $re['valid_cashtag'] = '/(^|[' . $tmp['spaces'] . '])([' . $tmp['cash_signs'] . '])(' . $tmp['cashtag'] . ')(?=($|\s|[[:punct:]]))/iu';
+ $re['end_cashtag_match'] = '/\A(?:[' . $tmp['cash_signs'] . ']|:\/\/)/u';
+
+ # These URL validation pattern strings are based on the ABNF from RFC 3986
+ $tmp['validate_url_unreserved'] = '[a-z\p{Cyrillic}0-9\-._~]';
+ $tmp['validate_url_pct_encoded'] = '(?:%[0-9a-f]{2})';
+ $tmp['validate_url_sub_delims'] = '[!$&\'()*+,;=]';
+ $tmp['validate_url_pchar'] = '(?:' . $tmp['validate_url_unreserved'] . '|' . $tmp['validate_url_pct_encoded'] . '|' . $tmp['validate_url_sub_delims'] . '|[:\|@])'; #/iox
+
+ $tmp['validate_url_userinfo'] = '(?:' . $tmp['validate_url_unreserved'] . '|' . $tmp['validate_url_pct_encoded'] . '|' . $tmp['validate_url_sub_delims'] . '|:)*'; #/iox
+
+ $tmp['validate_url_dec_octet'] = '(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])'; #/i
+ $tmp['validate_url_ipv4'] = '(?:' . $tmp['validate_url_dec_octet'] . '(?:\.' . $tmp['validate_url_dec_octet'] . '){3})'; #/iox
+ # Punting on real IPv6 validation for now
+ $tmp['validate_url_ipv6'] = '(?:\[[a-f0-9:\.]+\])'; #/i
+ # Also punting on IPvFuture for now
+ $tmp['validate_url_ip'] = '(?:' . $tmp['validate_url_ipv4'] . '|' . $tmp['validate_url_ipv6'] . ')'; #/iox
+ # This is more strict than the rfc specifies
+ $tmp['validate_url_subdomain_segment'] = '(?:[a-z0-9](?:[a-z0-9_\-]*[a-z0-9])?)'; #/i
+ $tmp['validate_url_domain_segment'] = '(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?)'; #/i
+ $tmp['validate_url_domain_tld'] = '(?:[a-z](?:[a-z0-9\-]*[a-z0-9])?)'; #/i
+ $tmp['validate_url_domain'] = '(?:(?:' . $tmp['validate_url_subdomain_segment'] . '\.)*(?:' . $tmp['validate_url_domain_segment'] . '\.)' . $tmp['validate_url_domain_tld'] . ')'; #/iox
+
+ $tmp['validate_url_host'] = '(?:' . $tmp['validate_url_ip'] . '|' . $tmp['validate_url_domain'] . ')'; #/iox
+ # Unencoded internationalized domains - this doesn't check for invalid UTF-8 sequences
+ $tmp['validate_url_unicode_subdomain_segment'] = '(?:(?:[a-z0-9]|[^\x00-\x7f])(?:(?:[a-z0-9_\-]|[^\x00-\x7f])*(?:[a-z0-9]|[^\x00-\x7f]))?)'; #/ix
+ $tmp['validate_url_unicode_domain_segment'] = '(?:(?:[a-z0-9]|[^\x00-\x7f])(?:(?:[a-z0-9\-]|[^\x00-\x7f])*(?:[a-z0-9]|[^\x00-\x7f]))?)'; #/ix
+ $tmp['validate_url_unicode_domain_tld'] = '(?:(?:[a-z]|[^\x00-\x7f])(?:(?:[a-z0-9\-]|[^\x00-\x7f])*(?:[a-z0-9]|[^\x00-\x7f]))?)'; #/ix
+ $tmp['validate_url_unicode_domain'] = '(?:(?:' . $tmp['validate_url_unicode_subdomain_segment'] . '\.)*(?:' . $tmp['validate_url_unicode_domain_segment'] . '\.)' . $tmp['validate_url_unicode_domain_tld'] . ')'; #/iox
+
+ $tmp['validate_url_unicode_host'] = '(?:' . $tmp['validate_url_ip'] . '|' . $tmp['validate_url_unicode_domain'] . ')'; #/iox
+
+ $tmp['validate_url_port'] = '[0-9]{1,5}';
+
+ $re['validate_url_unicode_authority'] = '/'
+ . '(?:(' . $tmp['validate_url_userinfo'] . ')@)?' # $1 userinfo
+ . '(' . $tmp['validate_url_unicode_host'] . ')' # $2 host
+ . '(?::(' . $tmp['validate_url_port'] . '))?' # $3 port
+ . '/iux';
+
+ $re['validate_url_authority'] = '/'
+ . '(?:(' . $tmp['validate_url_userinfo'] . ')@)?' # $1 userinfo
+ . '(' . $tmp['validate_url_host'] . ')' # $2 host
+ . '(?::(' . $tmp['validate_url_port'] . '))?' # $3 port
+ . '/ix';
+
+ $re['validate_url_scheme'] = '/(?:[a-z][a-z0-9+\-.]*)/i';
+ $re['validate_url_path'] = '/(\/' . $tmp['validate_url_pchar'] . '*)*/iu';
+ $re['validate_url_query'] = '/(' . $tmp['validate_url_pchar'] . '|\/|\?)*/iu';
+ $re['validate_url_fragment'] = '/(' . $tmp['validate_url_pchar'] . '|\/|\?)*/iu';
+
+ # Modified version of RFC 3986 Appendix B
+ $re['validate_url_unencoded'] = '/^' # Full URL
+ . '(?:'
+ . '([^:\/?#]+):\/\/' # $1 Scheme
+ . ')?'
+ . '([^\/?#]*)' # $2 Authority
+ . '([^?#]*)' # $3 Path
+ . '(?:'
+ . '\?([^#]*)' # $4 Query
+ . ')?'
+ . '(?:'
+ . '\#(.*)' # $5 Fragment
+ . ')?$/iux';
+
+ $re['invalid_characters'] = '/[' . $tmp['invalid_characters'] . ']/u';
+
+ $re['rtl_chars'] = '/[' . $tmp['rtl_chars'] . ']/iu';
+
+ # Flag that initialization is complete:
+ $initialized = true;
+ }
+}
+
+# Cause regular expressions to be initialized as soon as this file is loaded:
+Regex::__static();
diff --git a/app/Util/Lexer/RestrictedNames.php b/app/Util/Lexer/RestrictedNames.php
index eb820d40d..8f9680920 100644
--- a/app/Util/Lexer/RestrictedNames.php
+++ b/app/Util/Lexer/RestrictedNames.php
@@ -55,8 +55,6 @@ class RestrictedNames {
"js",
"localdomain",
"localhost",
- "login",
- "logout",
"mail",
"mailer-daemon",
"mailerdaemon",
@@ -108,8 +106,6 @@ class RestrictedNames {
"tutorial",
"tutorials",
"usenet",
- "user",
- "users",
"uucp",
"webmaster",
"wpad",
@@ -126,13 +122,23 @@ class RestrictedNames {
// Laravel Horizon
"horizon",
- // Reserved route
+ // Reserved routes
+ "account",
+ "api",
+ "auth",
"i",
-
- // Official accounts
- "pixelfed",
- "pixelfed-support",
- "pixelfed_support",
+ "discover",
+ "home",
+ "login",
+ "logout",
+ "p",
+ "password",
+ "search",
+ "settings",
+ "site",
+ "timeline",
+ "user",
+ "users",
];
public static function get()
diff --git a/app/Util/Lexer/StringUtils.php b/app/Util/Lexer/StringUtils.php
new file mode 100755
index 000000000..88722d158
--- /dev/null
+++ b/app/Util/Lexer/StringUtils.php
@@ -0,0 +1,104 @@
+ $string_length) {
+ $start = $string_length;
+ }
+ if ($length < 0) {
+ $length = max(0, $string_length - $start + $length);
+ } elseif ((is_null($length) === true) || ($length > $string_length)) {
+ $length = $string_length;
+ }
+ if (($start + $length) > $string_length) {
+ $length = $string_length - $start;
+ }
+
+ $suffixOffset = $start + $length;
+ $suffixLength = $string_length - $start - $length;
+ return static::substr($string, 0, $start, $encoding) . $replacement . static::substr($string, $suffixOffset, $suffixLength, $encoding);
+ }
+ return (is_null($length) === true) ? substr_replace($string, $replacement, $start) : substr_replace($string, $replacement, $start, $length);
+ }
+}
diff --git a/app/Util/Lexer/Validator.php b/app/Util/Lexer/Validator.php
new file mode 100755
index 000000000..ddfb2c5f6
--- /dev/null
+++ b/app/Util/Lexer/Validator.php
@@ -0,0 +1,388 @@
+
+ * @copyright Copyright © 2010, Nick Pope
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ */
+
+namespace App\Util\Lexer;
+
+use App\Util\Lexer\Regex;
+use App\Util\Lexer\Extractor;
+use App\Util\Lexer\StringUtils;
+
+/**
+ * Twitter Validator Class
+ *
+ * Performs "validation" on tweets.
+ *
+ * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
+ * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
+ * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
+ *
+ * @author Nick Pope
+ * @copyright Copyright © 2010, Nick Pope
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
+ * @package Twitter.Text
+ */
+class Validator extends Regex
+{
+
+ /**
+ * The maximum length of a tweet.
+ *
+ * @var int
+ */
+ const MAX_LENGTH = 140;
+
+ /**
+ * The length of a short URL beginning with http:
+ *
+ * @var int
+ */
+ protected $short_url_length = 23;
+
+ /**
+ * The length of a short URL beginning with http:
+ *
+ * @var int
+ */
+ protected $short_url_length_https = 23;
+
+ /**
+ *
+ * @var Extractor
+ */
+ protected $extractor = null;
+
+ /**
+ * Provides fluent method chaining.
+ *
+ * @param string $tweet The tweet to be validated.
+ * @param mixed $config Setup short URL length from Twitter API /help/configuration response.
+ *
+ * @see __construct()
+ *
+ * @return Validator
+ */
+ public static function create($tweet = null, $config = null)
+ {
+ return new self($tweet, $config);
+ }
+
+ /**
+ * Reads in a tweet to be parsed and validates it.
+ *
+ * @param string $tweet The tweet to validate.
+ */
+ public function __construct($tweet = null, $config = null)
+ {
+ parent::__construct($tweet);
+ if (!empty($config)) {
+ $this->setConfiguration($config);
+ }
+ $this->extractor = Extractor::create();
+ }
+
+ /**
+ * Setup short URL length from Twitter API /help/configuration response
+ *
+ * @param mixed $config
+ * @return Validator
+ * @link https://dev.twitter.com/docs/api/1/get/help/configuration
+ */
+ public function setConfiguration($config)
+ {
+ if (is_array($config)) {
+ // setup from array
+ if (isset($config['short_url_length'])) {
+ $this->setShortUrlLength($config['short_url_length']);
+ }
+ if (isset($config['short_url_length_https'])) {
+ $this->setShortUrlLengthHttps($config['short_url_length_https']);
+ }
+ } elseif (is_object($config)) {
+ // setup from object
+ if (isset($config->short_url_length)) {
+ $this->setShortUrlLength($config->short_url_length);
+ }
+ if (isset($config->short_url_length_https)) {
+ $this->setShortUrlLengthHttps($config->short_url_length_https);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the length of a short URL beginning with http:
+ *
+ * @param mixed $length
+ * @return Validator
+ */
+ public function setShortUrlLength($length)
+ {
+ $this->short_url_length = intval($length);
+ return $this;
+ }
+
+ /**
+ * Get the length of a short URL beginning with http:
+ *
+ * @return int
+ */
+ public function getShortUrlLength()
+ {
+ return $this->short_url_length;
+ }
+
+ /**
+ * Set the length of a short URL beginning with https:
+ *
+ * @param mixed $length
+ * @return Validator
+ */
+ public function setShortUrlLengthHttps($length)
+ {
+ $this->short_url_length_https = intval($length);
+ return $this;
+ }
+
+ /**
+ * Get the length of a short URL beginning with https:
+ *
+ * @return int
+ */
+ public function getShortUrlLengthHttps()
+ {
+ return $this->short_url_length_https;
+ }
+
+ /**
+ * Check whether a tweet is valid.
+ *
+ * @param string $tweet The tweet to validate.
+ * @return boolean Whether the tweet is valid.
+ */
+ public function isValidTweetText($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $length = $this->getTweetLength($tweet);
+ if (!$tweet || !$length) {
+ return false;
+ }
+ if ($length > self::MAX_LENGTH) {
+ return false;
+ }
+ if (preg_match(self::$patterns['invalid_characters'], $tweet)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Check whether a tweet is valid.
+ *
+ * @return boolean Whether the tweet is valid.
+ * @deprecated since version 1.1.0
+ */
+ public function validateTweet()
+ {
+ return $this->isValidTweetText();
+ }
+
+ /**
+ * Check whether a username is valid.
+ *
+ * @param string $username The username to validate.
+ * @return boolean Whether the username is valid.
+ */
+ public function isValidUsername($username = null)
+ {
+ if (is_null($username)) {
+ $username = $this->tweet;
+ }
+ $length = StringUtils::strlen($username);
+ if (empty($username) || !$length) {
+ return false;
+ }
+ $extracted = $this->extractor->extractMentionedScreennames($username);
+ return count($extracted) === 1 && $extracted[0] === substr($username, 1);
+ }
+
+ /**
+ * Check whether a username is valid.
+ *
+ * @return boolean Whether the username is valid.
+ * @deprecated since version 1.1.0
+ */
+ public function validateUsername()
+ {
+ return $this->isValidUsername();
+ }
+
+ /**
+ * Check whether a list is valid.
+ *
+ * @param string $list The list name to validate.
+ * @return boolean Whether the list is valid.
+ */
+ public function isValidList($list = null)
+ {
+ if (is_null($list)) {
+ $list = $this->tweet;
+ }
+ $length = StringUtils::strlen($list);
+ if (empty($list) || !$length) {
+ return false;
+ }
+ preg_match(self::$patterns['valid_mentions_or_lists'], $list, $matches);
+ $matches = array_pad($matches, 5, '');
+ return isset($matches) && $matches[1] === '' && $matches[4] && !empty($matches[4]) && $matches[5] === '';
+ }
+
+ /**
+ * Check whether a list is valid.
+ *
+ * @return boolean Whether the list is valid.
+ * @deprecated since version 1.1.0
+ */
+ public function validateList()
+ {
+ return $this->isValidList();
+ }
+
+ /**
+ * Check whether a hashtag is valid.
+ *
+ * @param string $hashtag The hashtag to validate.
+ * @return boolean Whether the hashtag is valid.
+ */
+ public function isValidHashtag($hashtag = null)
+ {
+ if (is_null($hashtag)) {
+ $hashtag = $this->tweet;
+ }
+ $length = StringUtils::strlen($hashtag);
+ if (empty($hashtag) || !$length) {
+ return false;
+ }
+ $extracted = $this->extractor->extractHashtags($hashtag);
+ return count($extracted) === 1 && $extracted[0] === substr($hashtag, 1);
+ }
+
+ /**
+ * Check whether a hashtag is valid.
+ *
+ * @return boolean Whether the hashtag is valid.
+ * @deprecated since version 1.1.0
+ */
+ public function validateHashtag()
+ {
+ return $this->isValidHashtag();
+ }
+
+ /**
+ * Check whether a URL is valid.
+ *
+ * @param string $url The url to validate.
+ * @param boolean $unicode_domains Consider the domain to be unicode.
+ * @param boolean $require_protocol Require a protocol for valid domain?
+ *
+ * @return boolean Whether the URL is valid.
+ */
+ public function isValidURL($url = null, $unicode_domains = true, $require_protocol = true)
+ {
+ if (is_null($url)) {
+ $url = $this->tweet;
+ }
+ $length = StringUtils::strlen($url);
+ if (empty($url) || !$length) {
+ return false;
+ }
+ preg_match(self::$patterns['validate_url_unencoded'], $url, $matches);
+ $match = array_shift($matches);
+ if (!$matches || $match !== $url) {
+ return false;
+ }
+ list($scheme, $authority, $path, $query, $fragment) = array_pad($matches, 5, '');
+ # Check scheme, path, query, fragment:
+ if (($require_protocol && !(
+ self::isValidMatch($scheme, self::$patterns['validate_url_scheme']) && preg_match('/^https?$/i', $scheme))
+ ) || !self::isValidMatch($path, self::$patterns['validate_url_path']) || !self::isValidMatch($query, self::$patterns['validate_url_query'], true)
+ || !self::isValidMatch($fragment, self::$patterns['validate_url_fragment'], true)) {
+ return false;
+ }
+ # Check authority:
+ $authority_pattern = $unicode_domains ? 'validate_url_unicode_authority' : 'validate_url_authority';
+ return self::isValidMatch($authority, self::$patterns[$authority_pattern]);
+ }
+
+ /**
+ * Check whether a URL is valid.
+ *
+ * @param boolean $unicode_domains Consider the domain to be unicode.
+ * @param boolean $require_protocol Require a protocol for valid domain?
+ *
+ * @return boolean Whether the URL is valid.
+ * @deprecated since version 1.1.0
+ */
+ public function validateURL($unicode_domains = true, $require_protocol = true)
+ {
+ return $this->isValidURL(null, $unicode_domains, $require_protocol);
+ }
+
+ /**
+ * Determines the length of a tweet. Takes shortening of URLs into account.
+ *
+ * @param string $tweet The tweet to validate.
+ * @return int the length of a tweet.
+ */
+ public function getTweetLength($tweet = null)
+ {
+ if (is_null($tweet)) {
+ $tweet = $this->tweet;
+ }
+ $length = StringUtils::strlen($tweet);
+ $urls_with_indices = $this->extractor->extractURLsWithIndices($tweet);
+ foreach ($urls_with_indices as $x) {
+ $length += $x['indices'][0] - $x['indices'][1];
+ $length += stripos($x['url'], 'https://') === 0 ? $this->short_url_length_https : $this->short_url_length;
+ }
+ return $length;
+ }
+
+ /**
+ * Determines the length of a tweet. Takes shortening of URLs into account.
+ *
+ * @return int the length of a tweet.
+ * @deprecated since version 1.1.0
+ */
+ public function getLength()
+ {
+ return $this->getTweetLength();
+ }
+
+ /**
+ * A helper function to check for a valid match. Used in URL validation.
+ *
+ * @param string $string The subject string to test.
+ * @param string $pattern The pattern to match against.
+ * @param boolean $optional Whether a match is compulsory or not.
+ *
+ * @return boolean Whether an exact match was found.
+ */
+ protected static function isValidMatch($string, $pattern, $optional = false)
+ {
+ $found = preg_match($pattern, $string, $matches);
+ if (!$optional) {
+ return (($string || $string === '') && $found && $matches[0] === $string);
+ } else {
+ return !(($string || $string === '') && (!$found || $matches[0] !== $string));
+ }
+ }
+}
diff --git a/app/Util/Media/Image.php b/app/Util/Media/Image.php
index f9ee8342d..3f67d43f5 100644
--- a/app/Util/Media/Image.php
+++ b/app/Util/Media/Image.php
@@ -103,44 +103,31 @@ class Image {
$ratio = $this->getAspectRatio($file, $thumbnail);
$aspect = $ratio['dimensions'];
$orientation = $ratio['orientation'];
+ if($media->mime === 'image/gif' && !$thumbnail)
+ {
+ return;
+ }
try {
- $img = Intervention::make($file);
- $img->fit($aspect['width'], $aspect['height'], function ($constraint) {
- $constraint->upsize();
+ $img = Intervention::make($file)->orientate();
+ $img->resize($aspect['width'], $aspect['height'], function ($constraint) {
+ $constraint->aspectRatio();
});
- $converted = $this->convertPngToJpeg($path, $thumbnail, $img->extension);
+ $converted = $this->setBaseName($path, $thumbnail, $img->extension);
$newPath = storage_path('app/'.$converted['path']);
- $is_png = false;
-
- if($img->extension == 'png' || $converted['png'] == true) {
- \Log::info('PNG detected, ' . json_encode([$img, $converted]));
- $is_png = true;
- $newPath = str_replace('.png', '.jpeg', $newPath);
- $img->encode('jpg', 80)->save($newPath);
- if(!$thumbnail) {
- @unlink($file);
- }
- \Log::info('PNG SAVED, ' . json_encode([$img, $newPath]));
- } else {
- \Log::info('PNG not detected, ' . json_encode([$img, $converted]));
- $img->save($newPath, 75);
- }
+
+ $img->save($newPath, 75);
if(!$thumbnail) {
$media->orientation = $orientation;
}
- if($is_png == true) {
- if($thumbnail == false) {
+ if($thumbnail == true) {
+ $media->thumbnail_path = $converted['path'];
+ $media->thumbnail_url = url(Storage::url($converted['path']));
+ } else {
$media->media_path = $converted['path'];
$media->mime = $img->mime;
- }
- }
-
- if($thumbnail == true) {
- $media->thumbnail_path = $converted['path'];
- $media->thumbnail_url = url(Storage::url($converted['path']));
}
$media->save();
@@ -150,18 +137,14 @@ class Image {
}
}
- public function convertPngToJpeg($basePath, $thumbnail = false, $extension)
+ public function setBaseName($basePath, $thumbnail = false, $extension)
{
$png = false;
$path = explode('.', $basePath);
$name = ($thumbnail == true) ? $path[0] . '_thumb' : $path[0];
$ext = last($path);
$basePath = "{$name}.{$ext}";
- if($extension == 'png' || $ext == 'png') {
- $ext = 'jpeg';
- $basePath = "{$name}.{$ext}";
- $png = true;
- }
+
return ['path' => $basePath, 'png' => $png];
}
diff --git a/app/WebSub.php b/app/WebSub.php
new file mode 100644
index 000000000..5be3ad93f
--- /dev/null
+++ b/app/WebSub.php
@@ -0,0 +1,10 @@
+2.2,<2.4"
+ },
+ "require-dev": {
+ "alcaeus/mongo-php-adapter": "^1.1",
+ "mongodb/mongodb": "^1.1",
+ "phpunit/phpunit": "^5.7",
+ "predis/predis": "~1.0"
+ },
+ "suggest": {
+ "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.7.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Caching library offering an object-oriented API for many cache backends",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "cache",
+ "caching"
+ ],
+ "time": "2017-08-25T07:02:50+00:00"
+ },
+ {
+ "name": "doctrine/collections",
+ "version": "v1.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/collections.git",
+ "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf",
+ "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "~0.1@dev",
+ "phpunit/phpunit": "^5.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Collections\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Collections Abstraction library",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "array",
+ "collections",
+ "iterator"
+ ],
+ "time": "2017-07-22T10:37:32+00:00"
+ },
+ {
+ "name": "doctrine/common",
+ "version": "v2.8.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/common.git",
+ "reference": "f68c297ce6455e8fd794aa8ffaf9fa458f6ade66"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/common/zipball/f68c297ce6455e8fd794aa8ffaf9fa458f6ade66",
+ "reference": "f68c297ce6455e8fd794aa8ffaf9fa458f6ade66",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "1.*",
+ "doctrine/cache": "1.*",
+ "doctrine/collections": "1.*",
+ "doctrine/inflector": "1.*",
+ "doctrine/lexer": "1.*",
+ "php": "~7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "lib/Doctrine/Common"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Common Library for Doctrine projects",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "annotations",
+ "collections",
+ "eventmanager",
+ "persistence",
+ "spl"
+ ],
+ "time": "2017-08-31T08:43:38+00:00"
+ },
+ {
+ "name": "doctrine/dbal",
+ "version": "v2.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/dbal.git",
+ "reference": "11037b4352c008373561dc6fc836834eed80c3b5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/11037b4352c008373561dc6fc836834eed80c3b5",
+ "reference": "11037b4352c008373561dc6fc836834eed80c3b5",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/common": "^2.7.1",
+ "ext-pdo": "*",
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^4.0",
+ "phpunit/phpunit": "^7.0",
+ "phpunit/phpunit-mock-objects": "!=3.2.4,!=3.2.5",
+ "symfony/console": "^2.0.5||^3.0",
+ "symfony/phpunit-bridge": "^3.4.5|^4.0.5"
+ },
+ "suggest": {
+ "symfony/console": "For helpful console commands such as SQL execution and import of files."
+ },
+ "bin": [
+ "bin/doctrine-dbal"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\DBAL\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ }
+ ],
+ "description": "Database Abstraction Layer",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "database",
+ "dbal",
+ "persistence",
+ "queryobject"
+ ],
+ "time": "2018-04-07T18:44:18+00:00"
+ },
{
"name": "doctrine/inflector",
"version": "v1.3.0",
@@ -571,6 +928,58 @@
],
"time": "2018-02-07T20:20:57+00:00"
},
+ {
+ "name": "greggilbert/recaptcha",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/greggilbert/recaptcha.git",
+ "reference": "c2ed383785a4fe20467ce470c97c303e5c5b85de"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/greggilbert/recaptcha/zipball/c2ed383785a4fe20467ce470c97c303e5c5b85de",
+ "reference": "c2ed383785a4fe20467ce470c97c303e5c5b85de",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/support": "~5.1",
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/migrations"
+ ],
+ "psr-4": {
+ "Greggilbert\\Recaptcha\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Greg Gilbert",
+ "email": "greg@greg-gilbert.com"
+ }
+ ],
+ "description": "reCAPTCHA Validator for Laravel 5",
+ "homepage": "http://github.com/greggilbert/recaptcha",
+ "keywords": [
+ "captcha",
+ "laravel",
+ "laravel5",
+ "recaptcha"
+ ],
+ "time": "2017-08-31T03:39:47+00:00"
+ },
{
"name": "guzzlehttp/guzzle",
"version": "6.3.3",
@@ -754,16 +1163,16 @@
},
{
"name": "intervention/image",
- "version": "2.4.1",
+ "version": "2.4.2",
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
- "reference": "3603dbcc9a17d307533473246a6c58c31cf17919"
+ "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Intervention/image/zipball/3603dbcc9a17d307533473246a6c58c31cf17919",
- "reference": "3603dbcc9a17d307533473246a6c58c31cf17919",
+ "url": "https://api.github.com/repos/Intervention/image/zipball/e82d274f786e3d4b866a59b173f42e716f0783eb",
+ "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb",
"shasum": ""
},
"require": {
@@ -783,7 +1192,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.3-dev"
+ "dev-master": "2.4-dev"
},
"laravel": {
"providers": [
@@ -820,7 +1229,7 @@
"thumbnail",
"watermark"
],
- "time": "2017-09-21T16:29:17+00:00"
+ "time": "2018-05-29T14:19:03+00:00"
},
{
"name": "jakub-onderka/php-console-color",
@@ -910,17 +1319,63 @@
"time": "2015-04-20T18:58:01+00:00"
},
{
- "name": "laravel/framework",
- "version": "v5.6.16",
+ "name": "kitetail/zttp",
+ "version": "v0.3.0",
"source": {
"type": "git",
- "url": "https://github.com/laravel/framework.git",
- "reference": "fcdbc791bc3e113ada38ab0a1147141fb9ec2b16"
+ "url": "https://github.com/kitetail/zttp.git",
+ "reference": "e788ab8fc5c0259f691e2960d17e0ddbab761c6a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/fcdbc791bc3e113ada38ab0a1147141fb9ec2b16",
- "reference": "fcdbc791bc3e113ada38ab0a1147141fb9ec2b16",
+ "url": "https://api.github.com/repos/kitetail/zttp/zipball/e788ab8fc5c0259f691e2960d17e0ddbab761c6a",
+ "reference": "e788ab8fc5c0259f691e2960d17e0ddbab761c6a",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/guzzle": "^6.0",
+ "php": ">=7.0",
+ "tightenco/collect": "^5.4"
+ },
+ "require-dev": {
+ "laravel/lumen-framework": "^5.4",
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/Zttp.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Adam Wathan",
+ "email": "adam.wathan@gmail.com"
+ }
+ ],
+ "description": "A developer-experience focused HTTP client, optimized for most common use cases.",
+ "keywords": [
+ "Guzzle",
+ "http"
+ ],
+ "time": "2017-08-09T15:31:26+00:00"
+ },
+ {
+ "name": "laravel/framework",
+ "version": "v5.6.23",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/framework.git",
+ "reference": "f547f0a71a12763d1adb8493237d541c9e3a5d10"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/f547f0a71a12763d1adb8493237d541c9e3a5d10",
+ "reference": "f547f0a71a12763d1adb8493237d541c9e3a5d10",
"shasum": ""
},
"require": {
@@ -931,7 +1386,7 @@
"ext-openssl": "*",
"league/flysystem": "^1.0.8",
"monolog/monolog": "~1.12",
- "nesbot/carbon": "^1.24.1",
+ "nesbot/carbon": "1.25.*",
"php": "^7.1.3",
"psr/container": "~1.0",
"psr/simple-cache": "^1.0",
@@ -1046,7 +1501,7 @@
"framework",
"laravel"
],
- "time": "2018-04-09T16:07:04+00:00"
+ "time": "2018-05-22T14:55:57+00:00"
},
{
"name": "laravel/horizon",
@@ -1118,16 +1573,16 @@
},
{
"name": "laravel/tinker",
- "version": "v1.0.5",
+ "version": "v1.0.7",
"source": {
"type": "git",
"url": "https://github.com/laravel/tinker.git",
- "reference": "94f6daf2131508cebd11cd6f8632ba586d7ecc41"
+ "reference": "e3086ee8cb1f54a39ae8dcb72d1c37d10128997d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/tinker/zipball/94f6daf2131508cebd11cd6f8632ba586d7ecc41",
- "reference": "94f6daf2131508cebd11cd6f8632ba586d7ecc41",
+ "url": "https://api.github.com/repos/laravel/tinker/zipball/e3086ee8cb1f54a39ae8dcb72d1c37d10128997d",
+ "reference": "e3086ee8cb1f54a39ae8dcb72d1c37d10128997d",
"shasum": ""
},
"require": {
@@ -1135,7 +1590,7 @@
"illuminate/contracts": "~5.1",
"illuminate/support": "~5.1",
"php": ">=5.5.9",
- "psy/psysh": "0.7.*|0.8.*",
+ "psy/psysh": "0.7.*|0.8.*|0.9.*",
"symfony/var-dumper": "~3.0|~4.0"
},
"require-dev": {
@@ -1177,20 +1632,20 @@
"laravel",
"psysh"
],
- "time": "2018-03-06T17:34:36+00:00"
+ "time": "2018-05-17T13:42:07+00:00"
},
{
"name": "league/flysystem",
- "version": "1.0.44",
+ "version": "1.0.45",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
- "reference": "168dbe519737221dc87d17385cde33073881fd02"
+ "reference": "a99f94e63b512d75f851b181afcdf0ee9ebef7e6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/168dbe519737221dc87d17385cde33073881fd02",
- "reference": "168dbe519737221dc87d17385cde33073881fd02",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a99f94e63b512d75f851b181afcdf0ee9ebef7e6",
+ "reference": "a99f94e63b512d75f851b181afcdf0ee9ebef7e6",
"shasum": ""
},
"require": {
@@ -1261,7 +1716,7 @@
"sftp",
"storage"
],
- "time": "2018-04-06T09:58:14+00:00"
+ "time": "2018-05-07T08:44:23+00:00"
},
{
"name": "league/fractal",
@@ -1460,24 +1915,24 @@
},
{
"name": "nikic/php-parser",
- "version": "v3.1.5",
+ "version": "v4.0.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce"
+ "reference": "e4a54fa90a5cd8e8dd3fb4099942681731c5cdd3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce",
- "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/e4a54fa90a5cd8e8dd3fb4099942681731c5cdd3",
+ "reference": "e4a54fa90a5cd8e8dd3fb4099942681731c5cdd3",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
- "php": ">=5.5"
+ "php": ">=7.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.0|~5.0"
+ "phpunit/phpunit": "^6.5 || ^7.0"
},
"bin": [
"bin/php-parse"
@@ -1485,7 +1940,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "4.0-dev"
}
},
"autoload": {
@@ -1507,7 +1962,7 @@
"parser",
"php"
],
- "time": "2018-02-28T20:30:58+00:00"
+ "time": "2018-03-25T17:35:16+00:00"
},
{
"name": "paragonie/random_compat",
@@ -1895,30 +2350,30 @@
},
{
"name": "psy/psysh",
- "version": "v0.8.18",
+ "version": "v0.9.4",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
- "reference": "5357b1cffc8fb375d6a9e3c86d5c82dd38a40834"
+ "reference": "4d969a0e08e1e05e7207c07cb4207017ecc9a331"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/bobthecow/psysh/zipball/5357b1cffc8fb375d6a9e3c86d5c82dd38a40834",
- "reference": "5357b1cffc8fb375d6a9e3c86d5c82dd38a40834",
+ "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4d969a0e08e1e05e7207c07cb4207017ecc9a331",
+ "reference": "4d969a0e08e1e05e7207c07cb4207017ecc9a331",
"shasum": ""
},
"require": {
"dnoegel/php-xdg-base-dir": "0.1",
"jakub-onderka/php-console-highlighter": "0.3.*",
- "nikic/php-parser": "~1.3|~2.0|~3.0",
- "php": ">=5.3.9",
+ "nikic/php-parser": "~1.3|~2.0|~3.0|~4.0",
+ "php": ">=5.4.0",
"symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0",
"symfony/var-dumper": "~2.7|~3.0|~4.0"
},
"require-dev": {
- "hoa/console": "~3.16|~1.14",
- "phpunit/phpunit": "^4.8.35|^5.4.3",
- "symfony/finder": "~2.1|~3.0|~4.0"
+ "bamarni/composer-bin-plugin": "^1.2",
+ "hoa/console": "~2.15|~3.16",
+ "phpunit/phpunit": "~4.8.35|~5.0|~6.0|~7.0"
},
"suggest": {
"ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)",
@@ -1933,15 +2388,15 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-develop": "0.8.x-dev"
+ "dev-develop": "0.9.x-dev"
}
},
"autoload": {
"files": [
- "src/Psy/functions.php"
+ "src/functions.php"
],
"psr-4": {
- "Psy\\": "src/Psy/"
+ "Psy\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -1963,7 +2418,7 @@
"interactive",
"shell"
],
- "time": "2018-04-02T05:41:44+00:00"
+ "time": "2018-05-22T06:48:07+00:00"
},
{
"name": "ramsey/uuid",
@@ -2045,6 +2500,56 @@
],
"time": "2018-01-20T00:28:24+00:00"
},
+ {
+ "name": "spatie/db-dumper",
+ "version": "2.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/db-dumper.git",
+ "reference": "ee745fba17bcf77c916f231a571bbde8dae8e001"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/db-dumper/zipball/ee745fba17bcf77c916f231a571bbde8dae8e001",
+ "reference": "ee745fba17bcf77c916f231a571bbde8dae8e001",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "symfony/process": "^3.0|^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\DbDumper\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Dump databases",
+ "homepage": "https://github.com/spatie/db-dumper",
+ "keywords": [
+ "database",
+ "db-dumper",
+ "dump",
+ "mysqldump",
+ "spatie"
+ ],
+ "time": "2018-04-27T15:10:51+00:00"
+ },
{
"name": "spatie/image-optimizer",
"version": "1.0.14",
@@ -2095,17 +2600,90 @@
"time": "2018-03-07T13:42:33+00:00"
},
{
- "name": "spatie/laravel-image-optimizer",
- "version": "1.1.3",
+ "name": "spatie/laravel-backup",
+ "version": "5.7.0",
"source": {
"type": "git",
- "url": "https://github.com/spatie/laravel-image-optimizer.git",
- "reference": "6dd6644240db119fe3c60aafd4b0513937b588af"
+ "url": "https://github.com/spatie/laravel-backup.git",
+ "reference": "60c6fbc27c9c2cbdf3c7da90b080a63d2599cd2d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/spatie/laravel-image-optimizer/zipball/6dd6644240db119fe3c60aafd4b0513937b588af",
- "reference": "6dd6644240db119fe3c60aafd4b0513937b588af",
+ "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/60c6fbc27c9c2cbdf3c7da90b080a63d2599cd2d",
+ "reference": "60c6fbc27c9c2cbdf3c7da90b080a63d2599cd2d",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/console": "~5.5.0|~5.6.0",
+ "illuminate/contracts": "~5.5.0|~5.6.0",
+ "illuminate/events": "~5.5.0|~5.6.0",
+ "illuminate/filesystem": "~5.5.0|~5.6.0",
+ "illuminate/notifications": "~5.5.0|~5.6.0",
+ "illuminate/support": "~5.5.0|~5.6.0",
+ "league/flysystem": "^1.0.27",
+ "php": "^7.1",
+ "spatie/db-dumper": "^2.10",
+ "spatie/temporary-directory": "^1.1",
+ "symfony/finder": "^3.3|^4.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "orchestra/testbench": "~3.5.0|~3.6.0",
+ "phpunit/phpunit": "^6.5 || ^7.0"
+ },
+ "suggest": {
+ "guzzlehttp/guzzle": "Allows notifications to be sent via Slack"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\Backup\\BackupServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Backup\\": "src"
+ },
+ "files": [
+ "src/Helpers/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "A Laravel 5 package to backup your application",
+ "homepage": "https://github.com/spatie/laravel-backup",
+ "keywords": [
+ "backup",
+ "database",
+ "laravel-backup",
+ "spatie"
+ ],
+ "time": "2018-05-11T07:06:58+00:00"
+ },
+ {
+ "name": "spatie/laravel-image-optimizer",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-image-optimizer.git",
+ "reference": "f98d1a8e90851ed0384b46f9b692297d47688a0c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-image-optimizer/zipball/f98d1a8e90851ed0384b46f9b692297d47688a0c",
+ "reference": "f98d1a8e90851ed0384b46f9b692297d47688a0c",
"shasum": ""
},
"require": {
@@ -2151,7 +2729,7 @@
"laravel-image-optimizer",
"spatie"
],
- "time": "2018-02-08T13:45:21+00:00"
+ "time": "2018-05-16T14:07:07+00:00"
},
{
"name": "spatie/laravel-partialcache",
@@ -2212,6 +2790,52 @@
],
"time": "2018-04-16T15:27:14+00:00"
},
+ {
+ "name": "spatie/temporary-directory",
+ "version": "1.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/temporary-directory.git",
+ "reference": "5e1799fa2297363ebfb4df296fea90afbd4ef9b7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/5e1799fa2297363ebfb4df296fea90afbd4ef9b7",
+ "reference": "5e1799fa2297363ebfb4df296fea90afbd4ef9b7",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\TemporaryDirectory\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alex Vanderbist",
+ "email": "alex@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Easily create, use and destroy temporary directories",
+ "homepage": "https://github.com/spatie/temporary-directory",
+ "keywords": [
+ "spatie",
+ "temporary-directory"
+ ],
+ "time": "2018-04-12T09:34:43+00:00"
+ },
{
"name": "swiftmailer/swiftmailer",
"version": "v6.0.2",
@@ -2269,16 +2893,16 @@
},
{
"name": "symfony/console",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "aad9a6fe47319f22748fd764f52d3a7ca6fa6b64"
+ "reference": "2d5d973bf9933d46802b01010bd25c800c87c242"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/aad9a6fe47319f22748fd764f52d3a7ca6fa6b64",
- "reference": "aad9a6fe47319f22748fd764f52d3a7ca6fa6b64",
+ "url": "https://api.github.com/repos/symfony/console/zipball/2d5d973bf9933d46802b01010bd25c800c87c242",
+ "reference": "2d5d973bf9933d46802b01010bd25c800c87c242",
"shasum": ""
},
"require": {
@@ -2298,7 +2922,7 @@
"symfony/process": "~3.4|~4.0"
},
"suggest": {
- "psr/log": "For using the console logger",
+ "psr/log-implementation": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
@@ -2306,7 +2930,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -2333,20 +2957,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2018-04-03T05:24:00+00:00"
+ "time": "2018-05-30T07:26:09+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "03f965583147957f1ecbad7ea1c9d6fd5e525ec2"
+ "reference": "03ac71606ecb0b0ce792faa17d74cc32c2949ef4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/03f965583147957f1ecbad7ea1c9d6fd5e525ec2",
- "reference": "03f965583147957f1ecbad7ea1c9d6fd5e525ec2",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/03ac71606ecb0b0ce792faa17d74cc32c2949ef4",
+ "reference": "03ac71606ecb0b0ce792faa17d74cc32c2949ef4",
"shasum": ""
},
"require": {
@@ -2355,7 +2979,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -2386,20 +3010,20 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2018-03-19T22:35:49+00:00"
+ "time": "2018-05-30T07:26:09+00:00"
},
{
"name": "symfony/debug",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
- "reference": "5961d02d48828671f5d8a7805e06579d692f6ede"
+ "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/5961d02d48828671f5d8a7805e06579d692f6ede",
- "reference": "5961d02d48828671f5d8a7805e06579d692f6ede",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/449f8b00b28ab6e6912c3e6b920406143b27193b",
+ "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b",
"shasum": ""
},
"require": {
@@ -2415,7 +3039,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -2442,20 +3066,20 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
- "time": "2018-04-03T05:24:00+00:00"
+ "time": "2018-05-16T14:33:22+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "63353a71073faf08f62caab4e6889b06a787f07b"
+ "reference": "2391ed210a239868e7256eb6921b1bd83f3087b5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/63353a71073faf08f62caab4e6889b06a787f07b",
- "reference": "63353a71073faf08f62caab4e6889b06a787f07b",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2391ed210a239868e7256eb6921b1bd83f3087b5",
+ "reference": "2391ed210a239868e7256eb6921b1bd83f3087b5",
"shasum": ""
},
"require": {
@@ -2478,7 +3102,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -2505,20 +3129,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2018-04-06T07:35:43+00:00"
+ "time": "2018-04-06T07:35:57+00:00"
},
{
"name": "symfony/finder",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "ca27c02b7a3fef4828c998c2ff9ba7aae1641c49"
+ "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/ca27c02b7a3fef4828c998c2ff9ba7aae1641c49",
- "reference": "ca27c02b7a3fef4828c998c2ff9ba7aae1641c49",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/087e2ee0d74464a4c6baac4e90417db7477dc238",
+ "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238",
"shasum": ""
},
"require": {
@@ -2527,7 +3151,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -2554,20 +3178,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2018-04-04T05:10:37+00:00"
+ "time": "2018-05-16T14:33:22+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "d0864a82e5891ab61d31eecbaa48bed5a09b8e6c"
+ "reference": "a916c88390fb861ee21f12a92b107d51bb68af99"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0864a82e5891ab61d31eecbaa48bed5a09b8e6c",
- "reference": "d0864a82e5891ab61d31eecbaa48bed5a09b8e6c",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a916c88390fb861ee21f12a92b107d51bb68af99",
+ "reference": "a916c88390fb861ee21f12a92b107d51bb68af99",
"shasum": ""
},
"require": {
@@ -2575,12 +3199,13 @@
"symfony/polyfill-mbstring": "~1.1"
},
"require-dev": {
+ "predis/predis": "~1.0",
"symfony/expression-language": "~3.4|~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -2607,33 +3232,34 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2018-04-03T05:24:00+00:00"
+ "time": "2018-05-25T14:55:38+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "6dd620d96d64456075536ffe3c6c4658dd689021"
+ "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6dd620d96d64456075536ffe3c6c4658dd689021",
- "reference": "6dd620d96d64456075536ffe3c6c4658dd689021",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90",
+ "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90",
"shasum": ""
},
"require": {
"php": "^7.1.3",
"psr/log": "~1.0",
"symfony/debug": "~3.4|~4.0",
- "symfony/event-dispatcher": "~3.4|~4.0",
- "symfony/http-foundation": "~3.4.4|~4.0.4"
+ "symfony/event-dispatcher": "~4.1",
+ "symfony/http-foundation": "~4.1",
+ "symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/config": "<3.4",
- "symfony/dependency-injection": "<3.4.5|<4.0.5,>=4",
- "symfony/var-dumper": "<3.4",
+ "symfony/dependency-injection": "<4.1",
+ "symfony/var-dumper": "<4.1",
"twig/twig": "<1.34|<2.4,>=2"
},
"provide": {
@@ -2645,7 +3271,7 @@
"symfony/config": "~3.4|~4.0",
"symfony/console": "~3.4|~4.0",
"symfony/css-selector": "~3.4|~4.0",
- "symfony/dependency-injection": "^3.4.5|^4.0.5",
+ "symfony/dependency-injection": "^4.1",
"symfony/dom-crawler": "~3.4|~4.0",
"symfony/expression-language": "~3.4|~4.0",
"symfony/finder": "~3.4|~4.0",
@@ -2654,7 +3280,7 @@
"symfony/stopwatch": "~3.4|~4.0",
"symfony/templating": "~3.4|~4.0",
"symfony/translation": "~3.4|~4.0",
- "symfony/var-dumper": "~3.4|~4.0"
+ "symfony/var-dumper": "~4.1"
},
"suggest": {
"symfony/browser-kit": "",
@@ -2666,7 +3292,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -2693,20 +3319,75 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2018-04-06T16:25:03+00:00"
+ "time": "2018-05-30T12:52:34+00:00"
},
{
- "name": "symfony/polyfill-mbstring",
- "version": "v1.7.0",
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.8.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b"
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b",
- "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae",
+ "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ },
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "time": "2018-04-30T19:57:29+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "3296adf6a6454a050679cde90f95350ad604b171"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171",
+ "reference": "3296adf6a6454a050679cde90f95350ad604b171",
"shasum": ""
},
"require": {
@@ -2718,7 +3399,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.7-dev"
+ "dev-master": "1.8-dev"
}
},
"autoload": {
@@ -2752,20 +3433,20 @@
"portable",
"shim"
],
- "time": "2018-01-30T19:27:44+00:00"
+ "time": "2018-04-26T10:06:28+00:00"
},
{
"name": "symfony/polyfill-php72",
- "version": "v1.7.0",
+ "version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
- "reference": "8eca20c8a369e069d4f4c2ac9895144112867422"
+ "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/8eca20c8a369e069d4f4c2ac9895144112867422",
- "reference": "8eca20c8a369e069d4f4c2ac9895144112867422",
+ "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/a4576e282d782ad82397f3e4ec1df8e0f0cafb46",
+ "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46",
"shasum": ""
},
"require": {
@@ -2774,7 +3455,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.7-dev"
+ "dev-master": "1.8-dev"
}
},
"autoload": {
@@ -2807,20 +3488,20 @@
"portable",
"shim"
],
- "time": "2018-01-31T17:43:24+00:00"
+ "time": "2018-04-26T10:06:28+00:00"
},
{
"name": "symfony/process",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "d7dc1ee5dfe9f732cb1bba7310f5b99f2b7a6d25"
+ "reference": "73445bd33b0d337c060eef9652b94df72b6b3434"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/d7dc1ee5dfe9f732cb1bba7310f5b99f2b7a6d25",
- "reference": "d7dc1ee5dfe9f732cb1bba7310f5b99f2b7a6d25",
+ "url": "https://api.github.com/repos/symfony/process/zipball/73445bd33b0d337c060eef9652b94df72b6b3434",
+ "reference": "73445bd33b0d337c060eef9652b94df72b6b3434",
"shasum": ""
},
"require": {
@@ -2829,7 +3510,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -2856,20 +3537,20 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2018-04-03T05:24:00+00:00"
+ "time": "2018-05-30T07:26:09+00:00"
},
{
"name": "symfony/routing",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "0663036dd57dbfd4e9ff29f75bbd5dd3253ebe71"
+ "reference": "180b51c66d10f09e562c9ebc395b39aacb2cf8a2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/0663036dd57dbfd4e9ff29f75bbd5dd3253ebe71",
- "reference": "0663036dd57dbfd4e9ff29f75bbd5dd3253ebe71",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/180b51c66d10f09e562c9ebc395b39aacb2cf8a2",
+ "reference": "180b51c66d10f09e562c9ebc395b39aacb2cf8a2",
"shasum": ""
},
"require": {
@@ -2901,7 +3582,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -2934,20 +3615,20 @@
"uri",
"url"
],
- "time": "2018-04-04T13:50:32+00:00"
+ "time": "2018-05-30T07:26:09+00:00"
},
{
"name": "symfony/translation",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "e20a9b7f9f62cb33a11638b345c248e7d510c938"
+ "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/e20a9b7f9f62cb33a11638b345c248e7d510c938",
- "reference": "e20a9b7f9f62cb33a11638b345c248e7d510c938",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/16328f5b217cebc8dd4adfe4aeeaa8c377581f5a",
+ "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a",
"shasum": ""
},
"require": {
@@ -2962,20 +3643,21 @@
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~3.4|~4.0",
+ "symfony/console": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/finder": "~2.8|~3.0|~4.0",
"symfony/intl": "~3.4|~4.0",
"symfony/yaml": "~3.4|~4.0"
},
"suggest": {
- "psr/log": "To use logging capability in translator",
+ "psr/log-implementation": "To use logging capability in translator",
"symfony/config": "",
"symfony/yaml": ""
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -3002,20 +3684,20 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2018-02-22T10:50:29+00:00"
+ "time": "2018-05-30T07:26:09+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v4.0.8",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "e1b4d008100f4d203cc38b0d793ad6252d8d8af0"
+ "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e1b4d008100f4d203cc38b0d793ad6252d8d8af0",
- "reference": "e1b4d008100f4d203cc38b0d793ad6252d8d8af0",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/bc88ad53e825ebacc7b190bbd360781fce381c64",
+ "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64",
"shasum": ""
},
"require": {
@@ -3024,20 +3706,26 @@
"symfony/polyfill-php72": "~1.5"
},
"conflict": {
- "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
+ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+ "symfony/console": "<3.4"
},
"require-dev": {
"ext-iconv": "*",
+ "symfony/process": "~3.4|~4.0",
"twig/twig": "~1.34|~2.4"
},
"suggest": {
"ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
- "ext-intl": "To show region name in time zone dump"
+ "ext-intl": "To show region name in time zone dump",
+ "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
},
+ "bin": [
+ "Resources/bin/var-dump-server"
+ ],
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -3071,7 +3759,57 @@
"debug",
"dump"
],
- "time": "2018-04-04T05:10:37+00:00"
+ "time": "2018-04-29T07:56:09+00:00"
+ },
+ {
+ "name": "tightenco/collect",
+ "version": "v5.6.23",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/tightenco/collect.git",
+ "reference": "0954fc3ca147a7d727d807e15113daba4a08c810"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/tightenco/collect/zipball/0954fc3ca147a7d727d807e15113daba4a08c810",
+ "reference": "0954fc3ca147a7d727d807e15113daba4a08c810",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1.3",
+ "symfony/var-dumper": ">=3.1.10"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.0",
+ "nesbot/carbon": "~1.20",
+ "phpunit/phpunit": "~7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/Collect/Support/helpers.php",
+ "src/Collect/Support/alias.php"
+ ],
+ "psr-4": {
+ "Tightenco\\Collect\\": "src/Collect"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylorotwell@gmail.com"
+ }
+ ],
+ "description": "Collect - Illuminate Collections as a separate package.",
+ "keywords": [
+ "collection",
+ "laravel"
+ ],
+ "time": "2018-05-22T17:57:22+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -3174,16 +3912,16 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
- "version": "v3.1.4",
+ "version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
- "reference": "7a91480cc6e597caed5117a3c5d685f06d35c5a1"
+ "reference": "d3cdca2ad6cc6e67735b4a63e7551c690a497f5f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/7a91480cc6e597caed5117a3c5d685f06d35c5a1",
- "reference": "7a91480cc6e597caed5117a3c5d685f06d35c5a1",
+ "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/d3cdca2ad6cc6e67735b4a63e7551c690a497f5f",
+ "reference": "d3cdca2ad6cc6e67735b4a63e7551c690a497f5f",
"shasum": ""
},
"require": {
@@ -3238,7 +3976,7 @@
"profiler",
"webprofiler"
],
- "time": "2018-03-06T08:35:31+00:00"
+ "time": "2018-05-03T18:27:04+00:00"
},
{
"name": "doctrine/instantiator",
@@ -3516,16 +4254,16 @@
},
{
"name": "mockery/mockery",
- "version": "1.0",
+ "version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
- "reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38"
+ "reference": "99e29d3596b16dabe4982548527d5ddf90232e99"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/mockery/mockery/zipball/1bac8c362b12f522fdd1f1fa3556284c91affa38",
- "reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38",
+ "url": "https://api.github.com/repos/mockery/mockery/zipball/99e29d3596b16dabe4982548527d5ddf90232e99",
+ "reference": "99e29d3596b16dabe4982548527d5ddf90232e99",
"shasum": ""
},
"require": {
@@ -3534,7 +4272,8 @@
"php": ">=5.6.0"
},
"require-dev": {
- "phpunit/phpunit": "~5.7|~6.1"
+ "phpdocumentor/phpdocumentor": "^2.9",
+ "phpunit/phpunit": "~5.7.10|~6.5"
},
"type": "library",
"extra": {
@@ -3553,7 +4292,7 @@
],
"authors": [
{
- "name": "Padraic Brady",
+ "name": "Pádraic Brady",
"email": "padraic.brady@gmail.com",
"homepage": "http://blog.astrumfutura.com"
},
@@ -3563,8 +4302,8 @@
"homepage": "http://davedevelopment.co.uk"
}
],
- "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.",
- "homepage": "http://github.com/mockery/mockery",
+ "description": "Mockery is a simple yet flexible PHP mock object framework",
+ "homepage": "https://github.com/mockery/mockery",
"keywords": [
"BDD",
"TDD",
@@ -3577,29 +4316,32 @@
"test double",
"testing"
],
- "time": "2017-10-06T16:20:43+00:00"
+ "time": "2018-05-08T08:54:48+00:00"
},
{
"name": "myclabs/deep-copy",
- "version": "1.7.0",
+ "version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
+ "reference": "478465659fd987669df0bd8a9bf22a8710e5f1b6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
- "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/478465659fd987669df0bd8a9bf22a8710e5f1b6",
+ "reference": "478465659fd987669df0bd8a9bf22a8710e5f1b6",
"shasum": ""
},
"require": {
- "php": "^5.6 || ^7.0"
+ "php": "^7.1"
+ },
+ "replace": {
+ "myclabs/deep-copy": "self.version"
},
"require-dev": {
"doctrine/collections": "^1.0",
"doctrine/common": "^2.6",
- "phpunit/phpunit": "^4.1"
+ "phpunit/phpunit": "^7.1"
},
"type": "library",
"autoload": {
@@ -3622,7 +4364,7 @@
"object",
"object graph"
],
- "time": "2017-10-19T19:58:43+00:00"
+ "time": "2018-05-29T17:25:09+00:00"
},
{
"name": "nunomaduro/collision",
@@ -3942,23 +4684,23 @@
},
{
"name": "phpspec/prophecy",
- "version": "1.7.5",
+ "version": "1.7.6",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401"
+ "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401",
- "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
+ "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
- "sebastian/comparator": "^1.1|^2.0",
+ "sebastian/comparator": "^1.1|^2.0|^3.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0"
},
"require-dev": {
@@ -4001,27 +4743,27 @@
"spy",
"stub"
],
- "time": "2018-02-19T10:16:54+00:00"
+ "time": "2018-04-18T13:57:24+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "6.0.3",
+ "version": "6.0.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "774a82c0c5da4c1c7701790c262035d235ab7856"
+ "reference": "865662550c384bc1db7e51d29aeda1c2c161d69a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/774a82c0c5da4c1c7701790c262035d235ab7856",
- "reference": "774a82c0c5da4c1c7701790c262035d235ab7856",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/865662550c384bc1db7e51d29aeda1c2c161d69a",
+ "reference": "865662550c384bc1db7e51d29aeda1c2c161d69a",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-xmlwriter": "*",
"php": "^7.1",
- "phpunit/php-file-iterator": "^1.4.2",
+ "phpunit/php-file-iterator": "^2.0",
"phpunit/php-text-template": "^1.2.1",
"phpunit/php-token-stream": "^3.0",
"sebastian/code-unit-reverse-lookup": "^1.0.1",
@@ -4064,29 +4806,29 @@
"testing",
"xunit"
],
- "time": "2018-04-06T15:39:20+00:00"
+ "time": "2018-06-01T07:51:50+00:00"
},
{
"name": "phpunit/php-file-iterator",
- "version": "1.4.5",
+ "version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
+ "reference": "e20525b0c2945c7c317fff95660698cb3d2a53bc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
- "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/e20525b0c2945c7c317fff95660698cb3d2a53bc",
+ "reference": "e20525b0c2945c7c317fff95660698cb3d2a53bc",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": "^7.1"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4.x-dev"
+ "dev-master": "2.0.x-dev"
}
},
"autoload": {
@@ -4101,7 +4843,7 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
@@ -4111,7 +4853,7 @@
"filesystem",
"iterator"
],
- "time": "2017-11-27T13:52:08+00:00"
+ "time": "2018-05-28T12:13:49+00:00"
},
{
"name": "phpunit/php-text-template",
@@ -4254,35 +4996,35 @@
},
{
"name": "phpunit/phpunit",
- "version": "7.1.3",
+ "version": "7.2.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "a7834993ddbf4b0ed2c3b2dc1f3b1d093ef910a9"
+ "reference": "3cf0836680bf5c365c627e8566d46c9e1f544db9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a7834993ddbf4b0ed2c3b2dc1f3b1d093ef910a9",
- "reference": "a7834993ddbf4b0ed2c3b2dc1f3b1d093ef910a9",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3cf0836680bf5c365c627e8566d46c9e1f544db9",
+ "reference": "3cf0836680bf5c365c627e8566d46c9e1f544db9",
"shasum": ""
},
"require": {
+ "doctrine/instantiator": "^1.1",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xml": "*",
- "myclabs/deep-copy": "^1.6.1",
+ "myclabs/deep-copy": "^1.7",
"phar-io/manifest": "^1.0.1",
"phar-io/version": "^1.0",
"php": "^7.1",
"phpspec/prophecy": "^1.7",
- "phpunit/php-code-coverage": "^6.0.1",
- "phpunit/php-file-iterator": "^1.4.3",
+ "phpunit/php-code-coverage": "^6.0.7",
+ "phpunit/php-file-iterator": "^2.0",
"phpunit/php-text-template": "^1.2.1",
"phpunit/php-timer": "^2.0",
- "phpunit/phpunit-mock-objects": "^6.1.1",
- "sebastian/comparator": "^2.1",
+ "sebastian/comparator": "^3.0",
"sebastian/diff": "^3.0",
"sebastian/environment": "^3.1",
"sebastian/exporter": "^3.1",
@@ -4291,10 +5033,14 @@
"sebastian/resource-operations": "^1.0",
"sebastian/version": "^2.0.1"
},
+ "conflict": {
+ "phpunit/phpunit-mock-objects": "*"
+ },
"require-dev": {
"ext-pdo": "*"
},
"suggest": {
+ "ext-soap": "*",
"ext-xdebug": "*",
"phpunit/php-invoker": "^2.0"
},
@@ -4304,7 +5050,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "7.1-dev"
+ "dev-master": "7.2-dev"
}
},
"autoload": {
@@ -4330,63 +5076,7 @@
"testing",
"xunit"
],
- "time": "2018-04-13T02:28:50+00:00"
- },
- {
- "name": "phpunit/phpunit-mock-objects",
- "version": "6.1.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
- "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/70c740bde8fd9ea9ea295be1cd875dd7b267e157",
- "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157",
- "shasum": ""
- },
- "require": {
- "doctrine/instantiator": "^1.0.5",
- "php": "^7.1",
- "phpunit/php-text-template": "^1.2.1",
- "sebastian/exporter": "^3.1"
- },
- "require-dev": {
- "phpunit/phpunit": "^7.0"
- },
- "suggest": {
- "ext-soap": "*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "6.1-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Mock Object library for PHPUnit",
- "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
- "keywords": [
- "mock",
- "xunit"
- ],
- "time": "2018-04-11T04:50:36+00:00"
+ "time": "2018-06-01T07:54:27+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@@ -4435,30 +5125,30 @@
},
{
"name": "sebastian/comparator",
- "version": "2.1.3",
+ "version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9"
+ "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9",
- "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/ed5fd2281113729f1ebcc64d101ad66028aeb3d5",
+ "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5",
"shasum": ""
},
"require": {
- "php": "^7.0",
- "sebastian/diff": "^2.0 || ^3.0",
+ "php": "^7.1",
+ "sebastian/diff": "^3.0",
"sebastian/exporter": "^3.1"
},
"require-dev": {
- "phpunit/phpunit": "^6.4"
+ "phpunit/phpunit": "^7.1"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.1.x-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -4495,7 +5185,7 @@
"compare",
"equality"
],
- "time": "2018-02-01T13:46:46+00:00"
+ "time": "2018-04-18T13:33:00+00:00"
},
{
"name": "sebastian/diff",
@@ -5044,7 +5734,9 @@
],
"aliases": [],
"minimum-stability": "dev",
- "stability-flags": [],
+ "stability-flags": {
+ "greggilbert/recaptcha": 20
+ },
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
diff --git a/config/app.php b/config/app.php
index b16e7f77e..a281d980b 100644
--- a/config/app.php
+++ b/config/app.php
@@ -65,7 +65,7 @@ return [
|
*/
- 'timezone' => 'UTC',
+ 'timezone' => env('APP_TIMEZONE', 'UTC'),
/*
|--------------------------------------------------------------------------
@@ -78,7 +78,7 @@ return [
|
*/
- 'locale' => 'en',
+ 'locale' => env('APP_LOCALE', 'en'),
/*
|--------------------------------------------------------------------------
@@ -91,7 +91,7 @@ return [
|
*/
- 'fallback_locale' => 'en',
+ 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
/*
|--------------------------------------------------------------------------
@@ -150,6 +150,7 @@ return [
/*
* Package Service Providers...
*/
+ Greggilbert\Recaptcha\RecaptchaServiceProvider::class,
/*
* Application Service Providers...
@@ -209,6 +210,7 @@ return [
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
+ 'Recaptcha' => Greggilbert\Recaptcha\Facades\Recaptcha::class,
],
];
diff --git a/config/backup.php b/config/backup.php
new file mode 100644
index 000000000..a1fc9bd98
--- /dev/null
+++ b/config/backup.php
@@ -0,0 +1,182 @@
+ [
+
+ /*
+ * The name of this application. You can use this name to monitor
+ * the backups.
+ */
+ 'name' => config('app.name'),
+
+ 'source' => [
+
+ 'files' => [
+
+ /*
+ * The list of directories and files that will be included in the backup.
+ */
+ 'include' => [
+ base_path(),
+ ],
+
+ /*
+ * These directories and files will be excluded from the backup.
+ *
+ * Directories used by the backup process will automatically be excluded.
+ */
+ 'exclude' => [
+ base_path('vendor'),
+ base_path('node_modules'),
+ ],
+
+ /*
+ * Determines if symlinks should be followed.
+ */
+ 'followLinks' => false,
+ ],
+
+ /*
+ * The names of the connections to the databases that should be backed up
+ * MySQL, PostgreSQL, SQLite and Mongo databases are supported.
+ */
+ 'databases' => [
+ 'mysql',
+ ],
+ ],
+
+ /*
+ * The database dump can be gzipped to decrease diskspace usage.
+ */
+ 'gzip_database_dump' => false,
+
+ 'destination' => [
+
+ /*
+ * The filename prefix used for the backup zip file.
+ */
+ 'filename_prefix' => '',
+
+ /*
+ * The disk names on which the backups will be stored.
+ */
+ 'disks' => [
+ 'local',
+ ],
+ ],
+ ],
+
+ /*
+ * You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'.
+ * For Slack you need to install guzzlehttp/guzzle.
+ *
+ * You can also use your own notification classes, just make sure the class is named after one of
+ * the `Spatie\Backup\Events` classes.
+ */
+ 'notifications' => [
+
+ 'notifications' => [
+ \Spatie\Backup\Notifications\Notifications\BackupHasFailed::class => ['mail'],
+ \Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFound::class => ['mail'],
+ \Spatie\Backup\Notifications\Notifications\CleanupHasFailed::class => ['mail'],
+ \Spatie\Backup\Notifications\Notifications\BackupWasSuccessful::class => ['mail'],
+ \Spatie\Backup\Notifications\Notifications\HealthyBackupWasFound::class => ['mail'],
+ \Spatie\Backup\Notifications\Notifications\CleanupWasSuccessful::class => ['mail'],
+ ],
+
+ /*
+ * Here you can specify the notifiable to which the notifications should be sent. The default
+ * notifiable will use the variables specified in this config file.
+ */
+ 'notifiable' => \Spatie\Backup\Notifications\Notifiable::class,
+
+ 'mail' => [
+ 'to' => '',
+ ],
+
+ 'slack' => [
+ 'webhook_url' => '',
+
+ /*
+ * If this is set to null the default channel of the webhook will be used.
+ */
+ 'channel' => null,
+
+ 'username' => null,
+
+ 'icon' => null,
+
+ ],
+ ],
+
+ /*
+ * Here you can specify which backups should be monitored.
+ * If a backup does not meet the specified requirements the
+ * UnHealthyBackupWasFound event will be fired.
+ */
+ 'monitorBackups' => [
+ [
+ 'name' => config('app.name'),
+ 'disks' => ['local'],
+ 'newestBackupsShouldNotBeOlderThanDays' => 1,
+ 'storageUsedMayNotBeHigherThanMegabytes' => 5000,
+ ],
+
+ /*
+ [
+ 'name' => 'name of the second app',
+ 'disks' => ['local', 's3'],
+ 'newestBackupsShouldNotBeOlderThanDays' => 1,
+ 'storageUsedMayNotBeHigherThanMegabytes' => 5000,
+ ],
+ */
+ ],
+
+ 'cleanup' => [
+ /*
+ * The strategy that will be used to cleanup old backups. The default strategy
+ * will keep all backups for a certain amount of days. After that period only
+ * a daily backup will be kept. After that period only weekly backups will
+ * be kept and so on.
+ *
+ * No matter how you configure it the default strategy will never
+ * delete the newest backup.
+ */
+ 'strategy' => \Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy::class,
+
+ 'defaultStrategy' => [
+
+ /*
+ * The number of days for which backups must be kept.
+ */
+ 'keepAllBackupsForDays' => 7,
+
+ /*
+ * The number of days for which daily backups must be kept.
+ */
+ 'keepDailyBackupsForDays' => 16,
+
+ /*
+ * The number of weeks for which one weekly backup must be kept.
+ */
+ 'keepWeeklyBackupsForWeeks' => 8,
+
+ /*
+ * The number of months for which one monthly backup must be kept.
+ */
+ 'keepMonthlyBackupsForMonths' => 4,
+
+ /*
+ * The number of years for which one yearly backup must be kept.
+ */
+ 'keepYearlyBackupsForYears' => 2,
+
+ /*
+ * After cleaning up the backups remove the oldest backup until
+ * this amount of megabytes has been reached.
+ */
+ 'deleteOldestBackupsWhenUsingMoreMegabytesThan' => 5000,
+ ],
+ ],
+];
diff --git a/config/cache.php b/config/cache.php
index fa12e5e4f..7045c4f30 100644
--- a/config/cache.php
+++ b/config/cache.php
@@ -70,7 +70,15 @@ return [
'redis' => [
'driver' => 'redis',
- 'connection' => 'default',
+ 'client' => 'predis',
+
+ 'default' => [
+ 'host' => env('REDIS_HOST', 'localhost'),
+ 'password' => env('REDIS_PASSWORD', null),
+ 'port' => env('REDIS_PORT', 6379),
+ 'database' => env('REDIS_DATABASE', 0),
+ ],
+
],
],
diff --git a/config/debugbar.php b/config/debugbar.php
new file mode 100644
index 000000000..4fada2da2
--- /dev/null
+++ b/config/debugbar.php
@@ -0,0 +1,201 @@
+ env('DEBUGBAR_ENABLED', false),
+ 'except' => [
+ //
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Storage settings
+ |--------------------------------------------------------------------------
+ |
+ | DebugBar stores data for session/ajax requests.
+ | You can disable this, so the debugbar stores data in headers/session,
+ | but this can cause problems with large data collectors.
+ | By default, file storage (in the storage folder) is used. Redis and PDO
+ | can also be used. For PDO, run the package migrations first.
+ |
+ */
+ 'storage' => [
+ 'enabled' => true,
+ 'driver' => 'file', // redis, file, pdo, custom
+ 'path' => storage_path('debugbar'), // For file driver
+ 'connection' => null, // Leave null for default connection (Redis/PDO)
+ 'provider' => '' // Instance of StorageInterface for custom driver
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Vendors
+ |--------------------------------------------------------------------------
+ |
+ | Vendor files are included by default, but can be set to false.
+ | This can also be set to 'js' or 'css', to only include javascript or css vendor files.
+ | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
+ | and for js: jquery and and highlight.js
+ | So if you want syntax highlighting, set it to true.
+ | jQuery is set to not conflict with existing jQuery scripts.
+ |
+ */
+
+ 'include_vendors' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Capture Ajax Requests
+ |--------------------------------------------------------------------------
+ |
+ | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors),
+ | you can use this option to disable sending the data through the headers.
+ |
+ | Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
+ */
+
+ 'capture_ajax' => true,
+ 'add_ajax_timing' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Custom Error Handler for Deprecated warnings
+ |--------------------------------------------------------------------------
+ |
+ | When enabled, the Debugbar shows deprecated warnings for Symfony components
+ | in the Messages tab.
+ |
+ */
+ 'error_handler' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Clockwork integration
+ |--------------------------------------------------------------------------
+ |
+ | The Debugbar can emulate the Clockwork headers, so you can use the Chrome
+ | Extension, without the server-side code. It uses Debugbar collectors instead.
+ |
+ */
+ 'clockwork' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | DataCollectors
+ |--------------------------------------------------------------------------
+ |
+ | Enable/disable DataCollectors
+ |
+ */
+
+ 'collectors' => [
+ 'phpinfo' => true, // Php version
+ 'messages' => true, // Messages
+ 'time' => true, // Time Datalogger
+ 'memory' => true, // Memory usage
+ 'exceptions' => true, // Exception displayer
+ 'log' => true, // Logs from Monolog (merged in messages if enabled)
+ 'db' => true, // Show database (PDO) queries and bindings
+ 'views' => true, // Views with their data
+ 'route' => true, // Current route information
+ 'auth' => true, // Display Laravel authentication status
+ 'gate' => true, // Display Laravel Gate checks
+ 'session' => true, // Display session data
+ 'symfony_request' => true, // Only one can be enabled..
+ 'mail' => true, // Catch mail messages
+ 'laravel' => false, // Laravel version and environment
+ 'events' => false, // All events fired
+ 'default_request' => false, // Regular or special Symfony request logger
+ 'logs' => false, // Add the latest log messages
+ 'files' => false, // Show the included files
+ 'config' => false, // Display config settings
+ 'cache' => false, // Display cache events
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Extra options
+ |--------------------------------------------------------------------------
+ |
+ | Configure some DataCollectors
+ |
+ */
+
+ 'options' => [
+ 'auth' => [
+ 'show_name' => true, // Also show the users name/email in the debugbar
+ ],
+ 'db' => [
+ 'with_params' => true, // Render SQL with the parameters substituted
+ 'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
+ 'timeline' => false, // Add the queries to the timeline
+ 'explain' => [ // Show EXPLAIN output on queries
+ 'enabled' => false,
+ 'types' => ['SELECT'], // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+
+ ],
+ 'hints' => true, // Show hints for common mistakes
+ ],
+ 'mail' => [
+ 'full_log' => false
+ ],
+ 'views' => [
+ 'data' => false, //Note: Can slow down the application, because the data can be quite large..
+ ],
+ 'route' => [
+ 'label' => true // show complete route on bar
+ ],
+ 'logs' => [
+ 'file' => null
+ ],
+ 'cache' => [
+ 'values' => true // collect cache values
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Inject Debugbar in Response
+ |--------------------------------------------------------------------------
+ |
+ | Usually, the debugbar is added just before