feat: initial ACRIB WordPress deployment

- WordPress 6.9.4 (es_ES) with Kadence theme
- Homepage: Hero, La Asociación, Pilares, Beneficios, Eventos, Miembros, Hazte Miembro, Contacto
- Brand identity: #13294b navy, #a12932 burgundy, #c69c48 gold
- Fonts: Raleway (headings) + Source Sans 3 (body) + Lato (UI)
- Plugins: Kadence Blocks, Polylang, Contact Form 7
- Custom CSS with full brand styling and responsive layout
- HTTPS enforced via wp-config.php proxy detection
This commit is contained in:
Malin
2026-05-19 19:25:59 +02:00
commit f3ff7b7186
6119 changed files with 1984255 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
StellarWP Arrays is a PHP library providing 50+ array manipulation utilities for WordPress plugins and PHP projects. It's designed as a standalone library that can be integrated into any PHP 7.4+ project.
## Commands
### Testing
```bash
# Run tests with SLIC
slic use arrays
slic run wpunit
# Run specific test
slic run wpunit tests/wpunit/GetTest.php
```
### Static Analysis
```bash
# Run PHPStan static analysis (level 5)
composer test:analysis
```
### Documentation
```bash
# Generate API documentation
composer create-docs
```
### Development Setup
```bash
# Install dependencies
composer install
# The arrays.php file is a WordPress plugin bootstrap for testing only
# Actual library code is in src/Arrays/Arr.php
```
## Architecture
### Core Structure
- **Main Class**: `StellarWP\Arrays\Arr` in `src/Arrays/Arr.php` - Contains all array manipulation methods
- **Namespace**: `StellarWP\Arrays` (PSR-4 autoloaded)
- **Methods**: 50+ static methods for array manipulation (get, set, filter, map, etc.)
### Testing Architecture
- **Framework**: Codeception with WP Browser for WordPress integration
- **Test Suite**: `wpunit` - WordPress unit tests
- **Test Location**: `tests/wpunit/` - Each method has its own test file
- **Base Class**: Tests extend `ArraysTestCase` for common functionality
- **CI**: Uses SLIC (StellarWP's infrastructure CLI) for automated testing
### Key Design Patterns
1. **Static Methods**: All array utilities are static methods on the `Arr` class
2. **Dot Notation**: Many methods support dot notation for nested array access (e.g., 'user.profile.name')
3. **Fluent Interface**: Methods can be chained for complex operations
4. **Laravel-inspired**: Many methods ported from Laravel's array helpers
### Integration Notes
- Can be used standalone via Composer: `composer require stellarwp/arrays`
- For production use in plugins, use Strauss for namespace prefixing to avoid conflicts
- The `arrays.php` file is only for testing - not needed in production
### Development Considerations
- No JavaScript/frontend components - pure PHP library
- WordPress coding standards should be followed
- All public methods must have comprehensive PHPDoc blocks
- New methods require corresponding test files in `tests/wpunit/`

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 StellarWP
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,8 @@
<?php
/**
* Plugin Name: Arrays
* Description: Arrays library with a plugin bootstrap file for automated testing.
* Version: 1.3.2
* Author: StellarWP
* Author URI: https://stellarwp.com
*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -0,0 +1,35 @@
# StellarWP Container Contract
Service Container interface for use in StellarWP libraries.
Note that this is not a Container implementation of its own. It is merely abstractions that describe the components of a Dependency Injection Container.
## Installation
It's recommended that you install this contract as a project dependency via [Composer](https://getcomposer.org/):
```bash
composer require stellarwp/container-contract
```
_Note: If you typically use a tool like [Strauss](https://github.com/BrianHenryIE/strauss), you should **not** customize the namespace of this library in order to have proper compatibility with StellarWP libraries._
## Usage
Simply `use` the `ContainerInterface` and implement it or use it as a type hint.
```php
use StellarWP\ContainerContract\ContainerInterface;
class Container implements ContainerInterface {
public function bind( string $id, $implementation ) {}
public function get( string $id ) {}
public function has( string $id ) {}
public function singleton( string $id, $implementation ) {}
}
```
## Example wrappers of other containers
* [DI52](/examples/di52/Container.php) ([repo](https://github.com/lucatume/di52))
* [PHP-DI](/examples/php-di/Container.php) ([repo](https://github.com/PHP-DI/PHP-DI))

View File

@@ -0,0 +1,76 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\Your\Namespace;
use KadenceWP\KadenceBlocks\StellarWP\ContainerContract\ContainerInterface;
// If you are including lucatume\DI52 using Strauss (recommended), then:
use KadenceWP\KadenceBlocks\Your\Namespace\lucatume\DI52\Container as DI52Container;
// If you are including lucatume\DI52 directly, then you'd want to do:
// use KadenceWP\KadenceBlocks\lucatume\DI52\Container as DI52Container;
/**
* @method mixed getVar(string $key, mixed|null $default = null)
* @method void register(string $serviceProviderClass, string ...$alias)
* @method self when(string $class)
* @method self needs(string $id)
* @method void give(mixed $implementation)
*/
class Container implements ContainerInterface {
/**
* @var DI52Container
*/
protected $container;
/**
* Container constructor.
*
* @param object $container The container to use.
*/
public function __construct( $container = null ) {
$this->container = $container ?: new DI52Container();
}
/**
* @inheritDoc
*/
public function bind( string $id, $implementation = null, array $afterBuildMethods = null ) {
$this->container->bind( $id, $implementation, $afterBuildMethods );
}
/**
* @inheritDoc
*/
public function get( string $id ) {
return $this->container->get( $id );
}
/**
* @return DI52Container
*/
public function get_container() {
return $this->container;
}
/**
* @inheritDoc
*/
public function has( string $id ) {
return $this->container->has( $id );
}
/**
* @inheritDoc
*/
public function singleton( string $id, $implementation = null, array $afterBuildMethods = null ) {
$this->container->singleton( $id, $implementation, $afterBuildMethods );
}
/**
* Defer all other calls to the container object.
*/
public function __call( $name, $args ) {
return $this->container->{$name}( ...$args );
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace KadenceWP\KadenceBlocks\Your\Namespace;
use KadenceWP\KadenceBlocks\StellarWP\ContainerContract\ContainerInterface;
// If you are including PHP-DI container using Strauss (recommended), then:
use KadenceWP\KadenceBlocks\Your\Namespace\DI\Container as PHPDIContainer;
// If you are including the PHP-DI container directly, then you'd want to do:
//use DI\Container as PHPDIContainer;
class Container implements ContainerInterface {
protected $container;
/**
* Container constructor.
*/
public function __construct() {
$this->container = new PHPDIContainer();
}
/**
* @inheritDoc
*/
public function bind( string $id, $implementation = null ) {
$this->container->set( $id, $implementation );
}
/**
* @inheritDoc
*/
public function get( string $id ) {
return $this->container->get( $id );
}
/**
* @inheritDoc
*/
public function has( string $id ) {
return $this->container->has( $id );
}
/**
* @inheritDoc
*/
public function singleton( string $id, $implementation = null ) {
$this->container->set( $id, $implementation );
}
}

View File

@@ -0,0 +1,59 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\StellarWP\ContainerContract;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface {
/**
* Binds an interface, a class or a string slug to an implementation.
*
* Existing implementations are replaced.
*
* @param string|class-string $id Identifier of the entry to look for.
* @param mixed $implementation The implementation that should be bound to the alias(es); can be a
* class name, an object or a closure.
*
* @return void
*/
public function bind( string $id, $implementation = null );
/**
* Finds an entry of the container by its identifier and returns it.
*
* @template T
*
* @param string|class-string<T> $id Identifier of the entry to look for.
*
* @return T|mixed
* @phpstan-return ($id is class-string ? T : mixed)
*/
public function get( string $id );
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string|class-string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has( string $id );
/**
* Binds an interface a class or a string slug to an implementation and will always return the same instance.
*
* @param string|class-string $id Identifier of the entry to look for.
* @param mixed $implementation The implementation that should be bound to the alias(es); can be a
* class name, an object or a closure.
*
* @return void This method does not return any value.
*/
public function singleton( string $id, $implementation = null );
}

View File

@@ -0,0 +1,62 @@
# Change Log
All notable changes to this project will be documented in this file. This project adhere to the [Semantic Versioning](http://semver.org/) standard.
## [1.3.0] - 2026-02-25
### Added
- Transaction nesting support via savepoints. Nested `beginTransaction()` calls now create savepoints instead of issuing `START TRANSACTION`, which would implicitly commit any existing transaction.
- `DB::transactionLevel()` method to retrieve the current transaction nesting depth.
### Changed
- `DB::commit()` releases the current savepoint when nested, and issues a real `COMMIT` only at the outermost level.
- `DB::rollback()` rolls back to the current savepoint when nested, and issues a real `ROLLBACK` only at the outermost level.
- `tests-php.yml` ensure slic runs in PHP 7.4.
## [1.0.8] TBD
* Feat - Add the `DB::generate_results` and `DB::generate_col` methods to the `DB` class to fetch all results matching an unbounded query with a set of bounded queries.
## [1.0.7] 2023-10-23
* Tweak - Updates around `trim()` for php 8.1 compatibility.
* Tweak - Force `From()` and `Select()` to convert passed non-strings to an empty string.
## [1.0.6] 2023-09-05
* Tweak - Fix array shape for errors in `DatabaseQueryException`
## [1.0.5] 2023-09-05
* Tweak - Updating docblock for `whereExists()` and `whereNotExists()` in response to a PHPStan flag.
## [1.0.4] 2023-06-06
* Tweak - Added more documentation for methods provided by DB.
* Tweak - Adjusted docblocks to better declare types.
## [1.0.3] 2022-11-22
* Tweak - Set composer.json `config.platform.php` to `7.0`.
## [1.0.2] 2022-11-22
* Fix - Adjust `DB::insert()`, `DB::delete()`, `DB::update()`, and `DB::replace()` signature to match `wpdb`'s supported method signatures.
* Fix - Adjust `DB::get_var()`, `DB::get_col()`, and `DB::get_results()` signature of first arg to match `wpdb`'s signature.
## [1.0.1] 2022-09-29
* Tweak - Added a `Config` class to handle overrides of the `DatabaseQueryException` and addition of a hook prefix.
* Tweak - Added tests for `Config`
* Docs - More documentation
## [1.0.0] 2022-08-17
* Feature - Initial version
* Docs - Documentation
* Tweak - Automated tests
[1.0.0]: https://github.com/stellarwp/schema/releases/tag/1.0.0
[1.0.1]: https://github.com/stellarwp/schema/releases/tag/1.0.1
[1.0.2]: https://github.com/stellarwp/schema/releases/tag/1.0.2
[1.0.3]: https://github.com/stellarwp/schema/releases/tag/1.0.3

View File

@@ -0,0 +1,202 @@
# StellarWP DB - Claude Instructions
## Project Overview
StellarWP DB is a WordPress database wrapper and query builder library that provides a fluent interface for constructing and executing database queries. It's built on top of WordPress's `$wpdb` global and adds a modern, chainable API with proper error handling.
**Key Features:**
- Fluent query builder interface
- SQL injection protection via WordPress's prepared statements
- Support for complex queries (joins, unions, subqueries, etc.)
- Meta table abstractions for WordPress-style data
- Error handling with exceptions
- Full compatibility with `$wpdb` methods
## Architecture
### Core Classes
1. **DB** (`src/DB/DB.php`)
- Static facade/decorator for `$wpdb`
- Entry point for creating query builders via `DB::table()`
- Provides static methods that wrap `$wpdb` methods with error checking
- Throws `DatabaseQueryException` on SQL errors
2. **QueryBuilder** (`src/DB/QueryBuilder/QueryBuilder.php`)
- Main query building class
- Uses traits to organize functionality:
- `Aggregate` - COUNT, SUM, AVG, MIN, MAX operations
- `CRUD` - INSERT, UPDATE, DELETE, UPSERT operations
- `FromClause` - FROM table handling
- `WhereClause` - WHERE conditions
- `JoinClause` - JOIN operations
- `SelectStatement` - SELECT column handling
- `MetaQuery` - WordPress meta table helpers
- And more...
3. **Config** (`src/DB/Config.php`)
- Configuration management
- Allows setting custom exception classes
- Hook prefix configuration for WordPress integration
### Directory Structure
```
/home/matt/git/db/
├── src/DB/ # Main source code
│ ├── Config.php # Configuration class
│ ├── DB.php # Main DB facade
│ ├── Database/ # Database-specific classes
│ │ ├── Actions/ # Database actions (e.g., EnableBigSqlSelects)
│ │ ├── Exceptions/ # Custom exceptions
│ │ └── Provider.php # Service provider
│ └── QueryBuilder/ # Query builder components
│ ├── Clauses/ # SQL clause representations
│ ├── Concerns/ # Traits for QueryBuilder
│ ├── Types/ # Type definitions (JoinType, Operator, etc.)
│ ├── QueryBuilder.php # Main query builder
│ ├── JoinQueryBuilder.php
│ └── WhereQueryBuilder.php
├── tests/ # Test suite
│ ├── wpunit/ # WordPress unit tests
│ └── _support/ # Test support classes
├── .github/workflows/ # GitHub Actions CI/CD
├── composer.json # PHP dependencies
├── phpstan.neon.dist # PHPStan configuration
└── README.md # Documentation
```
## Development Commands
### Composer Scripts
```bash
# Run static analysis with PHPStan
composer test:analysis
```
### Testing
The project uses Codeception with WordPress testing framework (wp-browser) and a tool called "slic" for managing the test environment:
```bash
# Run tests using slic (see .github/workflows/tests-php.yml)
${SLIC_BIN} run wpunit --ext DotReporter
```
### Static Analysis
PHPStan is configured at level 5 with WordPress-specific rules:
- Configuration: `phpstan.neon.dist`
- Includes WordPress stubs via `szepeviktor/phpstan-wordpress`
- Analyzes only the `src/` directory
## Code Conventions
### Namespacing
- Root namespace: `StellarWP\DB`
- Follow PSR-4 autoloading standard
- Tests use `StellarWP\DB\Tests` namespace
### WordPress Integration
- All table names are automatically prefixed with `$wpdb->prefix`
- Use `DB::raw()` to bypass automatic prefixing
- Methods match `$wpdb` signatures where applicable
### Error Handling
- SQL errors throw `DatabaseQueryException` by default
- Custom exception classes can be configured via `Config::setDatabaseQueryException()`
- All database operations should be wrapped in try-catch blocks in production code
### Query Building Patterns
```php
// Basic query
DB::table('posts')
->select('ID', 'post_title')
->where('post_status', 'publish')
->orderBy('post_date', 'DESC')
->limit(10)
->getAll();
// Complex query with joins and meta
DB::table('posts', 'p')
->select(['p.ID', 'id'], ['p.post_title', 'title'])
->attachMeta('postmeta', 'p.ID', 'post_id',
['_thumbnail_id', 'thumbnailId'],
['_custom_field', 'customValue']
)
->leftJoin('term_relationships', 'p.ID', 'tr.object_id', 'tr')
->where('p.post_type', 'post')
->where('p.post_status', 'publish')
->getAll();
```
## Important Notes
### For Strauss Integration
The library is designed to be included via Strauss for namespace isolation in WordPress plugins. The README examples assume a namespace prefix like `Boom\Shakalaka\`.
### WordPress Compatibility
- Requires WordPress with `$wpdb` global
- Compatible with WordPress coding standards
- Follows WordPress database structure conventions (posts, postmeta, etc.)
### Performance Considerations
- Queries are not executed until terminal methods are called (`get()`, `getAll()`, `count()`, etc.)
- Use `attachMeta()` for efficient meta queries instead of multiple joins
- The library generates SQL using WordPress's `prepare()` method for security
### Recent Updates (v1.0.8)
- Added `DB::generate_results()` and `DB::generate_col()` for handling large result sets with bounded queries
- PHP 8.1 compatibility improvements
- Better type safety in docblocks
## CI/CD
### GitHub Actions Workflows
1. **Tests** (`.github/workflows/tests-php.yml`)
- Runs on every push
- Uses `stellarwp/slic` for test environment setup
- Executes wpunit test suite
2. **Static Analysis** (`.github/workflows/static-analysis.yml`)
- Runs on every push
- PHP 8.0 environment
- Executes PHPStan via `composer test:analysis`
## Common Tasks
### Adding New Query Features
1. Create a new trait in `src/DB/QueryBuilder/Concerns/`
2. Add the trait to the `QueryBuilder` class
3. Implement the SQL generation method (e.g., `getYourFeatureSQL()`)
4. Add it to the `getSQL()` method if needed
5. Write tests in `tests/wpunit/QueryBuilder/`
### Debugging Queries
```php
// Get the generated SQL without executing
$sql = DB::table('posts')
->where('post_status', 'publish')
->getSQL();
// Use wpdb's last_query after execution
$results = DB::table('posts')->getAll();
$lastQuery = $wpdb->last_query;
```
### Meta Table Patterns
The library provides special handling for WordPress meta tables:
- `attachMeta()` - Efficiently join and select meta values
- `configureMetaTable()` - Customize meta table column names
- Automatic LEFT JOIN generation for each meta key
- Support for multiple meta values with the same key
## Resources
- [WordPress $wpdb Documentation](https://developer.wordpress.org/reference/classes/wpdb/)
- [Codeception Documentation](https://codeception.com/)
- [PHPStan Documentation](https://phpstan.org/)
- [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/)

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB;
use KadenceWP\KadenceBlocks\StellarWP\DB\Database\Exceptions\DatabaseQueryException;
class Config {
/**
* @var string
*/
protected static $databaseQueryException = DatabaseQueryException::class;
/**
* @var string
*/
protected static $hookPrefix = '';
/**
* Gets the DatabaseQueryException class.
*
* @return string
*/
public static function getDatabaseQueryException(): string {
return static::$databaseQueryException;
}
/**
* Gets the hook prefix.
*
* @return string
*/
public static function getHookPrefix(): string {
return static::$hookPrefix;
}
/**
* Resets this class back to the defaults.
*/
public static function reset() {
static::$hookPrefix = '';
static::$databaseQueryException = DatabaseQueryException::class;
}
/**
* Sets the DatabaseQueryException class.
*
* @param string $class Class name of the DatabaseQueryException to use.
*
* @return void
*/
public static function setDatabaseQueryException( string $class ) {
if ( ! is_a( $class, DatabaseQueryException::class, true ) ) {
throw new \InvalidArgumentException( 'The provided DatabaseQueryException class must be or must extend ' . DatabaseQueryException::class . '.' );
}
static::$databaseQueryException = $class;
}
/**
* Sets the hook prefix.
*
* @param string $prefix The prefix to add to hooks.
*
* @return void
*/
public static function setHookPrefix( string $prefix ) {
static::$hookPrefix = $prefix;
}
}

View File

@@ -0,0 +1,485 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB;
use Closure;
use Exception;
use Generator;
use KadenceWP\KadenceBlocks\StellarWP\DB\Database\Exceptions\DatabaseQueryException;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\RawSQL;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\QueryBuilder;
use WP_Error;
/**
* Class DB
*
* A static decorator for the $wpdb class and decorator function which does SQL error checking when performing queries.
* If a SQL error occurs a DatabaseQueryException is thrown.
*
* @method static int|bool query( string $query )
* @method static int|false insert( string $table, array $data, array|string|null $format = null )
* @method static int|false delete( string $table, array $where, array|string|null $where_format = null )
* @method static int|false update( string $table, array $data, array $where, array|string|null $format = null, array|string|null $where_format = null )
* @method static int|false replace( string $table, array $data, array|string|null $format = null )
* @method static null|string get_var( string|null $query = null, int $x = 0, int $y = 0 )
* @method static array|object|null|void get_row( string|null $query = null, string $output = OBJECT, int $y = 0 )
* @method static array get_col( string|null $query = null, int $x = 0 )
* @method static array|object|null get_results( string|null $query = null, string $output = OBJECT )
* @method static string get_charset_collate()
* @method static string esc_like( string $text )
* @method static string remove_placeholder_escape( string $text )
* @method static Config config()
*/
class DB {
/**
* Is this library initialized?
*
* @var bool
*/
private static $initialized = false;
/**
* The Database\Provider instance.
*
* @var Database\Provider
*/
private static $provider;
/**
* Current transaction nesting depth.
*
* @since 1.3.0
*
* @var int
*/
private static $transactionDepth = 0;
/**
* Initializes the service provider.
*
* @since 1.0.0
*/
public static function init(): void {
if ( ! self::$initialized ) {
return;
}
self::$provider = new Database\Provider();
self::$provider->register();
self::$initialized = true;
}
/**
* Returns the current transaction nesting depth.
*
* @since 1.3.0
*
* @return int
*/
public static function transactionLevel() {
return self::$transactionDepth;
}
/**
* Runs the dbDelta function and returns a WP_Error with any errors that occurred during the process
*
* @since 1.0.0
*
* @param $delta
*
* @return array
* @throws DatabaseQueryException
* @see dbDelta() for parameter and return details
*
*/
public static function delta( $delta ) {
return self::runQueryWithErrorChecking(
function () use ( $delta ) {
return dbDelta( $delta );
}
);
}
/**
* A convenience method for the $wpdb->prepare method
*
* @since 1.0.0
*
* @param string $query
* @param mixed ...$args
*
* @return false|mixed
* @see WPDB::prepare() for usage details
*
*/
public static function prepare( $query, ...$args ) {
global $wpdb;
return $wpdb->prepare( $query, ...$args );
}
/**
* Magic method which calls the static method on the $wpdb while performing error checking
*
* @since 1.0.0 add givewp_db_pre_query action
* @since 1.0.0
*
* @param $name
* @param $arguments
*
* @return mixed
* @throws DatabaseQueryException
*/
public static function __callStatic( $name, $arguments ) {
return self::runQueryWithErrorChecking(
static function () use ( $name, $arguments ) {
global $wpdb;
if ( in_array( $name, [ 'get_row', 'get_col', 'get_results', 'query' ], true ) ) {
$hook_prefix = Config::getHookPrefix();
/**
* Allow for hooking just before query execution.
*
* @since 1.0.0
*
* @param string $argument First argument passed to the $wpdb method.
* @param string $hook_prefix Prefix for the hook.
*/
do_action( 'stellarwp_db_pre_query', current( $arguments ), $hook_prefix );
if ( $hook_prefix ) {
/**
* Allow for hooking just before query execution.
*
* @since 1.0.0
*
* @param string $argument First argument passed to the $wpdb method.
* @param string $hook_prefix Prefix for the hook.
*/
do_action( "{$hook_prefix}_stellarwp_db_pre_query", current( $arguments ), $hook_prefix );
}
}
return call_user_func_array( [ $wpdb, $name ], $arguments );
}
);
}
/**
* Get last insert ID
*
* @since 1.0.0
* @return int
*/
public static function last_insert_id() {
global $wpdb;
return $wpdb->insert_id;
}
/**
* Prefix given table name with $wpdb->prefix
*
* @param string $tableName
*
* @return string
*/
public static function prefix( $tableName ) {
global $wpdb;
return $wpdb->prefix . $tableName;
}
/**
* Create QueryBuilder instance
*
* @param string|RawSQL $table
* @param string|null $alias
*
* @return QueryBuilder
*/
public static function table( $table, $alias = '' ) {
$builder = new QueryBuilder();
$builder->from( $table, $alias );
return $builder;
}
/**
* Runs a transaction. If the callable works then the transaction is committed. If the callable throws an exception
* then the transaction is rolled back.
*
* @since 1.0.0
*
* @param callable $callback
*
* @return void
* @throws Exception
*/
public static function transaction( callable $callback ) {
self::beginTransaction();
try {
$callback();
} catch ( Exception $e ) {
self::rollback();
throw $e;
}
self::commit();
}
/**
* Manually starts a transaction.
*
* Nested calls create savepoints instead of starting a new transaction,
* preventing MySQL's implicit commit behavior.
*
* @since 1.0.0
* @since 1.3.0 Added savepoint support for nested transactions.
*
* @return void
*/
public static function beginTransaction() {
global $wpdb;
if ( self::$transactionDepth === 0 ) {
$wpdb->query( 'START TRANSACTION' );
} else {
$wpdb->query( 'SAVEPOINT sp_' . self::$transactionDepth );
}
self::$transactionDepth++;
}
/**
* Manually rolls back a transaction.
*
* When nested, rolls back to the current savepoint. When at the outermost
* level, issues a real ROLLBACK.
*
* @since 1.0.0
* @since 1.3.0 Added savepoint support for nested transactions.
*
* @return void
*/
public static function rollback() {
global $wpdb;
self::$transactionDepth = max( 0, self::$transactionDepth - 1 );
if ( self::$transactionDepth === 0 ) {
$wpdb->query( 'ROLLBACK' );
} else {
$wpdb->query( 'ROLLBACK TO SAVEPOINT sp_' . self::$transactionDepth );
}
}
/**
* Manually commits a transaction.
*
* When nested, releases the current savepoint. When at the outermost
* level, issues a real COMMIT.
*
* @since 1.0.0
* @since 1.3.0 Added savepoint support for nested transactions.
*
* @return void
*/
public static function commit() {
global $wpdb;
self::$transactionDepth = max( 0, self::$transactionDepth - 1 );
if ( self::$transactionDepth === 0 ) {
$wpdb->query( 'COMMIT' );
} else {
$wpdb->query( 'RELEASE SAVEPOINT sp_' . self::$transactionDepth );
}
}
/**
* Used as a flag to tell QueryBuilder not to process the provided SQL
* If $args are provided, we will assume that dev wants to use DB::prepare method with raw SQL
*
* @param string $sql
* @param array ...$args
*
* @return RawSQL
*/
public static function raw( $sql, ...$args ) {
return new RawSQL( $sql, ...$args );
}
/**
* Runs a query callable and checks to see if any unique SQL errors occurred when it was run
*
* @since 1.0.0
*
* @param Callable $queryCaller
*
* @return mixed
* @throws DatabaseQueryException
*/
private static function runQueryWithErrorChecking( $queryCaller ) {
global $wpdb, $EZSQL_ERROR;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$errorCount = is_array( $EZSQL_ERROR ) ? count( $EZSQL_ERROR ) : 0;
$hasShowErrors = $wpdb->hide_errors();
$output = $queryCaller();
if ( $hasShowErrors ) {
$wpdb->show_errors();
}
$wpError = self::getQueryErrors( $errorCount );
if ( ! empty( $wpError->errors ) ) {
/** @var DatabaseQueryException */
$exception_class = Config::getDatabaseQueryException();
throw new $exception_class( $wpdb->last_query, $wpError->errors );
}
return $output;
}
/**
* Retrieves the SQL errors stored by WordPress
*
* @since 1.0.0
*
* @param int $initialCount
*
* @return WP_Error
*/
private static function getQueryErrors( $initialCount = 0 ) {
global $EZSQL_ERROR;
$wpError = new WP_Error();
if ( is_array( $EZSQL_ERROR ) ) {
for ( $index = $initialCount, $indexMax = count( $EZSQL_ERROR ); $index < $indexMax; $index ++ ) {
$error = $EZSQL_ERROR[ $index ];
if ( empty( $error['error_str'] ) || empty( $error['query'] ) || 0 === strpos(
$error['query'],
'DESCRIBE '
) ) {
continue;
}
$wpError->add( 'db_delta_error', $error['error_str'] );
}
}
return $wpError;
}
/**
* Get all the results from a query in batches.
*
* @since TBD
*
* @param string $query The SQL query.
* @param int $batch_size The number of results to return in each batch.
* @param Closure $fetch The function to fetch the results.
*
* @return Generator<mixed> A generator to get all the results of the query.
*/
private static function run_batched_query( string $query, int $batch_size, Closure $fetch ): Generator {
// Capture the end part of the main query, the one not bound with parentheses.
preg_match( '/([^)(]*?)$/', $query, $matches );
// Does the end part of the query contain a LIMIT? Look for `LIMIT <offset>, <limit>` or `LIMIT <limit>`.
$has_limit = (bool) preg_match( '/LIMIT\\s+\\d+\\s*(,\\s*\\d+)*$/i', $matches[1] );
if ( $has_limit ) {
yield from $fetch( $query );
return;
}
// Add a LIMIT template to the query.
$query_template = trim( $query . ' LIMIT %d, %d' );
$offset = 0;
$found_rows = 0;
$fetched = 0;
$key = 0;
// Set up the first query, make sure ot include SQL_CALC_FOUND_ROWS.
$first_query = self::prepare(
$query_template,
$offset,
$batch_size
);
// Build the first query to include SQL_CALC_FOUND_ROWS if not already present.
$first_query = preg_replace( '/^SELECT\\s*(?!SQL_CALC_FOUND_ROWS)/i', 'SELECT SQL_CALC_FOUND_ROWS ', $first_query );
$run_query = $first_query;
do {
foreach ( $fetch( $run_query ) as $result ) {
$fetched ++;
yield $key => $result;
$key ++;
}
if ( $offset === 0 ) {
// First run.
$found_rows = (int) self::get_var( 'SELECT FOUND_ROWS()' );
}
$offset += $batch_size;
// Compile the query for the next run.
$run_query = self::prepare(
$query_template,
$offset,
$batch_size
);
} while ( $fetched < $found_rows );
}
/**
* Get all the results from a query in batches.
*
* This method should be used to run an unbounded query in batches.
*
* @since TBD
*
* @param string|null $query The SQL query.
* @param string $output The required return type. One of OBJECT, ARRAY_A, ARRAY_N.
* @param int $batch_size The number of results to return in each batch.
*
* @return Generator<array|object|null> A generator to get all the results of the query.
*/
public static function generate_results( string $query = null, string $output = OBJECT, int $batch_size = 20 ): Generator {
yield from self::run_batched_query( $query, $batch_size, static function ( string $run_query ) use ( $output ) {
return self::get_results( $run_query, $output );
} );
}
/**
* Get all the values in a column from a query in batches.
*
* @since TBD
*
* @param string|null $query The SQL query.
* @param int $x The column number to return.
* @param int $batch_size The number of results to return in each batch.
*
* @return Generator<mixed> The values of the column.
*/
public function generate_col( string $query = null, int $x = 0, int $batch_size = 50 ): Generator {
yield from self::run_batched_query( $query, $batch_size, static function ( string $run_query ) use ( $x ) {
return self::get_col( $run_query, $x );
} );
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\StellarWP\DB\Database\Actions;
class EnableBigSqlSelects {
/**
* @since 1.0.0
*
* Enables mysql big selects for the session using a session system variable.
*
* This is necessary for hosts that have an arbitrary MAX_JOIN_SIZE limit, which prevents more complex queries from
* running properly. Setting SQL_BIG_SELECTS ignores this limit. This is also done by WooCommerce, supporting the
* idea that this is a viable option. There also doesn't seem to be a way for hosts to prevent this.
*
* @see https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_big_selects
* @see https://dev.mysql.com/doc/refman/5.7/en/system-variable-privileges.html
*
*/
public function set_var() {
static $bigSelects = false;
if ( ! $bigSelects ) {
global $wpdb;
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1;' );
$bigSelects = true;
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\Database\Exceptions;
use Throwable;
/**
* Class DatabaseQueryException
*
* An exception for when errors occurred within the database while performing a query, which stores the SQL errors the
* database returned
*
* @since 1.0.0
*/
class DatabaseQueryException extends \Exception {
/**
* @var array<string, string[]>
*/
private $queryErrors;
/**
* @var string
*/
private $query;
/**
* @since 1.0.0
*
* @param array<string, string[]> $queryErrors
*/
public function __construct(
string $query,
array $queryErrors,
string $message = 'Database Query',
$code = 0,
Throwable $previous = null
) {
$this->query = $query;
$this->queryErrors = $queryErrors;
parent::__construct( $message, $code, $previous );
}
/**
* Returns the query errors
*
* @since 1.0.0
*
* @return array<string, string[]>
*/
public function getQueryErrors(): array {
return $this->queryErrors;
}
public function getQuery(): string {
return $this->query;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\Database;
class Provider {
/**
* @var Actions\EnableBigSqlSelects
*/
public $action_enable_big_sql_selects;
/**
* Binds and sets up implementations.
*
* @since 1.0.0
*/
public function register() {
add_action( 'stellarwp_db_pre_query', [ $this, 'enable_big_sql_selects' ] );
}
/**
* Fires the EnableBigSqlSelects action.
*/
public function enable_big_sql_selects() {
if ( $this->action_enable_big_sql_selects === null ) {
$this->action_enable_big_sql_selects = new Actions\EnableBigSqlSelects();
}
$this->action_enable_big_sql_selects->set_var();
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\QueryBuilder;
/**
* @since 1.0.0
*/
class From {
/**
* @var string|RawSQL
*/
public $table;
/**
* @var string
*/
public $alias;
/**
* @param string|RawSQL $table
* @param string|null $alias
*/
public function __construct( $table, $alias = '' ) {
$this->table = QueryBuilder::prefixTable( $table );
$this->alias = is_scalar( $alias ) ? trim( (string) $alias ) : '';
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types\Math;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types\Operator;
use InvalidArgumentException;
/**
* @since 1.0.0
*/
class Having {
/**
* @var string
*/
public $column;
/**
* @var string
*/
public $comparisonOperator;
/**
* @var string|int
*/
public $value;
/**
* @var string
*/
public $logicalOperator;
/**
* @var string|null
*/
public $mathFunction;
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
* @param string|null $logicalOperator
* @param string $mathFunction
*/
public function __construct( $column, $comparisonOperator, $value, $logicalOperator, $mathFunction = null ) {
$this->column = trim( $column );
$this->comparisonOperator = $this->getComparisonOperator( $comparisonOperator );
$this->value = $value;
$this->logicalOperator = $logicalOperator ? $this->getLogicalOperator( $logicalOperator ) : '';
$this->mathFunction = $this->getMathFunction( $mathFunction );
}
/**
* @param string $logicalOperator
*
* @return string
*/
private function getLogicalOperator( $logicalOperator ) {
$operators = [
Operator::_AND,
Operator::_OR
];
$logicalOperator = strtoupper( $logicalOperator );
if ( ! in_array( $logicalOperator, $operators, true ) ) {
throw new InvalidArgumentException(
sprintf(
'Unsupported logical operator %s. Please use one of the supported operators (%s)',
$logicalOperator,
implode( ',', $operators )
)
);
}
return $logicalOperator;
}
/**
* @param string $comparisonOperator
*
* @return string
*/
private function getComparisonOperator( $comparisonOperator ) {
$operators = [
'<',
'<=',
'>',
'>=',
'<>',
'!=',
'='
];
if ( ! in_array( $comparisonOperator, $operators, true ) ) {
throw new InvalidArgumentException(
sprintf(
'Unsupported comparison operator %s. Please use one of the supported operators (%s)',
$comparisonOperator,
implode( ',', $operators )
)
);
}
return $comparisonOperator;
}
/**
* @param string $mathFunction
*
* @return string|null
*/
private function getMathFunction( $mathFunction ) {
if ( array_key_exists( $mathFunction, Math::getTypes() ) ) {
return $mathFunction;
}
return null;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\QueryBuilder;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types\JoinType;
use InvalidArgumentException;
/**
* @since 1.0.0
*/
class Join {
/**
* @var string
*/
public $table;
/**
* @var string
*/
public $joinType;
/**
* @var string|null
*/
public $alias;
/**
* @param string $table
* @param string $joinType \StellarWP\DB\QueryBuilder\Types\JoinType
* @param string|null $alias
*/
public function __construct( $joinType, $table, $alias = '' ) {
$this->table = QueryBuilder::prefixTable( $table );
$this->joinType = $this->getJoinType( $joinType );
$this->alias = is_scalar( $alias ) ? trim( (string) $alias ) : '';
}
/**
* @param string $type
*
* @return string
*/
private function getJoinType( $type ) {
$type = strtoupper( $type );
if ( array_key_exists( $type, JoinType::getTypes() ) ) {
return $type;
}
throw new InvalidArgumentException(
sprintf(
'Join type %s is not supported. Please provide one of the supported join types (%s)',
$type,
implode( ',', JoinType::getTypes() )
)
);
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types\Operator;
use InvalidArgumentException;
/**
* @since 1.0.0
*/
class JoinCondition {
/**
* @var string
*/
public $logicalOperator;
/**
* @var string
*/
public $column1;
/**
* @var mixed
*/
public $column2;
/**
* @var bool
*/
public $quote;
/**
* @param string $logicalOperator
* @param string $column1
* @param string $column2
* @param bool $quote
*/
public function __construct( $logicalOperator, $column1, $column2, $quote = false ) {
$this->logicalOperator = $this->getLogicalOperator( $logicalOperator );
$this->column1 = trim( $column1 );
$this->column2 = trim( $column2 );
$this->quote = $quote;
}
/**
* @param string $operator
*
* @return string
*/
private function getLogicalOperator( $operator ) {
$operator = strtoupper( $operator );
$supportedOperators = [
Operator::ON,
Operator::_AND,
Operator::_OR
];
if ( ! in_array( $operator, $supportedOperators, true ) ) {
throw new InvalidArgumentException(
sprintf(
'Unsupported logical operator %s. Please provide one of the supported operators (%s)',
$operator,
implode( ',', $supportedOperators )
)
);
}
return $operator;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\QueryBuilder;
/**
* @since 1.0.0
*/
class MetaTable {
/**
* @var string
*/
public $tableName;
/**
* @var string
*/
public $keyColumnName;
/**
* @var string
*/
public $valueColumnName;
/**
* @param string $table
* @param string $metaKeyColumnName
* @param string $metaValueColumnName
*/
public function __construct( $table, $metaKeyColumnName, $metaValueColumnName ) {
$this->tableName = QueryBuilder::prefixTable( $table );
$this->keyColumnName = trim( $metaKeyColumnName );
$this->valueColumnName = trim( $metaValueColumnName );
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses;
use InvalidArgumentException;
/**
* @since 1.0.0
*/
class OrderBy {
/**
* @var string
*/
public $column;
/**
* @var string
*/
public $direction;
/**
* @param $column
* @param $direction
*/
public function __construct( $column, $direction ) {
$this->column = trim( $column );
$this->direction = $this->getSortDirection( $direction );
}
/**
* @param string $direction
*
* @return string
*/
private function getSortDirection( $direction ) {
$direction = strtoupper( $direction );
$directions = ['ASC', 'DESC'];
if ( ! in_array( $direction, $directions, true ) ) {
throw new InvalidArgumentException(
sprintf(
'Unsupported sort direction %s. Please use one of the (%s)',
$direction,
implode( ',', $directions )
)
);
}
return $direction;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses;
use KadenceWP\KadenceBlocks\StellarWP\DB\DB;
/**
* @since 1.0.0
*/
class RawSQL {
/**
* @var string
*/
public $sql;
/**
* @param string $sql
* @param array<int,mixed>|string|null $args
*/
public function __construct( $sql, $args = null ) {
$this->sql = $args ? DB::prepare( $sql, $args ) : $sql;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses;
/**
* @since 1.0.0
*/
class Select {
/**
* @var string
*/
public $column;
/**
* @var string
*/
public $alias;
/**
* @param string $column
* @param string|null $alias
*/
public function __construct( $column, $alias = '' ) {
$this->column = trim( $column );
$this->alias = is_scalar( $alias ) ? trim( (string) $alias ) : '';
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\QueryBuilder;
/**
* @since 1.0.0
*/
class Union {
/**
* @var QueryBuilder
*/
public $builder;
/**
* @var bool
*/
public $all = false;
/**
* @param QueryBuilder $builder
* @param bool $all
*/
public function __construct( $builder, $all = false ) {
$this->builder = $builder;
$this->all = $all;
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types\Operator;
use InvalidArgumentException;
/**
* @since 1.0.0
*/
class Where {
/**
* @var string
*/
public $column;
/**
* @var mixed
*/
public $value;
/**
* @var string
*/
public $comparisonOperator;
/**
* @var string
*/
public $logicalOperator;
/**
* @param string $column
* @param mixed $value
* @param string $comparisonOperator
* @param string|null $logicalOperator
*/
public function __construct( $column, $value, $comparisonOperator, $logicalOperator ) {
$this->column = trim( $column );
$this->value = $value;
$this->comparisonOperator = $this->getComparisonOperator( $comparisonOperator );
$this->logicalOperator = $logicalOperator ? $this->getLogicalOperator( $logicalOperator ) : '';
}
/**
* @param string $comparisonOperator
*
* @return string
*/
private function getComparisonOperator( $comparisonOperator ) {
$operators = [
'<',
'<=',
'>',
'>=',
'<>',
'!=',
'=',
Operator::LIKE,
Operator::NOTLIKE,
Operator::IN,
Operator::NOTIN,
Operator::BETWEEN,
Operator::NOTBETWEEN,
Operator::ISNULL,
Operator::NOTNULL
];
if ( ! in_array( $comparisonOperator, $operators, true ) ) {
throw new InvalidArgumentException(
sprintf(
'Unsupported comparison operator %s. Please use one of the supported operators (%s)',
$comparisonOperator,
implode( ',', $operators )
)
);
}
return $comparisonOperator;
}
/**
* @param string $logicalOperator
*
* @return string
*/
private function getLogicalOperator( $logicalOperator ) {
$operators = [
Operator::_AND,
Operator::_OR
];
$logicalOperator = strtoupper( $logicalOperator );
if ( ! in_array( $logicalOperator, $operators, true ) ) {
throw new InvalidArgumentException(
sprintf(
'Unsupported logical operator %s. Please use one of the supported operators (%s)',
$logicalOperator,
implode( ',', $operators )
)
);
}
return $logicalOperator;
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\RawSQL;
/**
* @since 1.0.0
*/
trait Aggregate {
/**
* Returns the number of rows returned by a query
*
* @since 1.0.0
* @param null|string $column
*
* @return int
*/
public function count( $column = null ) {
$column = ( ! $column || $column === '*' ) ? '1' : trim( $column );
$this->selects[] = new RawSQL( 'SELECT COUNT(%1s) AS count', $column );
$result = $this->get();
if ( is_array( $result ) ) {
return +$result['count'];
}
return +$result->count;
}
/**
* Returns the total sum in a set of values
*
* @since 1.0.0
* @param string $column
*
* @return int|float
*/
public function sum( $column ) {
$this->selects[] = new RawSQL( 'SELECT SUM(%1s) AS sum', $column );
$result = $this->get();
if ( is_array( $result ) ) {
return +$result['sum'];
}
return +$result->sum;
}
/**
* Get the average value in a set of values
*
* @since 1.0.0
* @param string $column
*
* @return int|float
*/
public function avg( $column ) {
$this->selects[] = new RawSQL( 'SELECT AVG(%1s) AS avg', $column );
$result = $this->get();
if ( is_array( $result ) ) {
return +$result['avg'];
}
return +$result->avg;
}
/**
* Returns the minimum value in a set of values
*
* @since 1.0.0
* @param string $column
*
* @return int|float
*/
public function min( $column ) {
$this->selects[] = new RawSQL( 'SELECT MIN(%1s) AS min', $column );
$result = $this->get();
if ( is_array( $result ) ) {
return +$result['min'];
}
return +$result->min;
}
/**
* Returns the maximum value in a set of values
*
* @since 1.0.0
* @param string $column
*
* @return int|float
*/
public function max( $column ) {
$this->selects[] = new RawSQL( 'SELECT MAX(%1s) AS max', $column );
$result = $this->get();
if ( is_array( $result ) ) {
return +$result['max'];
}
return +$result->max;
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use KadenceWP\KadenceBlocks\StellarWP\DB\DB;
/**
* @since 1.0.0
*/
trait CRUD {
/**
* @see https://developer.wordpress.org/reference/classes/wpdb/insert/
*
* @since 1.0.0
*
* @param array|string $format
*
* @param array $data
* @return false|int
*
*/
public function insert( $data, $format = null ) {
return DB::insert(
$this->getTable(),
$data,
$format
);
}
/**
* @see https://developer.wordpress.org/reference/classes/wpdb/update/
*
* @since 1.0.0
*
* @param array $data
* @param array|string|null $format
*
* @return false|int
*
*/
public function update( $data, $format = null ) {
return DB::update(
$this->getTable(),
$data,
$this->getWhere(),
$format,
null
);
}
/**
* Upsert allows for inserting or updating a row depending on whether it already exists.
*
* @since 1.0.8
*
* @param array<string, string|int|float|bool|null> $data The data to insert or update.
* @param array $match The columns to match on.
* @param string|array|null $format Array of formats to be mapped to each value in $data. If string, the format will be used for all values in $data.
*
* @return false|int Number of rows updated/inserted, false on error.
*/
public function upsert( $data, $match = [], $format = null ) {
// Build the where clause(s).
foreach ( $match as $column ) {
$this->where( $column, $data[ $column ] );
}
// If the row exists, update it.
if ( $this->get() ) {
return $this->update( $data, $format );
}
// Otherwise, insert it.
return $this->insert( $data, $format );
}
/**
* Delete rows from the database.
*
* Unlike WordPress's $wpdb->delete() method, this implementation generates and executes
* a DELETE SQL statement directly, which allows for advanced features like ORDER BY and LIMIT.
*
* Supports:
* - WHERE clauses (including whereLike, whereIn, whereBetween, etc.)
* - ORDER BY for controlling which rows are deleted first
* - LIMIT to restrict the number of rows deleted
* - Complex WHERE conditions (AND, OR, nested queries)
*
* Usage examples:
* ```php
* // Simple delete with WHERE
* DB::table('posts')->where('post_status', 'draft')->delete();
*
* // Delete with LIMIT (delete only 10 rows)
* DB::table('posts')->where('post_type', 'temp')->limit(10)->delete();
*
* // Delete oldest posts first using ORDER BY and LIMIT
* DB::table('posts')
* ->where('post_status', 'trash')
* ->orderBy('post_date', 'ASC')
* ->limit(100)
* ->delete();
*
* // Delete with LIKE pattern
* DB::table('posts')->whereLike('post_title', 'Draft:%')->delete();
*
* // Delete with multiple conditions
* DB::table('posts')
* ->where('post_type', 'page')
* ->where('post_status', 'auto-draft')
* ->whereBetween('ID', 1, 1000)
* ->delete();
* ```
*
* Restrictions:
* - Table aliases in the FROM clause may not be supported on older database versions
* (MySQL < 8.0.24, MariaDB < 11.6). Avoid using table aliases with delete().
* - JOINs are not supported in DELETE statements with this implementation
*
* @since 1.0.0
*
* @return false|int Number of rows deleted, or false on error.
*
* @see QueryBuilder::deleteSQL() for the SQL generation logic
*/
public function delete() {
return DB::query( $this->deleteSQL() );
}
/**
* Get results
*
* @since 1.0.0
*
* @param string $output ARRAY_A|ARRAY_N|OBJECT|OBJECT_K
*
* @return array|object|null
*/
public function getAll( $output = OBJECT ) {
return DB::get_results( $this->getSQL(), $output );
}
/**
* Get row
*
* @since 1.0.0
*
* @param string $output ARRAY_A|ARRAY_N|OBJECT|OBJECT_K
*
* @return array|object|null
*/
public function get( $output = OBJECT ) {
return DB::get_row( $this->getSQL(), $output );
}
/**
* @since 1.0.0
*
* @return string
*/
private function getTable() {
return $this->froms[0]->table;
}
/**
* @since 1.0.0
*
* @return array[]
*/
private function getWhere() {
$wheres = [];
foreach ( $this->wheres as $where ) {
$wheres[ $where->column ] = $where->value;
}
return $wheres;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use KadenceWP\KadenceBlocks\StellarWP\DB\DB;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\From;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\RawSQL;
/**
* @since 1.0.0
*/
trait FromClause {
/**
* @var From[]
*/
protected $froms = [];
/**
* @param string|RawSQL $table
* @param string|null $alias
*
* @return $this
*/
public function from( $table, $alias = '' ) {
$this->froms[] = new From( $table, $alias );
return $this;
}
/**
* @return array|string[]
*/
protected function getFromSQL() {
if ( empty( $this->froms ) ) {
return [];
}
return [
'FROM ' . implode(
', ',
array_map( function ( From $from ) {
if ( $from->alias ) {
return DB::prepare(
'%1s AS %2s',
$from->table,
$from->alias
);
}
return DB::prepare( '%1s', $from->table );
}, $this->froms )
)
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use KadenceWP\KadenceBlocks\StellarWP\DB\DB;
/**
* @since 1.0.0
*/
trait GroupByStatement {
/**
* @var array
*/
protected $groupByColumns = [];
/**
* @return $this
*/
public function groupBy( $tableColumn ) {
if ( ! in_array( $tableColumn, $this->groupByColumns, true ) ) {
$this->groupByColumns[] = DB::prepare( '%1s', $tableColumn );
}
return $this;
}
protected function getGroupBySQL() {
return ! empty( $this->groupByColumns )
? [ 'GROUP BY ' . implode( ',', $this->groupByColumns ) ]
: [];
}
}

View File

@@ -0,0 +1,296 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use KadenceWP\KadenceBlocks\StellarWP\DB\DB;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\Having;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\RawSQL;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types\Math;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types\Operator;
/**
* @since 1.0.0
*/
trait HavingClause {
/**
* @var Having[]|RawSQL[]
*/
protected $havings = [];
/**
* @var bool
*/
private $includeHavingKeyword = true;
/**
* @param string $column
* @param string $comparisonOperator
* @param string $value
* @param null|string $mathFunction \StellarWP\DB\QueryBuilder\Types\Math
*
* @return $this
*
*/
public function having( $column, $comparisonOperator, $value, $mathFunction = null ) {
$this->havings[] = new Having(
$column,
$comparisonOperator,
$value,
empty( $this->havings ) ? null : Operator::_AND,
$mathFunction
);
return $this;
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string $value
* @param null|string $mathFunction \StellarWP\DB\QueryBuilder\Types\Math
*
* @return $this
*/
public function orHaving( $column, $comparisonOperator, $value, $mathFunction = null ) {
$this->havings[] = new Having(
$column,
$comparisonOperator,
$value,
empty( $this->havings ) ? null : Operator::_OR,
$mathFunction
);
return $this;
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
*
* @return $this
*/
public function havingCount( $column, $comparisonOperator, $value ) {
return $this->having(
$column,
$comparisonOperator,
$value,
Math::COUNT
);
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
*
* @return $this
*/
public function orHavingCount( $column, $comparisonOperator, $value ) {
return $this->orHaving(
$column,
$comparisonOperator,
$value,
Math::COUNT
);
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
*
* @return $this
*/
public function havingMin( $column, $comparisonOperator, $value ) {
return $this->having(
$column,
$comparisonOperator,
$value,
Math::MIN
);
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
*
* @return $this
*/
public function orHavingMin( $column, $comparisonOperator, $value ) {
return $this->orHaving(
$column,
$comparisonOperator,
$value,
Math::MIN
);
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
*
* @return $this
*/
public function havingMax( $column, $comparisonOperator, $value ) {
return $this->having(
$column,
$comparisonOperator,
$value,
Math::MAX
);
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
*
* @return $this
*/
public function orHavingMax( $column, $comparisonOperator, $value ) {
return $this->orHaving(
$column,
$comparisonOperator,
$value,
Math::MAX
);
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
*
* @return $this
*/
public function havingAvg( $column, $comparisonOperator, $value ) {
return $this->having(
$column,
$comparisonOperator,
$value,
Math::AVG
);
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
*
* @return $this
*/
public function orHavingAvg( $column, $comparisonOperator, $value ) {
return $this->orHaving(
$column,
$comparisonOperator,
$value,
Math::AVG
);
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
*
* @return $this
*/
public function havingSum( $column, $comparisonOperator, $value ) {
return $this->having(
$column,
$comparisonOperator,
$value,
Math::SUM
);
}
/**
* @param string $column
* @param string $comparisonOperator
* @param string|int $value
*
* @return $this
*/
public function orHavingSum( $column, $comparisonOperator, $value ) {
return $this->orHaving(
$column,
$comparisonOperator,
$value,
Math::SUM
);
}
/**
* Add raw SQL HAVING clause
*
* @param string $sql
* @param ...$args
*
* @return $this
*/
public function havingRaw( $sql, ...$args ) {
$this->havings[] = new RawSQL( $sql, $args );
return $this;
}
/**
* @return string[]
*/
protected function getHavingSQL() {
if ( empty( $this->havings ) ) {
return [];
}
$havings = [];
foreach ( $this->havings as $i => $having ) {
if ( $having instanceof RawSQL ) {
if ( $i === 0 ) {
// If the first element is an instance of RawSQL
// then we don't need the starting HAVING keyword because we assume that the dev will include that in RawSQL
$this->includeHavingKeyword = false;
}
$havings[] = $having->sql;
continue;
}
$havings[] = $this->buildHavingSQL( $having );
}
if ( $this->includeHavingKeyword ) {
return array_merge( ['HAVING'], $havings );
}
return $havings;
}
/**
* @param Having $having
*
* @return string
*/
private function buildHavingSQL( Having $having ) {
if ( $having->mathFunction ) {
return DB::prepare(
"%1s %2s(%3s) %4s %s",
$having->logicalOperator,
$having->mathFunction,
$having->column,
$having->comparisonOperator,
$having->value
);
}
return DB::prepare(
"%1s %s %3s %s",
$having->logicalOperator,
$having->column,
$having->comparisonOperator,
$having->value
);
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use Closure;
use KadenceWP\KadenceBlocks\StellarWP\DB\DB;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\JoinQueryBuilder;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\Join;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\RawSQL;
/**
* @since 1.0.0
*/
trait JoinClause {
/**
* @var Closure[]|RawSQL[]
*/
protected $joins = [];
/**
* Method used to build advanced JOIN queries, Check README.md for more info.
* If you need to perform only simple JOINs with only one JOIN condition, then you don't need this method.
*
* @param Closure $callback The closure will receive a StellarWP\DB\QueryBuilder\JoinQueryBuilder instance
*
* @return static
*/
public function join( $callback ) {
$this->joins[] = $callback;
return $this;
}
/**
* @param string|RawSQL $table
* @param string $column1
* @param string $column2
* @param string|null $alias
*
* @return static
*/
public function leftJoin( $table, $column1, $column2, $alias = '' ) {
$this->join(
function ( JoinQueryBuilder $builder ) use ( $table, $column1, $column2, $alias ) {
$builder
->leftJoin( $table, $alias )
->on( $column1, $column2 );
}
);
return $this;
}
/**
* @param string|RawSQL $table
* @param string $column1
* @param string $column2
* @param string|null $alias
*
* @return static
*/
public function innerJoin( $table, $column1, $column2, $alias = '' ) {
$this->join(
function ( JoinQueryBuilder $builder ) use ( $table, $column1, $column2, $alias ) {
$builder
->innerJoin( $table, $alias )
->on( $column1, $column2 );
}
);
return $this;
}
/**
* @param string|RawSQL $table
* @param string $column1
* @param string $column2
* @param string|null $alias
*
* @return static
*/
public function rightJoin( $table, $column1, $column2, $alias = '' ) {
$this->join(
function ( JoinQueryBuilder $builder ) use ( $table, $column1, $column2, $alias ) {
$builder
->rightJoin( $table, $alias )
->on( $column1, $column2 );
}
);
return $this;
}
/**
* Add raw SQL JOIN clause
*
* @param string $sql
* @param ...$args
*
* @return static
*/
public function joinRaw( $sql, ...$args ) {
$this->joins[] = new RawSQL( $sql, $args );
return $this;
}
/**
* @return string[]
*/
protected function getJoinSQL() {
return array_map(function ( $callback ) {
if ( $callback instanceof RawSQL ) {
return $callback->sql;
}
$builder = new JoinQueryBuilder();
call_user_func( $callback, $builder );
$joins = array_map( function ( $join ) {
if ( $join instanceof RawSQL ) {
return $join->sql;
}
if ( $join instanceof Join ) {
if ( $join->alias ) {
return DB::prepare(
'%1s JOIN %2s %3s',
$join->joinType,
$join->table,
$join->alias
);
}
return DB::prepare(
'%1s JOIN %2s',
$join->joinType,
$join->table
);
}
// JoinCondition
return DB::prepare(
$join->quote
? ' %1s %2s = %s'
: ' %1s %2s = %3s',
$join->logicalOperator,
$join->column1,
$join->column2
);
}, $builder->getDefinedJoins() );
return implode( ' ', $joins );
}, $this->joins );
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
/**
* @since 1.0.0
*/
trait LimitStatement {
/**
* @var int
*/
protected $limit;
/**
* @param int $limit
*
* @return $this
*/
public function limit( $limit ) {
$this->limit = (int) $limit;
return $this;
}
protected function getLimitSQL() {
return $this->limit
? [ "LIMIT {$this->limit}" ]
: [];
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\MetaTable;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\RawSQL;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\JoinQueryBuilder;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\QueryBuilder;
/**
* @since 1.0.0
*/
trait MetaQuery {
/**
* @var MetaTable[]
*/
private $metaTablesConfigs = [];
/**
* @var string
*/
private $defaultMetaKeyColumn = 'meta_key';
/**
* @var string
*/
private $defaultMetaValueColumn = 'meta_value';
/**
* @param string|RawSQL $table
* @param string $metaKeyColumn
* @param string $metaValueColumn
*
* @return $this
*/
public function configureMetaTable( $table, $metaKeyColumn, $metaValueColumn ) {
$this->metaTablesConfigs[] = new MetaTable(
$table,
$metaKeyColumn,
$metaValueColumn
);
return $this;
}
/**
* @param string|RawSQL $table
*
* @return MetaTable
*/
protected function getMetaTable( $table ) {
$tableName = QueryBuilder::prefixTable( $table );
foreach ( $this->metaTablesConfigs as $metaTable ) {
if ( $metaTable->tableName === $tableName ) {
return $metaTable;
}
}
return new MetaTable(
$table,
$this->defaultMetaKeyColumn,
$this->defaultMetaValueColumn
);
}
/**
* Select meta columns
*
* @since 1.0.0 optimize group concat functionality
* @since 1.0.0 add group concat functionality
* @since 1.0.0
*
* @param string|RawSQL $table
* @param string $foreignKey
* @param string $primaryKey
* @param array|string $columns
*
* @return $this
*/
public function attachMeta( $table, $foreignKey, $primaryKey, ...$columns ) {
$metaTable = $this->getMetaTable( $table );
foreach ( $columns as $i => $definition ) {
if ( is_array( $definition ) ) {
list( $column, $columnAlias, $concat ) = array_pad( $definition, 3, false );
} else {
$column = $definition;
$columnAlias = $concat = false;
}
// Set dynamic alias
$tableAlias = sprintf('%s_%s_%d', ( $table instanceof RawSQL ) ? $table->sql : $table, 'attach_meta', $i);
// Check if we have meta columns that dev wants to group concat
if ( $concat ) {
/**
* Include foreign key to prevent errors if sql_mode is only_full_group_by
*
* @see https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
*/
$this->groupBy( $foreignKey );
// Group concat same key values into faux array
// @example key => ["value1", "value2"]
$this->selectRaw(
"CONCAT('[',GROUP_CONCAT(DISTINCT CONCAT('\"',%1s,'\"')),']') AS %2s",
$tableAlias . '.' . $metaTable->valueColumnName,
$columnAlias ?: $column
);
} else {
$this->select( [ "{$tableAlias}.{$metaTable->valueColumnName}", $columnAlias ?: $column ] );
}
$this->join(
function ( JoinQueryBuilder $builder ) use (
$table,
$foreignKey,
$primaryKey,
$tableAlias,
$column,
$metaTable
) {
$builder
->leftJoin( $table, $tableAlias )
->on( $foreignKey, "{$tableAlias}.{$primaryKey}" )
->andOn( "{$tableAlias}.{$metaTable->keyColumnName}", $column, true );
}
);
}
return $this;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
/**
* @since 1.0.0
*/
trait OffsetStatement {
/**
* @var int
*/
protected $offset;
/**
* @param int $offset
*
* @return $this
*/
public function offset( $offset ) {
$this->offset = (int) $offset;
return $this;
}
protected function getOffsetSQL() {
return $this->limit && $this->offset
? [ "OFFSET {$this->offset}" ]
: [];
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use KadenceWP\KadenceBlocks\StellarWP\DB\DB;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\OrderBy;
/**
* @since 1.0.0
*/
trait OrderByStatement {
/**
* @var OrderBy[]
*/
protected $orderBys = [];
/**
* @param string $column
* @param string $direction ASC|DESC
*
* @return $this
*/
public function orderBy( $column, $direction = 'ASC' ) {
$this->orderBys[] = new OrderBy( $column, $direction );
return $this;
}
/**
* @return array|string[]
*/
protected function getOrderBySQL() {
if ( empty( $this->orderBys ) ) {
return [];
}
$orderBys = implode(
', ',
array_map( function ( OrderBy $order ) {
return DB::prepare( '%1s %2s', $order->column, $order->direction );
}, $this->orderBys )
);
return [ 'ORDER BY ' . $orderBys ];
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use KadenceWP\KadenceBlocks\StellarWP\DB\DB;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\RawSQL;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\Select;
/**
* @since 1.0.0
*/
trait SelectStatement {
/**
* @var Select[]|RawSQL[]
*/
protected $selects = [];
/**
* @var bool
*/
protected $distinct = false;
/**
* @var bool
*/
private $includeSelectKeyword = true;
/**
* @param array|string $columns
*
* @return $this
*/
public function select( ...$columns ) {
$selects = array_map(function ( $select ) {
if ( is_array( $select ) ) {
list( $column, $alias ) = $select;
return new Select( $column, $alias );
}
return new Select( $select );
}, $columns );
$this->selects = array_merge( $this->selects, $selects );
return $this;
}
/**
* Add raw SQL SELECT statement
*
* @param string $sql
* @param ...$args
*/
public function selectRaw( $sql, ...$args ) {
$this->selects[] = new RawSQL( $sql, $args );
return $this;
}
/**
* Select distinct
*
* @return $this
*/
public function distinct() {
$this->distinct = true;
return $this;
}
/**
* @return string[]
*/
protected function getSelectSQL() {
// Select all by default
if ( empty( $this->selects ) ) {
$this->select( '*' );
}
$selects = [];
foreach ( $this->selects as $i => $select ) {
if ( $select instanceof RawSQL ) {
if ( $i === 0 ) {
// If the first element is an instance of RawSQL
// then we don't need the starting SELECT keyword because we assume that the dev will include that in RawSQL
$this->includeSelectKeyword = false;
}
$selects[] = $select->sql;
continue;
}
if ( $select->alias ) {
$selects[] = DB::prepare(
'%1s AS %2s',
$select->column,
$select->alias
);
continue;
}
$selects[] = DB::prepare( '%1s', $select->column );
}
$selectStatements = implode( ', ', $selects );
if ( $this->includeSelectKeyword ) {
$keyword = 'SELECT ';
if ( $this->distinct ) {
$keyword .= 'DISTINCT ';
}
return [ $keyword . $selectStatements ];
}
return [ $selectStatements ];
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\RawSQL;
/**
* @since 1.0.0
*/
trait TablePrefix {
/**
* @param string|RawSQL $table
*
* @return string
*/
public static function prefixTable( $table ) {
global $wpdb;
// Shared tables in multisite environment
$sharedTables = [
'users' => $wpdb->users,
'usermeta' => $wpdb->usermeta,
];
if ( $table instanceof RawSQL ) {
return $table->sql;
}
if ( array_key_exists( $table, $sharedTables ) ) {
return $sharedTables[ $table ];
}
return $wpdb->prefix . $table;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\Union;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\QueryBuilder;
/**
* @since 1.0.0
*/
trait UnionOperator {
/**
* @var array
*/
protected $unions = [];
/**
* @param QueryBuilder $union
*
* @return $this
*/
public function union( ...$union ) {
$this->unions = array_map( function ( QueryBuilder $builder ) {
return new Union( $builder );
}, $union );
return $this;
}
/**
* @param QueryBuilder $union
*
* @return $this
*/
public function unionAll( ...$union ) {
$this->unions = array_map( function ( QueryBuilder $builder ) {
return new Union( $builder, true );
}, $union );
return $this;
}
/**
* @return array|string[]
*/
protected function getUnionSQL() {
if ( empty( $this->unions ) ) {
return [];
}
return array_map( function ( Union $union ) {
return ( $union->all ? 'UNION ALL ' : 'UNION ' ) . $union->builder->getSQL();
}, $this->unions );
}
}

View File

@@ -0,0 +1,488 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns;
use Closure;
use KadenceWP\KadenceBlocks\StellarWP\DB\DB;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\RawSQL;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\Where;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\QueryBuilder;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types\Operator;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\WhereQueryBuilder;
/**
* @since 1.0.0
*/
trait WhereClause {
/**
* @var Where[]|RawSQL[]|string[]
*/
protected $wheres = [];
/**
* @var bool
*/
private $includeWhereKeyword = true;
/**
* @param string|Closure|null $column The Closure will receive a StellarWP\DB\QueryBuilder\WhereQueryBuilder instance
* @param string|Closure|array|null|mixed $value The Closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance
* @param string $comparisonOperator
* @param string $logicalOperator
*
* @return $this
*/
private function setWhere( $column, $value, $comparisonOperator, $logicalOperator ) {
// If the columns is a Closure instance, we will assume the developer
// wants to begin a nested where statement which is wrapped in parentheses.
if ( $column instanceof Closure && is_null( $value ) ) {
$builder = new WhereQueryBuilder();
call_user_func( $column, $builder );
// Since this is a nested where statement, we have to remove the starting WHERE keyword
// which is the first returned array element from the getWhereSQL method
$wheres = $builder->getSQL();
array_shift( $wheres );
$this->wheres[] = sprintf(
"%s (%s)",
empty( $this->wheres ) ? null : $logicalOperator,
implode( ' ', $wheres )
);
} // If the value is a Closure instance, we will assume the developer is performing an entire sub-select within the query
elseif ( $value instanceof Closure ) {
$builder = new QueryBuilder();
call_user_func( $value, $builder );
$this->wheres[] = sprintf(
"%s %s %s (%s)",
empty( $this->wheres ) ? null : $logicalOperator,
$column,
$comparisonOperator,
$builder->getSQL()
);
} // Standard WHERE clause
else {
$this->wheres[] = new Where(
$column,
$value,
$comparisonOperator,
empty( $this->wheres ) ? null : $logicalOperator
);
}
return $this;
}
/**
* @param string|Closure|null $column The closure will receive a StellarWP\DB\QueryBuilder\WhereQueryBuilder instance
* @param string|Closure|array|null|mixed $value The closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance
* @param string $comparisonOperator
*
* @return $this
*/
public function where( $column, $value = null, $comparisonOperator = '=' ) {
return $this->setWhere(
$column,
$value,
$comparisonOperator,
Operator::_AND
);
}
/**
* @param string|Closure $column
* @param string|Closure|array|null|mixed $value
* @param string $comparisonOperator
*
* @return $this
*/
public function orWhere( $column, $value = null, $comparisonOperator = '=' ) {
return $this->setWhere(
$column,
$value,
$comparisonOperator,
Operator::_OR
);
}
/**
* @param string $column
* @param array|Closure $value
*
* @return $this
*/
public function whereIn( $column, $value ) {
return $this->where(
$column,
$value,
Operator::IN
);
}
/**
* @param string $column
* @param array|Closure $value
*
* @return $this
*/
public function orWhereIn( $column, $value ) {
return $this->orWhere(
$column,
$value,
Operator::IN
);
}
/**
* @param string $column
* @param array|Closure $value
*
* @return $this
*/
public function whereNotIn( $column, $value ) {
return $this->where(
$column,
$value,
Operator::NOTIN
);
}
/**
* @param string $column
* @param array|Closure $value
*
* @return $this
*/
public function orWhereNotIn( $column, $value ) {
return $this->orWhere(
$column,
$value,
Operator::NOTIN
);
}
/**
* @param string $column
* @param string|int $min
* @param string|int $max
*
* @return $this
*/
public function whereBetween( $column, $min, $max ) {
return $this->where(
$column,
[ $min, $max ],
Operator::BETWEEN
);
}
/**
* @param string $column
* @param string|int $min
* @param string|int $max
*
* @return $this
*/
public function whereNotBetween( $column, $min, $max ) {
return $this->where(
$column,
[ $min, $max ],
Operator::NOTBETWEEN
);
}
/**
* @param string $column
* @param string|int $min
* @param string|int $max
*
* @return $this
*/
public function orWhereBetween( $column, $min, $max ) {
return $this->orWhere(
$column,
[ $min, $max ],
Operator::BETWEEN
);
}
/**
* @param string $column
* @param string|int $min
* @param string|int $max
*
* @return $this
*/
public function orWhereNotBetween( $column, $min, $max ) {
return $this->orWhere(
$column,
[ $min, $max ],
Operator::NOTBETWEEN
);
}
/**
* @param string $column
* @param string $value
*
* @return $this
*/
public function whereLike( $column, $value ) {
return $this->where(
$column,
$value,
Operator::LIKE
);
}
/**
* @param string $column
* @param string $value
*
* @return $this
*/
public function whereNotLike( $column, $value ) {
return $this->where(
$column,
$value,
Operator::NOTLIKE
);
}
/**
* @param string $column
* @param mixed $value
*
* @return $this
*/
public function orWhereLike( $column, $value ) {
return $this->orWhere(
$column,
$value,
Operator::LIKE
);
}
/**
* @param string $column
* @param mixed $value
*
* @return $this
*/
public function orWhereNotLike( $column, $value ) {
return $this->orWhere(
$column,
$value,
Operator::NOTLIKE
);
}
/**
* @param string $column
*
* @return $this
*/
public function whereIsNull( $column ) {
return $this->where(
$column,
null,
Operator::ISNULL
);
}
/**
* @param string $column
*
* @return $this
*/
public function orWhereIsNull( $column ) {
return $this->orWhere(
$column,
null,
Operator::ISNULL
);
}
/**
* @param string $column
*
* @return $this
*/
public function whereIsNotNull( $column ) {
return $this->where(
$column,
null,
Operator::NOTNULL
);
}
/**
* @param string $column
*
* @return $this
*/
public function orWhereIsNotNull( $column ) {
return $this->orWhere(
$column,
null,
Operator::NOTNULL
);
}
/**
* @param Closure $callback The closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance
*
* @return $this
*/
public function whereExists( $callback ) {
return $this->where(
null,
$callback,
Operator::EXISTS
);
}
/**
* @param Closure $callback The closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance
*
* @return $this
*/
public function whereNotExists( $callback ) {
return $this->where(
null,
$callback,
Operator::NOTEXISTS
);
}
/**
* Add raw SQL WHERE clause
*
* @param $sql
* @param ...$args
*
* @return $this
*/
public function whereRaw( $sql, ...$args ) {
$this->wheres[] = new RawSQL( $sql, $args );
return $this;
}
/**
* @return string[]
*/
protected function getWhereSQL() {
// Bailout
if ( empty( $this->wheres ) ) {
return [];
}
$wheres = [];
foreach ( $this->wheres as $i => $where ) {
if ( $where instanceof RawSQL ) {
if ( $i === 0 ) {
// If the first element is an instance of RawSQL
// then we don't need the starting WHERE keyword because we assume that the dev will include that in RawSQL
$this->includeWhereKeyword = false;
}
$wheres[] = $where->sql;
continue;
}
if ( $where instanceof Where ) {
$wheres[] = $this->buildWhereSQL( $where );
continue;
}
// If the variable $where is not an instance of the Where class
// it means the SQL is already generated by the Query Builder, so we just return that
$wheres[] = $where;
}
if ( $this->includeWhereKeyword ) {
return array_merge( ['WHERE'], $wheres );
}
return $wheres;
}
/**
* @param Where $where
*
* @return string
*/
private function buildWhereSQL( Where $where ) {
switch ( $where->comparisonOperator ) {
// Handle membership conditions
case Operator::IN:
case Operator::NOTIN:
return DB::prepare(
"%1s %2s %3s",
$where->logicalOperator,
$where->column,
$where->comparisonOperator
) . ' (' . implode(
',',
array_map( function ( $where ) {
return DB::prepare( '%s', $where );
}, $where->value )
) . ')';
// Handle BETWEEN conditions
case Operator::BETWEEN:
case Operator::NOTBETWEEN:
list( $min, $max ) = $where->value;
return DB::prepare(
"%1s %2s %3s %s AND %s",
$where->logicalOperator,
$where->column,
$where->comparisonOperator,
$min,
$max
);
// Handle LIKE conditions
case Operator::LIKE:
case Operator::NOTLIKE:
return DB::prepare(
"%1s %2s %3s %s",
$where->logicalOperator,
$where->column,
$where->comparisonOperator,
strpos( $where->value, '%' ) !== false
? $where->value
: sprintf( '%%%s%%', $where->value )
);
// Handle NULL conditions
case Operator::ISNULL:
case Operator::NOTNULL:
return DB::prepare(
"%1s %2s %3s",
$where->logicalOperator,
$where->column,
$where->comparisonOperator
);
// Standard WHERE clause
default:
return DB::prepare(
"%1s %2s %3s %s",
$where->logicalOperator,
$where->column,
$where->comparisonOperator,
$where->value
);
}
}
}

View File

@@ -0,0 +1,166 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\Join;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\JoinCondition;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Clauses\RawSQL;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types\JoinType;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types\Operator;
/**
* @since 1.0.0
*/
class JoinQueryBuilder {
/**
* @var Join[]|JoinCondition[]|RawSQL[]
*/
private $joins = [];
/**
* @param string|RawSQL $table
* @param string|null $alias
*
* @return $this
*/
public function leftJoin( $table, $alias = '' ) {
return $this->join(
JoinType::LEFT,
$table,
$alias
);
}
/**
* @param string|RawSQL $table
* @param string|null $alias
*
* @return $this
*/
public function rightJoin( $table, $alias = '' ) {
return $this->join(
JoinType::RIGHT,
$table,
$alias
);
}
/**
* @param string|RawSQL $table
* @param string|null $alias
*
* @return $this
*/
public function innerJoin( $table, $alias = '' ) {
return $this->join(
JoinType::INNER,
$table,
$alias
);
}
/**
* @param string $column1
* @param string $column2
* @param bool $quote
*
* @return $this
*/
public function on( $column1, $column2, $quote = false ) {
return $this->joinCondition(
Operator::ON,
$column1,
$column2,
$quote
);
}
/**
* @param string $column1
* @param string $column2
* @param bool $quote
*
* @return $this
*/
public function andOn( $column1, $column2, $quote = null ) {
return $this->joinCondition(
Operator::_AND,
$column1,
$column2,
$quote
);
}
/**
* @param string $column1
* @param string $column2
* @param bool $quote
*
* @return $this
*/
public function orOn( $column1, $column2, $quote = null ) {
return $this->joinCondition(
Operator::_OR,
$column1,
$column2,
$quote
);
}
/**
* Add raw SQL JOIN clause
*
* @param string $sql
* @param array<int,mixed> ...$args
*/
public function joinRaw( $sql, ...$args ) {
$this->joins[] = new RawSQL( $sql, $args );
}
/**
* Add Join
*
* @param string $joinType
* @param string|RawSQL $table
* @param string|null $alias
*
* @return $this
*/
private function join( $joinType, $table, $alias = '' ) {
$this->joins[] = new Join(
$joinType,
$table,
$alias
);
return $this;
}
/**
* Add JoinCondition
*
* @param string $operator
* @param string $column1
* @param string $column2
* @param bool $quote
*
* @return $this
*/
private function joinCondition( $operator, $column1, $column2, $quote ) {
$this->joins[] = new JoinCondition(
$operator,
$column1,
$column2,
$quote
);
return $this;
}
/**
* @return Join[]|JoinCondition[]|RawSQL[]
*/
public function getDefinedJoins() {
return $this->joins;
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\Aggregate;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\CRUD;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\FromClause;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\GroupByStatement;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\HavingClause;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\JoinClause;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\LimitStatement;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\MetaQuery;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\OffsetStatement;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\OrderByStatement;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\SelectStatement;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\TablePrefix;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\UnionOperator;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\WhereClause;
/**
* @since 1.0.0
*/
class QueryBuilder {
use Aggregate;
use CRUD;
use FromClause;
use GroupByStatement;
use HavingClause;
use JoinClause;
use LimitStatement;
use MetaQuery;
use OffsetStatement;
use OrderByStatement;
use SelectStatement;
use TablePrefix;
use UnionOperator;
use WhereClause;
/**
* @return string
*/
public function getSQL() {
$sql = array_merge(
$this->getSelectSQL(),
$this->getFromSQL(),
$this->getJoinSQL(),
$this->getWhereSQL(),
$this->getGroupBySQL(),
$this->getHavingSQL(),
$this->getOrderBySQL(),
$this->getLimitSQL(),
$this->getOffsetSQL(),
$this->getUnionSQL()
);
return $this->buildSQL($sql);
}
/**
* Generate the SQL for a DELETE query. Only the FROM, WHERE, ORDER BY, and LIMIT clauses are included.
* RETURNING is not supported.
* Note that aliases are supported only on MySQL >= 8.0.24 and MariaDB >= 11.6.
*
* @see https://mariadb.com/docs/server/reference/sql-statements/data-manipulation/changing-deleting-data/delete
* @see https://dev.mysql.com/doc/refman/8.4/en/delete.html
*
* @return string DELETE query.
*/
public function deleteSQL() {
$sql = array_merge(
$this->getFromSQL(),
$this->getWhereSQL(),
$this->getOrderBySQL(),
$this->getLimitSQL()
);
return 'DELETE ' . $this->buildSQL($sql);
}
/**
* Build the SQL query from the given parts.
*
* @param array $sql The SQL query parts.
*
* @return string SQL query.
*/
private function buildSQL( $sql ) {
return str_replace(
[ ' ', ' ' ],
' ',
implode( ' ', $sql )
);
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types;
/**
* @since 1.0.0
*/
class JoinType extends Type {
const INNER = 'INNER';
const LEFT = 'LEFT';
const RIGHT = 'RIGHT';
}

View File

@@ -0,0 +1,14 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types;
/**
* @since 1.0.0
*/
class Math extends Type {
const SUM = 'SUM';
const MIN = 'MIN';
const MAX = 'MAX';
const COUNT = 'COUNT';
const AVG = 'AVG';
}

View File

@@ -0,0 +1,24 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types;
/**
* @since 1.0.0
*/
class Operator extends Type {
// _AND and _OR constants are prefixed with underscore to be compatible with PHP 5.6
const _AND = 'AND';
const _OR = 'OR';
const ON = 'ON';
const BETWEEN = 'BETWEEN';
const NOTBETWEEN = 'NOT BETWEEN';
const EXISTS = 'EXISTS';
const NOTEXISTS = 'NOT EXISTS';
const IN = 'IN';
const NOTIN = 'NOT IN';
const LIKE = 'LIKE';
const NOTLIKE = 'NOT LIKE';
const NOT = 'NOT';
const ISNULL = 'IS NULL';
const NOTNULL = 'IS NOT NULL';
}

View File

@@ -0,0 +1,19 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Types;
use ReflectionClass;
/**
* @since 1.0.0
*/
abstract class Type {
/**
* Get Defined Types
*
* @return array
*/
public static function getTypes() {
return ( new ReflectionClass( static::class ) )->getConstants();
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder;
use KadenceWP\KadenceBlocks\StellarWP\DB\QueryBuilder\Concerns\WhereClause;
/**
* @since 1.0.0
*/
class WhereQueryBuilder {
use WhereClause;
/**
* @return string[]
*/
public function getSQL() {
return $this->getWhereSQL();
}
}

View File

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

View File

@@ -0,0 +1,152 @@
# Harbor
Bundled library that handles licensing, updates, and feature gating for WordPress plugins and themes.
[![PHP Compatibility](https://github.com/stellarwp/harbor/actions/workflows/compatibility.yml/badge.svg)](https://github.com/stellarwp/harbor/actions/workflows/compatibility.yml)
[![PHP Tests](https://github.com/stellarwp/harbor/actions/workflows/tests-php.yml/badge.svg)](https://github.com/stellarwp/harbor/actions/workflows/tests-php.yml)
[![PHPStan](https://github.com/stellarwp/harbor/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/stellarwp/harbor/actions/workflows/static-analysis.yml)
[![E2E Tests](https://github.com/stellarwp/harbor/actions/workflows/tests-e2e.yml/badge.svg)](https://github.com/stellarwp/harbor/actions/workflows/tests-e2e.yml)
## Installation
It's recommended that you install Harbor as a project dependency via [Composer](https://getcomposer.org/):
```bash
composer require stellarwp/harbor
```
> We _actually_ recommend that this library gets included in your project using [Strauss](https://github.com/BrianHenryIE/strauss).
>
> Luckily, adding Strauss to your `composer.json` is only slightly more complicated than adding a typical dependency, so checkout our [strauss docs](https://github.com/stellarwp/global-docs/blob/main/docs/strauss-setup.md).
## Initialize the library
Initializing the Harbor library should be done within the `plugins_loaded` action, preferably at priority `0`.
```php
use LiquidWeb\Harbor\Config;
use LiquidWeb\Harbor\Harbor;
add_action( 'plugins_loaded', function() {
/**
* Configure the container.
*
* The container must be compatible with stellarwp/container-contract.
* See here: https://github.com/stellarwp/container-contract#usage.
*
* If you do not have a container, we recommend https://github.com/lucatume/di52
* and the corresponding wrapper:
* https://github.com/stellarwp/container-contract/blob/main/examples/di52/Container.php
*/
$container = new Container();
// Use a plugin basename constant defined in your main plugin file,
// e.g. define( 'MY_PLUGIN_BASENAME', plugin_basename( __FILE__ ) )
Config::set_plugin_basename( MY_PLUGIN_BASENAME );
Config::set_container( $container );
Harbor::init();
}, 0 );
```
## Translation
Package is using `__( 'Invalid request: nonce field is expired. Please try again.', '%TEXTDOMAIN%' )` function for translation. In order to change domain placeholder `'%TEXTDOMAIN%'` to your plugin translation domain run
```bash
./bin/stellar-harbor domain=<your-plugin-domain>
```
or
```bash
./bin/stellar-harbor
```
and prompt the plugin domain
You can also add lines below to your composer file in order to run command automatically
```json
"scripts": {
"stellar-harbor": [
"vendor/bin/stellar-harbor domain=<your-plugin-domain>"
],
"post-install-cmd": [
"@stellar-harbor"
],
"post-update-cmd": [
"@stellar-harbor"
]
}
```
## Registering a plugin
Harbor discovers your plugin's embedded key automatically by scanning active plugins for a file named `LWSW_KEY.php` in the plugin root. No filter registration is required. See the [Harbor Integration Guide](/docs/guides/integration.md) for more details.
## Changelog
This project uses [@stellarwp/changelogger](https://github.com/stellarwp/changelogger) to manage its changelog. All notable changes are tracked via changelog entry files in the `changelog/` directory.
To add a new changelog entry:
```bash
bunx @stellarwp/changelogger add
```
To compile changelog entries into `changelog.txt`:
```bash
bunx @stellarwp/changelogger write --overwrite-version <version>
```
## Releasing
1. Run the **Release Prep** workflow (`Actions → Release Prep → Run workflow`). Supply the target branch, version (e.g. `1.2.0`), and the release date (e.g. `2026-04-29`). The workflow bumps the `VERSION` constant, compiles the changelog, and opens a PR automatically.
2. Review and merge the PR.
3. Create a GitHub Release with a new tag in the format `vX.X.X` targeting the merge commit.
## Documentation
Start with [Harbor Overview](/docs/harbor.md) for the full architecture.
### Subsystems
- [Licensing](/docs/subsystems/licensing.md) — Key discovery, API responses, validation workflows, caching.
- [Catalog](/docs/subsystems/catalog.md) — Product families, tiers, features, the Commerce Portal API.
- [Features](/docs/subsystems/features.md) — Feature types, resolution, strategies, Manager API.
- [Cron](/docs/subsystems/cron.md) — Scheduled refresh of catalog and licensing data.
- [Frontend](/docs/subsystems/frontend.md) — React app, @wordpress/data store, component hierarchy, CSS scoping.
- [Notices](/docs/subsystems/notices.md) — Admin notices, legacy license warnings, persistent dismissal.
### Architecture
- [Unified License Key](/docs/architecture/unified-license-key-system-design.md) — Key model, seat mechanics, system boundaries.
- [Fat Leader / Thin Instance](/docs/architecture/fat-leader-thin-instance.md) — Leader election, cross-instance hooks.
- [Conventions](/docs/architecture/conventions.md) — Naming conventions for namespaces, packages, identifiers.
### API Reference
- [REST: License](/docs/api/rest/license.md) — License endpoints.
- [REST: Catalog](/docs/api/rest/catalog.md) — Catalog endpoints.
- [REST: Features](/docs/api/rest/features.md) — Feature endpoints.
- [REST: Legacy Licenses](/docs/api/rest/legacy-licenses.md) — Legacy license endpoints.
- [Liquid Web Licensing v1](/docs/api/liquid-web-software-licensing-v1.md) — External licensing API consumed by Harbor.
### Guides
- [Integration Guide](/docs/guides/integration.md) — How to integrate your plugin with Harbor.
- [CLI Commands](/docs/guides/cli.md) — WP-CLI commands for feature management.
- [Testing](/docs/guides/testing.md) — PHP tests with Codeception/`slic`; E2E tests with Playwright/wp-env.
### Plugins with Harbor
| Plugin name | Repository | Distribution | Note |
|-------------------------|-------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
| GiveWP | [impress-org/givewp](https://github.com/impress-org/givewp) | [wp.org](https://wordpress.org/plugins/give/) | |
| LearnDash | [stellarwp/learndash-core](https://github.com/stellarwp/learndash-core) | [Herald](https://herald.nexcess.com/admin/products/sfwd-lms) | |
| MemberDash | [stellarwp/memberdash](https://github.com/stellarwp/memberdash) | [Herald](https://herald.nexcess.com/admin/products/memberdash) | |
| The Events Calendar | [the-events-calendar/the-events-calendar](https://github.com/the-events-calendar/the-events-calendar) | [wp.org](https://wordpress.org/plugins/the-events-calendar/) | [tribe-common](https://github.com/the-events-calendar/tribe-common) should be updated first |
| Event Tickets | [the-events-calendar/event-tickets](https://github.com/the-events-calendar/event-tickets) | [wp.org](https://wordpress.org/plugins/event-tickets/) | [tribe-common](https://github.com/the-events-calendar/tribe-common) should be updated first |
| Kadence Memberships Pro | [stellarwp/restrict-content-pro](https://github.com/stellarwp/restrict-content-pro) | [Herald](https://herald.nexcess.com/admin/products/restrict-content-pro) | |
| Kadence Blocks | [stellarwp/kadence-blocks](https://github.com/stellarwp/kadence-blocks) | [wp.org](https://wordpress.org/plugins/kadence-blocks/) | |
| Kadence Shop Kit | [stellarwp/kadence-shop-kit](https://github.com/stellarwp/kadence-shop-kit) | [Herald](https://herald.nexcess.com/admin/products/kadence-shop-kit) | |
| Kadence Theme Kit Pro | [stellarwp/kadence-pro](https://github.com/stellarwp/kadence-pro) | [Herald](https://herald.nexcess.com/admin/products/kadence-theme-pro) | |

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env php
<?php declare(strict_types=1);
function ask( string $question, string $default = '' ): string {
$answer = readline( $question . ( $default ? " ($default)" : null ) . ': ' );
if ( ! $answer ) {
return $default;
}
return $answer;
}
function confirm( string $question, bool $default = false ): bool {
$answer = ask( $question . ' (' . ( $default ? 'Y/n' : 'y/N' ) . ')' );
if ( ! $answer ) {
return $default;
}
return strtolower( $answer ) === 'y';
}
function replace_in_file( string $file, array $replacements ): void {
$contents = file_get_contents( $file );
file_put_contents(
$file,
str_replace(
array_keys( $replacements ),
array_values( $replacements ),
$contents
)
);
}
function writeln( string $line ): void {
echo $line . PHP_EOL;
}
function run( string $command ): string {
return trim( shell_exec( $command ) ?: '' );
}
parse_str( implode( '&', array_slice( $argv, 1 ) ), $result );
$domain = $result['domain'] ?? ask( 'Text Domain Name', 'my-text-domain' );
if ( empty( $domain ) ) {
writeln( 'Translation domain could not be empty' );
exit( 0 );
}
if ( ! confirm( 'Modify files?', true ) ) {
exit( 1 );
}
$files = explode( PHP_EOL, run( 'grep -E -r -l -i "%TEXTDOMAIN%" "' . dirname( __FILE__, 2 ) . '/src"' ) );
$files = array_filter( $files );
foreach ( $files as $file ) {
replace_in_file( $file, [
'%TEXTDOMAIN%' => $domain
] );
}

View File

@@ -0,0 +1 @@
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '6301c15122a6fd371454');

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => 'f2eccc34eeedc5b1912d');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
### [1.2.0] 2026-05-13
* Feature - The features page no longer shows unless a Premium Addon has been registered.
* Tweak - Added the `lw_harbor/loaded` action to indicate when Harbor is loaded.
* Tweak - Added the `lw_harbor/premium_plugin_exists` filter to declare whether a premium plugin exists on a site.
* Tweak - Removed opt-in as it will not be necessary now that external communications are gated behind a Premium Addon enabling Harbor.
* Tweak - The `lw-harbor/hide_menu_item` now also applies to the submenu item created by `lw_harbor_register_submenu()`.
### [1.1.0] 2026-05-12
* Feature - External connections are now gated behind a per-site opt-in flow.
* Fix - Moved the Licensing Page to a submenu item under Settings.
* Tweak - Added the `lw-harbor/hide_menu_item` to hide the Licensing Page submenu item while leaving the submenu page registered.
### [1.0.1] 2026-04-29
* Feature - Show Harbor version on admin page footer.
* Fix - Upgrade buttons now route existing subscribers to the portal's change-plan flow for the target tier instead of the catalog `purchase_url`, which was adding a duplicate plan to the basket. Unlicensed visitors still see the `purchase_url` for a fresh purchase.
### [1.0.0] 2026-04-28
* Feature - Initial version.

View File

@@ -0,0 +1 @@
<?php

View File

@@ -0,0 +1,154 @@
@import "tailwindcss" important;
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0 0);
--color-card: oklch(1 0 0);
--color-card-foreground: oklch(0.145 0 0);
--color-popover: oklch(1 0 0);
--color-popover-foreground: oklch(0.145 0 0);
--color-primary: oklch(0.205 0.08 265);
--color-primary-foreground: oklch(0.985 0 0);
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu,
Cantarell, "Helvetica Neue", sans-serif;
--color-secondary: oklch(0.965 0 0);
--color-secondary-foreground: oklch(0.205 0 0);
--color-muted: oklch(0.955 0 0);
--color-muted-foreground: oklch(0.45 0 0);
--color-accent: oklch(0.965 0 0);
--color-accent-foreground: oklch(0.205 0 0);
--color-destructive: oklch(0.577 0.245 27.325);
--color-destructive-foreground: oklch(0.577 0.245 27.325);
--color-border: oklch(0.9 0 0);
--color-input: oklch(0.9 0 0);
--color-ring: oklch(0.708 0.165 254.624);
--color-chart-1: oklch(0.646 0.222 41.116);
--color-chart-2: oklch(0.6 0.118 184.704);
--color-chart-3: oklch(0.398 0.07 227.392);
--color-chart-4: oklch(0.828 0.189 84.429);
--color-chart-5: oklch(0.769 0.188 70.08);
--color-sidebar: oklch(0.985 0 0);
--color-sidebar-foreground: oklch(0.145 0 0);
--color-sidebar-primary: oklch(0.205 0.08 265);
--color-sidebar-primary-foreground: oklch(0.985 0 0);
--color-sidebar-accent: oklch(0.965 0 0);
--color-sidebar-accent-foreground: oklch(0.205 0 0);
--color-sidebar-border: oklch(0.922 0 0);
--color-sidebar-ring: oklch(0.708 0.165 254.624);
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--radius-xl: 0.75rem;
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
}
.lw-harbor-ui {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.955 0 0);
--muted-foreground: oklch(0.45 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.9 0 0);
--input: oklch(0.9 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.lw-harbor-ui .dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
.lw-harbor-ui {
background-color: var(--color-background);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--color-foreground);
}
.lw-harbor-ui * {
border-color: var(--color-border);
}
#wpcontent:has(.lw-harbor-ui) {
padding-left: 0;
}
#wpcontent:has(.lw-harbor-ui) #wpbody-content {
padding-bottom: 0;
}
#wpcontent:has(.lw-harbor-ui) + #wpfooter {
display: none;
}
.wrap:has(> .lw-harbor-ui) {
margin: 0;
padding: 0;
}
@layer base {
* {
@apply border-border box-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.15 6.4043H20.8472L18.842 10.9403L21.489 15.561H11.744L10.9019 12.0425" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.33154 2.33496H11.9541C12.3121 2.33496 12.6248 2.57595 12.7143 2.92077L14.9352 11.4916H4.92121" fill="white"/>
<path d="M11.9541 2C12.4425 2 12.8724 2.317 13.0186 2.7793L13.0352 2.83594L13.8828 6.10645H21.3018L19.1729 10.9229L22 15.8584H11.5176L10.4551 11.8252H5.39941L8.19531 21.5723C8.24619 21.7496 8.14538 21.9348 7.96973 21.9863C7.80555 22.0343 7.6338 21.9489 7.57031 21.793L7.55859 21.7588L2.00977 2.41602L2.00293 2.38086L2 2.3418V2.31836L2.00391 2.2832L2.01074 2.25L2.01855 2.22266L2.03223 2.19141L2.04785 2.16211L2.06543 2.13477L2.08398 2.11133L2.11133 2.08398L2.14648 2.05664L2.16797 2.04297L2.19238 2.03027L2.22461 2.01758L2.26172 2.00781L2.30273 2.00098L2.33203 2H11.9541ZM15.2559 11.4062L13.1582 15.2627L20.9785 15.2637L18.5117 10.958L20.3926 6.70117H14.0371L15.2559 11.4062ZM5.20703 11.1562H14.5059L12.3936 3.00488C12.3458 2.82117 12.1852 2.68786 11.9951 2.6709L11.9541 2.66895H2.77246L5.20703 11.1562Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.8803 5.51111H7.78457L8.1267 6.65545H9.21865C9.57966 6.65545 9.78176 6.43418 9.68085 6.09083C9.57778 5.74018 9.24266 5.51111 8.8803 5.51111ZM6.09473 4.40527H8.73488C9.92046 4.40527 10.7922 5.17554 11.0599 6.09861C11.3254 7.01361 10.9021 7.76878 9.72781 7.76878H8.4594L8.97475 9.49232H7.63484L6.09473 4.40527Z" fill="#041C1E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,16 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_383_6481)">
<path d="M22.8547 10.174C22.7492 9.51465 22.2449 9.17345 21.8756 8.92455C21.7537 8.84214 21.569 8.71687 21.5427 8.66082C21.2641 7.76909 21.1108 6.69604 20.9724 5.77628C20.8603 5.03124 20.7251 4.36038 20.5751 3.83951C20.0872 2.14175 18.996 2 18.012 2H15.1192C14.7681 2 13.9209 2 13.255 2.60987C13.0259 2.81921 13.1775 3.20162 13.4874 3.20162C13.5566 3.20162 13.6209 3.17195 13.6703 3.12415C14.0791 2.7302 14.6132 2.65932 15.1192 2.65932H18.0103C18.8823 2.65932 19.5779 2.75987 19.9422 4.02248C20.0773 4.48895 20.2092 5.1252 20.318 5.8488C20.4614 6.79823 20.6163 7.9059 20.913 8.85697C20.9971 9.12729 21.2443 9.29377 21.5064 9.47014C21.8196 9.68113 22.1427 9.90035 22.202 10.2762C22.3273 11.069 22.4591 12.175 22.1542 12.6415C21.8971 13.0338 21.4421 13.2382 21.1273 13.2382H20.3163C20.3097 13.2382 20.3048 13.2365 20.2998 13.2365C20.2998 13.2365 20.2998 13.2365 20.2982 13.2365C20.2042 13.2365 20.1169 13.2744 20.051 13.3404C19.985 13.4063 19.9488 13.4953 19.9504 13.5909L20.7531 16.8793C20.7548 17.0524 20.8784 17.1958 21.0482 17.2221C21.0663 17.2254 21.0844 17.2271 21.1042 17.2271H21.1059C21.1998 17.2271 21.2872 17.1892 21.3531 17.1232C21.4191 17.0573 21.4553 16.9683 21.4537 16.8727L20.6806 13.8975H21.1289C21.7223 13.8975 22.3569 13.5382 22.708 13.0025C23.0426 12.4898 23.0871 11.6443 22.8547 10.174Z" fill="#041C1E"/>
<path d="M10.776 2.65438C10.2288 2.06923 9.50684 2 8.83433 2H5.94155C5.43552 2 3.89105 2 3.357 3.83787C3.22183 4.30104 3.06854 4.94883 2.93997 5.73507C2.77514 6.7356 2.63339 7.89765 2.34328 8.85203C2.2757 9.0729 2.01527 9.3152 1.78451 9.52948C1.52078 9.77508 1.27188 10.0058 1.20265 10.2877C0.807059 11.8998 1.06749 12.5393 1.34276 13.0107C1.59166 13.4376 2.22461 13.8975 2.8147 13.8975H3.32733L2.55262 16.8793C2.55757 17.0689 2.71415 17.2205 2.90206 17.2205C2.90536 17.2205 2.90866 17.2205 2.91195 17.2205C2.93173 17.2205 2.94986 17.2172 2.96799 17.2139C3.13777 17.1809 3.25645 17.0326 3.2515 16.8628L4.05423 13.5794C4.04929 13.3898 3.8927 13.2382 3.70479 13.2382C3.70149 13.2382 3.6982 13.2382 3.6949 13.2382H2.81305C2.48834 13.2382 2.06142 12.9382 1.91143 12.6794C1.73835 12.3827 1.47627 11.9327 1.8422 10.4459C1.86363 10.3553 2.08615 10.1492 2.23285 10.0124C2.5246 9.74211 2.85426 9.43388 2.97294 9.04323C3.27953 8.03282 3.42293 6.83944 3.5927 5.81254C3.71303 5.08234 3.86797 4.43291 3.9883 4.02248C4.33939 2.82086 5.17343 2.65932 5.94155 2.65932H8.83268C9.40794 2.65932 9.90573 2.70548 10.2832 3.09777C10.3475 3.16536 10.4381 3.19997 10.5304 3.19997C10.814 3.20162 10.9689 2.86207 10.776 2.65438Z" fill="#041C1E"/>
<path d="M4.12507 10.5518C3.94376 10.5518 3.79541 10.7001 3.79541 10.8814C3.79541 11.0627 3.94376 11.2111 4.12507 11.2111H5.3712C5.80635 11.2111 5.81624 10.5518 5.38768 10.5518H4.12507Z" fill="#041C1E"/>
<path d="M16.1991 6.18809C15.7063 4.47879 14.6563 4.35352 13.6228 4.35352H12.23H11.7025H10.3097C9.2762 4.35352 8.16524 4.62384 7.70865 6.18809C7.36745 7.36168 7.33119 9.97096 6.7345 11.9374C6.53506 12.5934 5.54607 13.1044 5.43398 13.5643C4.98235 15.3988 5.3219 16.0054 5.55431 16.4043C5.78672 16.8032 6.39495 17.2334 6.91417 17.2334H17.0051C17.5243 17.2334 18.1491 16.9103 18.4985 16.3779C18.8479 15.8455 18.843 14.8631 18.6106 13.4011C18.4556 12.4203 17.2672 12.1879 17.1205 11.7198C16.5337 9.84239 16.5716 7.48036 16.1991 6.18809Z" fill="white"/>
<path d="M15.2053 14.6478H8.78014C8.50652 14.6478 8.28564 14.4632 8.28564 14.234C8.28564 14.0049 8.50652 13.8203 8.78014 13.8203H15.2053C15.4789 13.8203 15.6997 14.0049 15.6997 14.234C15.6997 14.4632 15.4789 14.6478 15.2053 14.6478Z" fill="#041C1E"/>
<path d="M19.8484 10.5518C20.0297 10.5518 20.178 10.7001 20.178 10.8814C20.178 11.0627 20.0297 11.2111 19.8484 11.2111H18.6022C18.1671 11.2111 18.1572 10.5518 18.5857 10.5518H19.8484Z" fill="#041C1E"/>
<path d="M18.9353 13.3499C18.8183 12.6147 18.2496 12.2307 17.8342 11.9505C17.6842 11.8499 17.4568 11.6966 17.4337 11.6225C17.1139 10.5972 16.982 9.39559 16.865 8.33407C16.7727 7.48849 16.6853 6.69071 16.5139 6.09732C15.9617 4.18528 14.7321 4.02539 13.6211 4.02539H10.308C9.73607 4.02539 7.9938 4.02539 7.39052 6.09732C7.23723 6.62478 7.14657 7.39289 7.04108 8.28133C6.90427 9.43679 6.75098 10.7456 6.41802 11.8433C6.33725 12.1087 6.03396 12.3922 5.76529 12.6411C5.46859 12.9164 5.18838 13.1785 5.11256 13.4867C4.62631 15.468 5.03014 16.1586 5.26915 16.5707C5.54771 17.047 6.25484 17.563 6.91416 17.563H7.2191L6.55977 21.1151C6.53505 21.387 6.7345 21.6277 7.00647 21.6524L7.08723 21.6541C7.32459 21.6376 7.52239 21.4513 7.54546 21.2057L8.21303 17.563H15.787L16.4562 21.214C16.4793 21.4579 16.6771 21.6458 16.9145 21.6623L16.9952 21.6607C17.2672 21.6359 17.4666 21.3953 17.4419 21.1233L16.7826 17.5646H17.0084C17.6727 17.5646 18.3831 17.1608 18.7771 16.5608C19.1529 15.9806 19.2007 15.0213 18.9353 13.3499ZM18.2216 16.1965C17.9183 16.6597 17.3793 16.902 17.0035 16.902H6.91251C6.52516 16.902 6.01748 16.5443 5.83781 16.2377C5.63507 15.8899 5.33014 15.3658 5.75375 13.6433C5.78177 13.5263 6.02243 13.3021 6.21528 13.1224C6.54494 12.8158 6.91746 12.4697 7.04932 12.0329C7.39876 10.8791 7.557 9.53899 7.69711 8.3555C7.79766 7.49673 7.88666 6.75499 8.02347 6.27863C8.4339 4.87097 9.40806 4.68142 10.308 4.68142H13.6211C14.6398 4.68142 15.4541 4.79845 15.881 6.27698C16.0359 6.81433 16.12 7.58574 16.2106 8.40166C16.331 9.49284 16.4661 10.7291 16.8057 11.8153C16.898 12.1104 17.1732 12.2966 17.4666 12.4928C17.8342 12.74 18.2133 12.9971 18.2842 13.4488C18.4293 14.3685 18.5809 15.6493 18.2216 16.1965Z" fill="#041C1E"/>
</g>
<defs>
<clipPath id="clip0_383_6481">
<rect width="22" height="19.661" fill="white" transform="translate(1 2)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.75244 8.15137H21.6537V17.1107C21.6537 18.1003 20.8485 18.9026 19.8552 18.9026H6.75244V8.15137Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.7057 8.22566V6.5966C21.7238 5.5533 20.864 4.69858 19.7985 4.70021L8.48549 4.71862C7.44119 4.72002 6.5928 5.54538 6.58447 6.5684L6.58447 8.22566H21.7057Z" fill="#041C1E"/>
<path d="M18.2692 3C18.4208 3 18.5456 3.11231 18.5616 3.2566L18.5633 3.28798L18.5631 4.41199L19.7983 4.41196C21.006 4.41049 21.9846 5.36173 22.0001 6.53146V16.8561C22.0001 18.1462 20.9319 19.1919 19.6144 19.1919L11.8197 19.1914C11.6573 19.1914 11.5256 19.0624 11.5256 18.9034C11.5257 18.7443 11.6574 18.6154 11.8198 18.6155L19.6144 18.616C20.5864 18.616 21.3781 17.8605 21.4107 16.9166L21.4117 16.8561L21.4117 6.53321C21.3951 5.69737 20.7122 5.01834 19.8585 4.98891L19.7987 4.98792L18.5631 4.98795L18.5633 6.1631C18.5633 6.32215 18.4316 6.45108 18.2692 6.45108C18.1176 6.45108 17.9928 6.33877 17.9768 6.19448L17.9751 6.1631L17.9749 4.98795L10.4979 4.98872L10.4979 6.1631C10.4979 6.32215 10.3663 6.45108 10.2038 6.45108C10.0522 6.45108 9.92742 6.33877 9.91145 6.19448L9.90973 6.1631L9.90969 4.98872L8.48604 4.98899C7.62689 4.99014 6.91794 5.67026 6.88025 6.51078L6.87872 6.56833L6.87968 8.67155C6.87975 8.83059 6.74813 8.95958 6.5857 8.95965C6.4341 8.95972 6.30924 8.84746 6.29321 8.70318L6.29147 8.6718L6.29053 6.56617C6.29999 5.40457 7.24604 4.45057 8.41898 4.41411L8.48561 4.41303L9.90969 4.41276L9.90973 3.28798C9.90973 3.12893 10.0414 3 10.2038 3C10.3554 3 10.4802 3.11231 10.4962 3.2566L10.4979 3.28798L10.4979 4.41276L17.9749 4.41199L17.9751 3.28798C17.9751 3.12893 18.1068 3 18.2692 3Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.599 14.5228C14.599 15.2876 13.9429 15.8138 12.8182 15.8138C11.5809 15.8138 11.0498 15.3549 11.0498 14.3637V14.2474H11.6747V14.3331C11.6747 14.9878 12.0308 15.2753 12.8119 15.2753C13.5118 15.2753 13.9492 14.9511 13.9492 14.4616C13.9492 13.9354 13.6117 13.7702 12.9869 13.7702H12.3933V13.2929H12.9869C13.6055 13.2929 13.8867 13.0421 13.8867 12.5893C13.8867 12.1549 13.5368 11.8429 12.8432 11.8429C12.1433 11.8429 11.7434 12.2222 11.7434 12.7545V12.8218H11.131V12.7362C11.131 11.8796 11.7497 11.3105 12.8619 11.3105C13.9242 11.3105 14.5178 11.8184 14.5178 12.5281C14.5178 13.0176 14.3053 13.3419 13.8617 13.4949C14.3303 13.6356 14.599 13.9599 14.599 14.5228ZM17.7236 15.153V15.7281H15.2304V15.153H16.174V12.5588H15.4554V12.0937C15.974 12.0693 16.1927 11.8551 16.2802 11.3779H16.7801V15.153H17.7236Z" fill="#041C1E"/>
<path d="M6.22949 14.2949C8.39521 14.2949 10.1514 16.0141 10.1514 18.1348C10.1511 19.0306 8.39504 19.542 6.22949 19.542C4.06414 19.5419 2.30883 19.1585 2.30859 18.1348C2.30859 16.0143 4.064 14.2952 6.22949 14.2949ZM6.22949 10.4551C7.24016 10.4551 8.05957 11.2574 8.05957 12.2471C8.0595 13.2366 7.24011 14.0391 6.22949 14.0391C5.21908 14.0388 4.40047 13.2365 4.40039 12.2471C4.40039 11.2576 5.21904 10.4553 6.22949 10.4551Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.2117 10.659C5.29232 10.659 4.54701 11.3888 4.54701 12.289C4.54701 13.1892 5.29232 13.919 6.2117 13.919C7.13109 13.919 7.8764 13.1892 7.8764 12.289C7.8764 11.3888 7.13109 10.659 6.2117 10.659ZM3.9588 12.289C3.9588 11.0707 4.96746 10.083 6.2117 10.083C7.45595 10.083 8.4646 11.0707 8.4646 12.289C8.4646 13.0813 8.03801 13.7761 7.39745 14.1651C9.14621 14.6665 10.4234 16.2489 10.4234 18.1234C10.4234 18.4852 10.253 18.7883 9.99483 19.0277C9.74113 19.2629 9.39485 19.4454 9.00667 19.5853C8.22926 19.8656 7.21274 20.0003 6.2117 20.0003C5.21066 20.0003 4.19415 19.8656 3.41674 19.5853C3.02855 19.4454 2.68227 19.2629 2.42857 19.0277C2.17041 18.7883 2 18.4852 2 18.1234C2 16.2489 3.27719 14.6665 5.02595 14.1651C4.38539 13.7761 3.9588 13.0813 3.9588 12.289ZM6.2117 14.5753C4.2105 14.5753 2.58821 16.1639 2.58821 18.1234C2.58821 18.2912 2.66265 18.4515 2.83297 18.6094C3.00776 18.7715 3.2736 18.92 3.61997 19.0448C4.31166 19.2942 5.25395 19.4243 6.2117 19.4243C7.16946 19.4243 8.11174 19.2942 8.80343 19.0448C9.1498 18.92 9.41564 18.7715 9.59043 18.6094C9.76075 18.4515 9.8352 18.2912 9.8352 18.1234C9.8352 16.1639 8.2129 14.5753 6.2117 14.5753Z" fill="#041C1E"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.6812 11.8147C14.6812 13.7772 13.0532 15.3686 11.0449 15.3686C9.03666 15.3686 7.40869 13.7772 7.40869 11.8147C7.40869 9.85184 9.03666 8.26074 11.0449 8.26074C13.0532 8.26074 14.6812 9.85184 14.6812 11.8147Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.75139 19.9863C8.1069 19.8316 8.52367 19.9878 8.68205 20.3349C8.84036 20.6826 8.68014 21.0903 8.32463 21.2451C7.96903 21.3995 7.55329 21.2428 7.39494 20.8955C7.23676 20.5479 7.39611 20.141 7.75139 19.9863ZM17.0639 15.916C17.4195 15.7613 17.8363 15.9173 17.9946 16.2646C18.1528 16.6122 17.9925 17.0189 17.6371 17.1738C17.2814 17.3285 16.8649 17.1726 16.7065 16.8252C16.5482 16.4776 16.7085 16.0706 17.0639 15.916ZM3.13029 10.8056C3.48588 10.651 3.9027 10.807 4.06096 11.1543C4.21927 11.5019 4.05919 11.9095 3.70353 12.0644L3.65666 12.082C3.31197 12.2043 2.92439 12.0471 2.77287 11.7148C2.61472 11.3673 2.77496 10.9603 3.13029 10.8056ZM19.2973 5.98531C19.6528 5.83074 20.0696 5.9868 20.2279 6.33394C20.3863 6.68158 20.226 7.08928 19.8705 7.2441C19.5149 7.39869 19.0982 7.2419 18.9399 6.89449C18.7819 6.54698 18.942 6.13993 19.2973 5.98531ZM8.77775 2.75484C9.13335 2.60028 9.55019 2.75716 9.70842 3.10445C9.86637 3.45198 9.70635 3.85888 9.351 4.01363C8.99533 4.16827 8.57872 4.01145 8.42033 3.66402C8.26239 3.31654 8.42251 2.90944 8.77775 2.75484Z" fill="white"/>
<path d="M9.36486 16.2968C9.44644 16.1408 9.63695 16.0669 9.80822 16.1307C9.97947 16.1946 10.0714 16.3734 10.026 16.5428L10.0143 16.579L8.87365 19.4979C9.06698 19.6358 9.22734 19.8236 9.33166 20.0526C9.64003 20.7298 9.34774 21.5194 8.67834 21.8505L8.61388 21.8798C7.89937 22.1905 7.06309 21.8767 6.74474 21.1786C6.42665 20.4801 6.74817 19.6623 7.46252 19.3514C7.70676 19.2452 7.9653 19.2119 8.21252 19.2421L9.34924 16.3309L9.36486 16.2968ZM8.68224 20.3358C8.52402 19.9885 8.10717 19.8317 7.75158 19.9862C7.39633 20.1408 7.23621 20.5479 7.39416 20.8954C7.55255 21.2428 7.96916 21.3996 8.32482 21.245C8.68017 21.0902 8.84018 20.6833 8.68224 20.3358ZM14.8824 14.6093C14.9884 14.4816 15.1679 14.446 15.3131 14.5145L15.3483 14.5341L15.3824 14.5585L16.4967 15.4393C16.5808 15.3774 16.6737 15.3238 16.774 15.2801C17.4887 14.9693 18.3261 15.2841 18.6442 15.9823C18.9619 16.6806 18.6407 17.4983 17.9264 17.8094C17.2118 18.1203 16.3746 17.8064 16.0563 17.1083C15.8869 16.7363 15.8993 16.3303 16.0553 15.9852L14.9352 15.0985L14.9059 15.0731C14.7795 14.9488 14.7671 14.7485 14.8824 14.6093ZM17.9938 16.2645C17.8354 15.9173 17.4187 15.7612 17.0631 15.9159C16.708 16.0707 16.5484 16.4776 16.7067 16.8251C16.8651 17.1725 17.2816 17.3284 17.6373 17.1737C17.9925 17.0187 18.152 16.612 17.9938 16.2645ZM11.0445 7.91394C13.2493 7.91394 15.0367 9.66049 15.0367 11.8153C15.0366 13.9699 13.2491 15.7167 11.0445 15.7167C8.84016 15.7164 7.05345 13.9697 7.05334 11.8153C7.05334 9.66063 8.84001 7.91418 11.0445 7.91394ZM11.0445 8.60925C9.23292 8.60948 7.76428 10.0446 7.76428 11.8153C7.7644 13.5857 9.23309 15.0211 11.0445 15.0214C12.8562 15.0214 14.3257 13.5858 14.3258 11.8153C14.3258 10.0445 12.8564 8.60925 11.0445 8.60925ZM2.84045 10.1708C3.55507 9.85991 4.39248 10.1738 4.71056 10.8719C4.75144 10.9618 4.7821 11.0539 4.80236 11.1464L6.12853 11.2216L6.1676 11.2264C6.34413 11.2555 6.47395 11.4101 6.46349 11.5887C6.4528 11.7675 6.30543 11.907 6.12658 11.9159H6.08752L4.77013 11.8407C4.6541 12.2097 4.38197 12.5296 3.99279 12.6991C3.27821 13.0099 2.441 12.6962 2.12267 11.9979C1.80466 11.2995 2.12633 10.4818 2.84045 10.1708ZM4.06017 11.1542C3.9018 10.8071 3.48503 10.6509 3.12951 10.8055C2.77443 10.9603 2.61491 11.3673 2.77306 11.7147C2.92459 12.0471 3.31216 12.2043 3.65685 12.0819L3.70373 12.0643C4.05921 11.9094 4.21845 11.5017 4.06017 11.1542ZM19.0074 5.35046C19.7221 5.0396 20.5595 5.35347 20.8776 6.05163C21.186 6.72893 20.8928 7.51852 20.2233 7.84949L20.1598 7.87878C19.6033 8.12084 18.973 7.98402 18.5709 7.58191L18.5377 7.60339L15.4274 9.39441L15.3932 9.41199C15.2298 9.4841 15.0341 9.42574 14.941 9.27136C14.848 9.11703 14.8902 8.921 15.0338 8.81628L15.067 8.79577L18.1764 7.00476L18.2194 6.98425C18.0342 6.33494 18.3584 5.63311 19.0074 5.35046ZM8.48791 2.11999C9.2025 1.80915 10.0399 2.12306 10.358 2.82117C10.6665 3.49846 10.3733 4.28805 9.70373 4.61902L9.6842 4.62878L10.3717 7.03992L10.3805 7.07995C10.4081 7.25187 10.3003 7.4208 10.1246 7.46863C9.948 7.51669 9.76527 7.4253 9.69885 7.26257L9.68615 7.22644L8.98498 4.76648C8.47247 4.738 7.99348 4.43704 7.77013 3.94714C7.45236 3.24879 7.77387 2.4309 8.48791 2.11999ZM20.2272 6.33484C20.069 5.98764 19.653 5.83086 19.2975 5.98523C18.9422 6.13985 18.782 6.54689 18.9401 6.89441C19.0985 7.24187 19.515 7.39874 19.8707 7.24402C20.226 7.08918 20.3853 6.68237 20.2272 6.33484ZM9.70763 3.10437C9.54946 2.75719 9.13347 2.60044 8.77795 2.75476C8.42255 2.90943 8.26224 3.31733 8.42053 3.66492C8.57907 4.0121 8.99563 4.16821 9.35119 4.01355C9.70651 3.85868 9.8658 3.4519 9.70763 3.10437Z" fill="#041C1E"/>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,20 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_383_6477)">
<path d="M5.97119 11.3391V8.39153C5.97119 7.24319 6.90768 6.3125 8.06317 6.3125H19.5951C20.7506 6.3125 21.6871 7.24319 21.6871 8.39153V18.9069C21.6871 20.0552 20.7506 20.9859 19.5951 20.9859H11.4796" fill="white"/>
<path d="M5.97119 9.96893V8.39153C5.97119 7.24319 6.90768 6.3125 8.06317 6.3125H19.5951C20.7506 6.3125 21.6871 7.24319 21.6871 8.39153V10.0145L5.97119 9.971V9.96893Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.06331 6.67312C7.10453 6.67312 6.33133 7.44474 6.33133 8.39215V11.3397C6.33133 11.5385 6.17015 11.6997 5.97133 11.6997C5.77251 11.6997 5.61133 11.5385 5.61133 11.3397V8.39215C5.61133 7.04289 6.71111 5.95312 8.06331 5.95312H19.5953C20.9475 5.95312 22.0473 7.04289 22.0473 8.39215V18.9075C22.0473 20.2568 20.9475 21.3465 19.5953 21.3465H11.4797C11.2809 21.3465 11.1197 21.1853 11.1197 20.9865C11.1197 20.7877 11.2809 20.6265 11.4797 20.6265H19.5953C20.5541 20.6265 21.3273 19.8549 21.3273 18.9075V8.39215C21.3273 7.44474 20.5541 6.67312 19.5953 6.67312H8.06331Z" fill="#041C1E"/>
<path d="M6.59915 21.6892C8.96633 21.6892 10.8853 19.7821 10.8853 17.4295C10.8853 15.077 8.96633 13.1699 6.59915 13.1699C4.23197 13.1699 2.31299 15.077 2.31299 17.4295C2.31299 19.7821 4.23197 21.6892 6.59915 21.6892Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5988 13.5296C4.42833 13.5296 2.67264 15.2776 2.67264 17.4292C2.67264 19.5808 4.42833 21.3288 6.5988 21.3288C8.76927 21.3288 10.525 19.5808 10.525 17.4292C10.525 15.2776 8.76927 13.5296 6.5988 13.5296ZM1.95264 17.4292C1.95264 14.8757 4.03491 12.8096 6.5988 12.8096C9.16269 12.8096 11.245 14.8757 11.245 17.4292C11.245 19.9826 9.16269 22.0488 6.5988 22.0488C4.03491 22.0488 1.95264 19.9826 1.95264 17.4292Z" fill="#041C1E"/>
<path d="M6.04199 15.1846V18.3373H8.93489" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.04213 14.8242C6.24095 14.8242 6.40213 14.9854 6.40213 15.1842V17.977H8.93503C9.13385 17.977 9.29503 18.1381 9.29503 18.337C9.29503 18.5358 9.13385 18.697 8.93503 18.697H5.68213V15.1842C5.68213 14.9854 5.84331 14.8242 6.04213 14.8242Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2202 12.6041C13.2202 12.4053 13.3814 12.2441 13.5802 12.2441H19.4724C19.6712 12.2441 19.8324 12.4053 19.8324 12.6041C19.8324 12.803 19.6712 12.9641 19.4724 12.9641H13.5802C13.3814 12.9641 13.2202 12.803 13.2202 12.6041Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4399 12.6041C10.4399 12.4053 10.6011 12.2441 10.7999 12.2441H11.8858C12.0847 12.2441 12.2458 12.4053 12.2458 12.6041C12.2458 12.803 12.0847 12.9641 11.8858 12.9641H10.7999C10.6011 12.9641 10.4399 12.803 10.4399 12.6041Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2202 15.0192C13.2202 14.8204 13.3814 14.6592 13.5802 14.6592H19.4724C19.6712 14.6592 19.8324 14.8204 19.8324 15.0192C19.8324 15.218 19.6712 15.3792 19.4724 15.3792H13.5802C13.3814 15.3792 13.2202 15.218 13.2202 15.0192Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2202 17.4313C13.2202 17.2325 13.3814 17.0713 13.5802 17.0713H19.4724C19.6712 17.0713 19.8324 17.2325 19.8324 17.4313C19.8324 17.6301 19.6712 17.7913 19.4724 17.7913H13.5802C13.3814 17.7913 13.2202 17.6301 13.2202 17.4313Z" fill="#041C1E"/>
</g>
<defs>
<clipPath id="clip0_383_6477">
<rect width="20" height="16" fill="white" transform="translate(2 6)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,16 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.39321 4.04809L5.41656 4.06692L7.51566 5.97057C7.61977 6.065 7.62648 6.22446 7.53065 6.32701C7.28016 6.59508 7.13869 6.94554 7.13869 7.31844C7.13869 8.12595 7.79978 8.77998 8.61447 8.77998C9.03529 8.77998 9.42708 8.60505 9.70498 8.30314C9.80082 8.19901 9.98318 8.42221 10.0882 8.51742C11.0201 9.4335 11.8008 10.2009 11.8008 10.2009C11.8008 10.2009 14.2495 8.10662 14.655 7.50731C14.7373 7.38575 14.7982 7.21309 14.9145 7.29766L14.937 7.31606L16.875 9.08808C16.9839 9.18771 16.9842 9.3572 16.8757 9.45723L16.8238 9.50647C16.4794 9.85044 16.2824 10.3153 16.2824 10.812C16.2824 11.8311 17.1088 12.6559 18.126 12.6559C18.7024 12.6559 19.2344 12.3896 19.582 11.9421C19.6679 11.8314 19.8269 11.8113 19.9381 11.8917L19.9611 11.9104L21.8949 13.6787C21.9862 13.7622 22.0033 13.898 21.9356 14.0009C20.3806 16.362 16.3226 18.7281 14.1058 18.9428C12.1616 19.131 10.8245 18.8724 9.324 17.906L9.22799 17.8434C8.20646 17.1682 6.3334 15.6478 3.59997 13.2764L3.25066 12.9728C2.42937 12.5544 1.96372 11.7019 2.00221 10.354C2.05814 8.38776 3.1445 6.1543 5.05193 4.08305C5.14094 3.9864 5.28854 3.97325 5.39321 4.04809Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6545 18.0722C18.1306 17.4732 20.9049 15.5014 22.0002 13.8877C22.0002 13.8877 21.2631 13.2428 19.7889 11.953C19.4052 12.4322 18.8053 12.7409 18.1306 12.7409C16.9721 12.7409 16.0329 11.8312 16.0329 10.7094C16.0329 10.1483 16.6473 9.27268 16.6473 9.27268L14.7709 7.50098C14.2676 8.22232 13.2291 9.4063 11.903 10.3606C11.2925 10.8 10.621 11.1907 9.9126 11.4652" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.05152 4.08343C5.14056 3.98707 5.28778 3.9745 5.39234 4.04925L5.41578 4.06781L7.51539 5.97113C7.6193 6.06554 7.62571 6.22507 7.53004 6.32757C7.27962 6.59556 7.13852 6.94602 7.13844 7.31878C7.13844 8.1263 7.79934 8.7807 8.61402 8.7807C9.03458 8.78063 9.42605 8.60576 9.70387 8.30414C9.7997 8.20001 9.96411 8.19232 10.0691 8.28753L11.1462 9.2641C11.2504 9.35868 11.257 9.51906 11.1609 9.62152C11.0647 9.72401 10.9019 9.72975 10.7976 9.63519L9.8982 8.82074L9.88551 8.83148C9.55084 9.10694 9.131 9.2691 8.68433 9.2846L8.61402 9.28558C7.51486 9.28558 6.62379 8.40451 6.62379 7.31878C6.62386 6.91849 6.74641 6.53657 6.96754 6.21527L6.99 6.18304L5.25953 4.61273L5.23512 4.64007C3.55876 6.52909 2.60226 8.52643 2.5193 10.269L2.51539 10.3686C2.47223 11.8801 3.09961 12.5799 4.20289 12.7416C6.55044 13.0853 8.94215 12.1575 11.5574 10.2504L11.6765 10.1625C13.0218 9.1253 13.9334 8.2693 14.5476 7.36175C14.6299 7.24027 14.7976 7.21372 14.9138 7.29828L14.9363 7.31683L16.8738 9.08832C16.9827 9.1879 16.9832 9.35741 16.8748 9.45746L16.823 9.50726C16.4787 9.85122 16.282 10.3163 16.282 10.8129C16.2821 11.832 17.1086 12.6567 18.1257 12.6567C18.7019 12.6565 19.2333 12.3901 19.5808 11.9428C19.6667 11.8322 19.8261 11.8118 19.9373 11.892L19.9607 11.9116L21.8943 13.6791C21.9855 13.7625 22.0028 13.8986 21.9353 14.0014C20.3804 16.3625 16.322 18.7291 14.1052 18.9438C12.161 19.132 10.8235 18.873 9.32301 17.9067L9.2273 17.8442C8.20577 17.169 6.3328 15.6482 3.59937 13.2768L3.24976 12.9731C2.42872 12.5547 1.96329 11.7026 2.00172 10.3549C2.05764 8.38872 3.14409 6.15469 5.05152 4.08343ZM14.7771 7.91937C14.1587 8.74775 13.3076 9.53722 12.1257 10.4575L11.989 10.5639L11.739 10.7455C9.16034 12.601 6.76514 13.5402 4.37281 13.2729L4.9773 13.7934C7.01783 15.5411 8.48272 16.7194 9.36695 17.3256L9.51441 17.4252C10.9477 18.3724 12.1812 18.6222 14.0545 18.4409C16.0439 18.2482 19.757 16.1177 21.3367 13.9662L21.3836 13.9018L19.8054 12.4594L19.7927 12.4721C19.3742 12.8903 18.8098 13.1411 18.2019 13.1606L18.1257 13.1616C16.823 13.1616 15.7685 12.1092 15.7683 10.8129C15.7683 10.2614 15.9599 9.7388 16.3015 9.32367L16.3347 9.28558L14.8025 7.88421L14.7771 7.91937Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.955 15.2023C16.7029 15.4707 16.7205 15.8887 16.9941 16.136C17.268 16.3833 17.6946 16.3658 17.9467 16.0974C18.199 15.829 18.1814 15.411 17.9075 15.164C17.6337 14.9169 17.2073 14.9339 16.955 15.2023Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9331 13.4407C14.6808 13.7091 14.6986 14.127 14.9722 14.3741C15.2461 14.6216 15.6727 14.6041 15.9248 14.3357C16.1771 14.0673 16.1593 13.6494 15.8854 13.4021C15.6116 13.1551 15.1852 13.1723 14.9331 13.4407Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.9106 11.679C12.6585 11.9475 12.6761 12.3654 12.9497 12.6125C13.2235 12.8598 13.6501 12.8424 13.9022 12.5739C14.1545 12.3057 14.137 11.8875 13.8631 11.6404C13.5892 11.3933 13.1629 11.4106 12.9106 11.679Z" fill="white"/>
<path d="M5.3937 4.04852L5.41705 4.06713L7.51614 5.94905C7.62026 6.0424 7.62697 6.20004 7.53114 6.30143C7.28065 6.56643 7.13918 6.9129 7.13918 7.28153C7.13918 8.07983 7.80027 8.7264 8.61495 8.7264C9.03578 8.7264 9.42757 8.55347 9.70547 8.25499C9.80131 8.15206 9.98367 8.37271 10.0887 8.46683C11.0206 9.37246 11.8012 10.1311 11.8012 10.1311C11.8012 10.1311 14.2499 8.06072 14.6555 7.46826C14.7378 7.34808 14.7987 7.17739 14.915 7.261L14.9375 7.27918L16.8755 9.03098C16.9844 9.12947 16.9847 9.29703 16.8762 9.39592L16.8243 9.44459C16.4799 9.78464 16.2829 10.2442 16.2829 10.7352C16.2829 11.7427 17.1092 12.5581 18.1265 12.5581C18.7029 12.5581 19.2349 12.2948 19.5825 11.8524C19.6684 11.743 19.8274 11.7231 19.9386 11.8026L19.9616 11.8211L21.8954 13.5692C21.9867 13.6517 22.0038 13.786 21.9361 13.8877C20.3811 16.2219 16.323 18.561 14.1063 18.7732C12.1621 18.9593 10.825 18.7036 9.32448 17.7483L9.22848 17.6864C8.20695 17.0189 6.33389 15.5158 3.60046 13.1715L3.25115 12.8713C2.42986 12.4577 1.96421 11.615 2.0027 10.2824C2.05862 8.33866 3.14499 6.13069 5.05242 4.08308C5.14143 3.98754 5.28903 3.97454 5.3937 4.04852Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6515 17.9093C18.1229 17.3191 20.8884 15.376 21.9801 13.7859C21.9801 13.7859 21.2454 13.1503 19.7759 11.8793C19.3935 12.3516 18.7955 12.6557 18.1229 12.6557C16.9681 12.6557 16.0319 11.7594 16.0319 10.6538C16.0319 10.101 16.6443 9.23808 16.6443 9.23808L14.7739 7.49219C14.2722 8.20302 13.2371 9.36974 11.9152 10.3102C11.3066 10.7431 10.6372 11.1282 9.93115 11.3986" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.05347 4.09556C5.15726 3.98419 5.32889 3.96927 5.45093 4.05552L5.47828 4.077L7.57105 5.95298C7.69223 6.06186 7.7003 6.24597 7.58863 6.36411C7.34629 6.62053 7.20875 6.95563 7.20875 7.31236C7.20892 8.08463 7.84866 8.70973 8.63648 8.70982C9.04353 8.70982 9.42231 8.54252 9.69117 8.25376C9.80292 8.13375 9.99452 8.12545 10.1169 8.23521L11.1902 9.1981C11.3116 9.30714 11.3189 9.49114 11.2068 9.60923C11.0946 9.72711 10.9055 9.73465 10.7839 9.62583L9.91578 8.84751L9.91285 8.85044C9.57693 9.11691 9.15939 9.27418 8.71558 9.29087L8.63648 9.29185C7.51693 9.29176 6.61029 8.40534 6.61011 7.31236C6.61011 6.92038 6.72758 6.54532 6.94117 6.2274L6.9607 6.1981L5.29664 4.70591L5.28296 4.72154C3.63302 6.56642 2.69032 8.51303 2.60523 10.2108L2.60132 10.3182C2.55893 11.7857 3.16764 12.4577 4.24585 12.6141C6.56845 12.9503 8.93906 12.0421 11.533 10.1747L11.6628 10.0809C13.0005 9.06137 13.9064 8.22054 14.5154 7.33091C14.6113 7.1908 14.8066 7.16023 14.9421 7.25767L14.9685 7.27915L16.9002 9.02525C17.0271 9.14004 17.0275 9.33473 16.9011 9.45005L16.8503 9.4979C16.5151 9.829 16.324 10.2766 16.324 10.7547C16.324 11.7358 17.1281 12.5292 18.1179 12.5292C18.6787 12.5291 19.1966 12.2733 19.5349 11.8426C19.6352 11.7153 19.8204 11.6924 19.95 11.785L19.9763 11.8065L21.9041 13.5487C22.0104 13.6448 22.0307 13.8012 21.9519 13.9198C20.3947 16.2574 16.3377 18.5965 14.115 18.8094C12.1264 18.9998 10.7682 18.7273 9.22437 17.7186L9.11695 17.6473L9.00367 17.5702C7.89784 16.8106 5.98771 15.2619 3.26441 12.9178C2.43506 12.4975 1.96307 11.6443 2.00171 10.3026C2.05778 8.35387 3.1457 6.14353 5.05347 4.09556ZM14.8015 7.94712C14.1852 8.75827 13.3409 9.53132 12.1736 10.4305L12.0271 10.5428C9.40219 12.4603 6.96957 13.4582 4.5398 13.2352L5.05542 13.6737C7.19707 15.4866 8.70053 16.6747 9.55835 17.2352C10.9782 18.1629 12.1975 18.4072 14.0554 18.2293C16.0139 18.0419 19.6712 15.9734 21.2546 13.8768L21.3074 13.8045L19.7937 12.4364L19.7468 12.4803C19.3298 12.8629 18.7839 13.091 18.198 13.1102L18.1179 13.1122C16.7951 13.1122 15.7244 12.0554 15.7244 10.7547C15.7244 10.205 15.917 9.68461 16.2595 9.26939L16.2732 9.25279L14.8123 7.93247L14.8015 7.94712Z" fill="#041C1E"/>
<path d="M10.015 17.4377C10.015 19.2138 8.53345 20.6536 6.70562 20.6536C4.87802 20.6536 3.39648 19.2138 3.39648 17.4377C3.39648 15.6617 4.87802 14.2217 6.70562 14.2217C8.53345 14.2217 10.015 15.6617 10.015 17.4377Z" fill="#56F58E"/>
<path d="M6.55122 17.4624L5.39404 17.4303L8.01913 15.084L6.87026 17.1809L8.02744 17.213L5.56454 19.7544L6.55122 17.4624Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.7434 14.6972C16.4704 14.9405 16.4525 15.3523 16.7035 15.6172C16.9546 15.8821 17.3799 15.8994 17.6529 15.6561C17.9262 15.4128 17.944 15.001 17.6929 14.7363C17.4417 14.4716 17.0167 14.4539 16.7434 14.6972Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9813 13.0206C14.708 13.2638 14.6904 13.6756 14.9414 13.9403C15.1925 14.2054 15.6177 14.2226 15.8908 13.9794C16.1641 13.7361 16.1817 13.3243 15.9305 13.0594C15.6794 12.7947 15.2544 12.7773 14.9813 13.0206Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2283 11.2745C12.9552 11.5178 12.9374 11.9297 13.1883 12.1944C13.4395 12.4593 13.8647 12.4765 14.1378 12.2332C14.4111 11.9901 14.4289 11.578 14.1777 11.3133C13.9266 11.0486 13.5016 11.0312 13.2283 11.2745Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,14 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.39321 4.04809L5.41656 4.06692L7.51566 5.97057C7.61977 6.065 7.62648 6.22446 7.53065 6.32701C7.28016 6.59508 7.13869 6.94554 7.13869 7.31844C7.13869 8.12595 7.79978 8.77998 8.61447 8.77998C9.03529 8.77998 9.42708 8.60505 9.70498 8.30314C9.80082 8.19901 9.98318 8.42221 10.0882 8.51742C11.0201 9.4335 11.8008 10.2009 11.8008 10.2009C11.8008 10.2009 14.2495 8.10662 14.655 7.50731C14.7373 7.38575 14.7982 7.21309 14.9145 7.29766L14.937 7.31606L16.875 9.08808C16.9839 9.18771 16.9842 9.3572 16.8757 9.45723L16.8238 9.50647C16.4794 9.85044 16.2824 10.3153 16.2824 10.812C16.2824 11.8311 17.1088 12.6559 18.126 12.6559C18.7024 12.6559 19.2344 12.3896 19.582 11.9421C19.6679 11.8314 19.8269 11.8113 19.9381 11.8917L19.9611 11.9104L21.8949 13.6787C21.9862 13.7622 22.0033 13.898 21.9356 14.0009C20.3806 16.362 16.3226 18.7281 14.1058 18.9428C12.1616 19.131 10.8245 18.8724 9.324 17.906L9.22799 17.8434C8.20646 17.1682 6.3334 15.6478 3.59997 13.2764L3.25066 12.9728C2.42937 12.5544 1.96372 11.7019 2.00221 10.354C2.05814 8.38776 3.1445 6.1543 5.05193 4.08305C5.14094 3.9864 5.28854 3.97325 5.39321 4.04809Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6545 18.0722C18.1306 17.4732 20.9049 15.5014 22.0002 13.8877C22.0002 13.8877 21.2631 13.2428 19.7889 11.953C19.4052 12.4322 18.8053 12.7409 18.1306 12.7409C16.9721 12.7409 16.0329 11.8312 16.0329 10.7094C16.0329 10.1483 16.6473 9.27268 16.6473 9.27268L14.7709 7.50098C14.2676 8.22232 13.2291 9.4063 11.903 10.3606C11.2925 10.8 10.621 11.1907 9.9126 11.4652" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.05152 4.08343C5.14056 3.98707 5.28778 3.9745 5.39234 4.04925L5.41578 4.06781L7.51539 5.97113C7.6193 6.06554 7.62571 6.22507 7.53004 6.32757C7.27962 6.59556 7.13852 6.94602 7.13844 7.31878C7.13844 8.1263 7.79934 8.7807 8.61402 8.7807C9.03458 8.78063 9.42605 8.60576 9.70387 8.30414C9.7997 8.20001 9.96411 8.19232 10.0691 8.28753L11.1462 9.2641C11.2504 9.35868 11.257 9.51906 11.1609 9.62152C11.0647 9.72401 10.9019 9.72975 10.7976 9.63519L9.8982 8.82074L9.88551 8.83148C9.55084 9.10694 9.131 9.2691 8.68433 9.2846L8.61402 9.28558C7.51486 9.28558 6.62379 8.40451 6.62379 7.31878C6.62386 6.91849 6.74641 6.53657 6.96754 6.21527L6.99 6.18304L5.25953 4.61273L5.23512 4.64007C3.55876 6.52909 2.60226 8.52643 2.5193 10.269L2.51539 10.3686C2.47223 11.8801 3.09961 12.5799 4.20289 12.7416C6.55044 13.0853 8.94215 12.1575 11.5574 10.2504L11.6765 10.1625C13.0218 9.1253 13.9334 8.2693 14.5476 7.36175C14.6299 7.24027 14.7976 7.21372 14.9138 7.29828L14.9363 7.31683L16.8738 9.08832C16.9827 9.1879 16.9832 9.35741 16.8748 9.45746L16.823 9.50726C16.4787 9.85122 16.282 10.3163 16.282 10.8129C16.2821 11.832 17.1086 12.6567 18.1257 12.6567C18.7019 12.6565 19.2333 12.3901 19.5808 11.9428C19.6667 11.8322 19.8261 11.8118 19.9373 11.892L19.9607 11.9116L21.8943 13.6791C21.9855 13.7625 22.0028 13.8986 21.9353 14.0014C20.3804 16.3625 16.322 18.7291 14.1052 18.9438C12.161 19.132 10.8235 18.873 9.32301 17.9067L9.2273 17.8442C8.20577 17.169 6.3328 15.6482 3.59937 13.2768L3.24976 12.9731C2.42872 12.5547 1.96329 11.7026 2.00172 10.3549C2.05764 8.38872 3.14409 6.15469 5.05152 4.08343ZM14.7771 7.91937C14.1587 8.74775 13.3076 9.53722 12.1257 10.4575L11.989 10.5639L11.739 10.7455C9.16034 12.601 6.76514 13.5402 4.37281 13.2729L4.9773 13.7934C7.01783 15.5411 8.48272 16.7194 9.36695 17.3256L9.51441 17.4252C10.9477 18.3724 12.1812 18.6222 14.0545 18.4409C16.0439 18.2482 19.757 16.1177 21.3367 13.9662L21.3836 13.9018L19.8054 12.4594L19.7927 12.4721C19.3742 12.8903 18.8098 13.1411 18.2019 13.1606L18.1257 13.1616C16.823 13.1616 15.7685 12.1092 15.7683 10.8129C15.7683 10.2614 15.9599 9.7388 16.3015 9.32367L16.3347 9.28558L14.8025 7.88421L14.7771 7.91937Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.955 15.2023C16.7029 15.4707 16.7205 15.8887 16.9941 16.136C17.268 16.3833 17.6946 16.3658 17.9467 16.0974C18.199 15.829 18.1814 15.411 17.9075 15.164C17.6337 14.9169 17.2073 14.9339 16.955 15.2023Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9331 13.4407C14.6808 13.7091 14.6986 14.127 14.9722 14.3741C15.2461 14.6216 15.6727 14.6041 15.9248 14.3357C16.1771 14.0673 16.1593 13.6494 15.8854 13.4021C15.6116 13.1551 15.1852 13.1723 14.9331 13.4407Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.9106 11.679C12.6585 11.9475 12.6761 12.3654 12.9497 12.6125C13.2235 12.8598 13.6501 12.8424 13.9022 12.5739C14.1545 12.3057 14.137 11.8875 13.8631 11.6404C13.5892 11.3933 13.1629 11.4106 12.9106 11.679Z" fill="white"/>
<path d="M5.3937 4.04922L5.41705 4.06811L7.51614 5.9777C7.62026 6.07242 7.62697 6.23238 7.53114 6.33526C7.28065 6.60416 7.13918 6.95572 7.13918 7.32978C7.13918 8.13982 7.80027 8.79589 8.61495 8.79589C9.03578 8.79589 9.42757 8.62041 9.70547 8.31755C9.80131 8.21311 9.98367 8.437 10.0887 8.53251C11.0206 9.45145 11.8012 10.2213 11.8012 10.2213C11.8012 10.2213 14.2499 8.12043 14.6555 7.51925C14.7378 7.3973 14.7987 7.2241 14.915 7.30894L14.9375 7.32739L16.8755 9.10495C16.9844 9.20489 16.9847 9.37491 16.8762 9.47525L16.8243 9.52464C16.4799 9.86969 16.2829 10.336 16.2829 10.8342C16.2829 11.8566 17.1092 12.6839 18.1265 12.6839C18.7029 12.6839 19.2349 12.4168 19.5825 11.9679C19.6684 11.8569 19.8274 11.8366 19.9386 11.9173L19.9616 11.9361L21.8954 13.7099C21.9867 13.7937 22.0038 13.9299 21.9361 14.0331C20.3811 16.4016 16.323 18.7751 14.1063 18.9904C12.1621 19.1793 10.825 18.9198 9.32448 17.9505L9.22848 17.8876C8.20695 17.2103 6.33389 15.6852 3.60046 13.3063L3.25115 13.0018C2.42986 12.5821 1.96421 11.727 2.0027 10.3748C2.05862 8.40245 3.14499 6.16201 5.05242 4.08429C5.14143 3.98734 5.28903 3.97415 5.3937 4.04922Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6515 18.1143C18.1229 17.5154 20.8884 15.5437 21.9801 13.9302C21.9801 13.9302 21.2454 13.2853 19.7759 11.9956C19.3935 12.4748 18.7955 12.7834 18.1229 12.7834C16.9681 12.7834 16.0319 11.8739 16.0319 10.7521C16.0319 10.1911 16.6443 9.31551 16.6443 9.31551L14.7739 7.54395C14.2722 8.26523 13.2371 9.44911 11.9152 10.4034C11.3066 10.8427 10.6372 11.2334 9.93115 11.5078" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.05398 4.09706C5.15776 3.9843 5.32948 3.96867 5.45144 4.05604L5.47878 4.0785L7.57058 5.98182C7.69199 6.09227 7.6999 6.27886 7.58815 6.39882C7.34598 6.65891 7.20933 6.99894 7.20925 7.36073C7.20925 8.14456 7.84898 8.77968 8.63698 8.77968C9.044 8.77965 9.42284 8.60977 9.69167 8.31678C9.8034 8.19509 9.99407 8.18698 10.1165 8.29823L11.1907 9.27479C11.312 9.38545 11.3194 9.57199 11.2073 9.69178C11.0951 9.81168 10.906 9.81897 10.7844 9.70839L9.91628 8.91932L9.91335 8.92225C9.57735 9.19271 9.15904 9.35167 8.71511 9.36854L8.63698 9.3705C7.51725 9.3705 6.61062 8.46999 6.61062 7.36073C6.61068 6.96311 6.72814 6.58265 6.94167 6.26014L6.9612 6.23085L5.29714 4.7162L5.28347 4.73182C3.63341 6.6039 2.69083 8.57934 2.60573 10.3021L2.60085 10.4115C2.55846 11.9006 3.16802 12.582 4.24636 12.7406C6.56896 13.0817 8.93956 12.161 11.5335 10.266L11.6634 10.1713C13.0011 9.13667 13.9068 8.28303 14.5159 7.38026C14.6118 7.23807 14.8071 7.20712 14.9426 7.30604L14.969 7.32753L16.9007 9.09901C17.0276 9.21546 17.028 9.41361 16.9016 9.53065L16.8509 9.5785C16.5156 9.91439 16.3236 10.3687 16.3235 10.8539C16.3235 11.8494 17.1286 12.6557 18.1184 12.6557C18.679 12.6555 19.1962 12.3952 19.5344 11.9584C19.6347 11.829 19.8209 11.8058 19.9505 11.8998L19.9768 11.9213L21.9046 13.6898C22.0106 13.7873 22.031 13.9456 21.9524 14.0658C20.3951 16.4379 16.3372 18.8108 14.1145 19.0267C12.126 19.2199 10.7678 18.9439 9.2239 17.9203L9.11648 17.849L9.00319 17.7709C7.89737 17.0002 5.98725 15.4278 3.26394 13.0492C2.43477 12.6226 1.96346 11.7564 2.00222 10.3949C2.05836 8.41758 3.14627 6.17507 5.05398 4.09706ZM14.802 8.00526C14.1857 8.82835 13.3414 9.61333 12.1741 10.5258L12.0276 10.64C9.40285 12.5855 6.96994 13.5976 4.5403 13.3715L5.05593 13.8158C7.1972 15.6551 8.69997 16.8612 9.55788 17.4301C10.9778 18.3715 12.1979 18.6193 14.0559 18.4389C16.0144 18.2486 19.6707 16.1502 21.2542 14.0228L21.3079 13.9496L19.7942 12.5599L19.7473 12.6049C19.3304 12.9932 18.7844 13.225 18.1985 13.2445L18.1175 13.2465C16.7949 13.2462 15.7249 12.1736 15.7249 10.8539C15.725 10.2961 15.9175 9.7683 16.26 9.34706L16.2727 9.33046L14.8118 7.99061L14.802 8.00526Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.7434 14.8548C16.4704 15.1017 16.4525 15.5195 16.7035 15.7883C16.9546 16.0571 17.3799 16.0746 17.6529 15.8277C17.9262 15.5809 17.944 15.163 17.6929 14.8944C17.4417 14.6258 17.0167 14.6079 16.7434 14.8548Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9813 13.1537C14.708 13.4006 14.6904 13.8184 14.9414 14.087C15.1925 14.356 15.6177 14.3735 15.8908 14.1266C16.1641 13.8798 16.1817 13.4619 15.9305 13.1931C15.6794 12.9246 15.2544 12.9069 14.9813 13.1537Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2283 11.3813C12.9552 11.6282 12.9374 12.0461 13.1883 12.3147C13.4395 12.5835 13.8647 12.601 14.1378 12.3541C14.4111 12.1074 14.4289 11.6893 14.1777 11.4207C13.9266 11.1521 13.5016 11.1344 13.2283 11.3813Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.32861 9.553L14.4273 4.80859L18.5207 15.7885L5.42197 20.5329L4.15859 17.1441C4.15859 17.1441 5.96775 16.3307 5.28552 14.2974C4.60329 12.2641 2.64253 13.0774 2.64253 13.0774L1.32861 9.553Z" fill="white"/>
<path d="M14.3232 4.95508L18.8108 3.33239L20.0989 6.84841C18.852 7.29932 18.167 8.55436 18.569 9.65163C18.9632 10.7278 20.2567 11.2525 21.4827 10.8469L21.5546 10.8218L22.8428 14.338L18.3553 15.9607L14.3232 4.95508Z" fill="#041C1E"/>
<path d="M12.2574 5.3503C12.4166 5.29295 12.5929 5.37487 12.6509 5.53304C12.7047 5.6806 12.6365 5.84246 12.4979 5.91028L12.4673 5.9234L1.7867 9.78546L2.87145 12.7463L2.91754 12.7359C4.0596 12.5004 5.23233 13.1107 5.67262 14.2122L5.69806 14.2788C6.11677 15.4221 5.61363 16.6755 4.566 17.2354L4.52393 17.2569L5.60868 20.2177L7.62174 19.4898C7.77045 19.436 7.93373 19.5041 8.002 19.642L8.01526 19.6726C8.06926 19.8202 8.00137 19.9829 7.86259 20.0507L7.8317 20.0629L5.53027 20.8951C5.38165 20.9488 5.21865 20.8816 5.15035 20.7438L5.13709 20.7133L3.84877 17.1967C3.79104 17.0386 3.87319 16.864 4.03232 16.8064C4.97737 16.4646 5.46512 15.4262 5.12133 14.4873C4.77732 13.5484 3.73181 13.0645 2.78666 13.4063C2.63798 13.46 2.47465 13.3919 2.4064 13.2541L2.39314 13.2235L1.10515 9.70791C1.05102 9.56017 1.11898 9.39758 1.25781 9.32975L1.28837 9.31663L12.2574 5.3503ZM18.4692 3.10413C18.6179 3.05036 18.7821 3.11809 18.8504 3.25602L18.8627 3.28687L20.1507 6.8025C20.2087 6.96079 20.1268 7.13617 19.9675 7.19378C19.0225 7.53564 18.5354 8.5736 18.8794 9.51249C19.2235 10.4512 20.2682 10.9354 21.2131 10.5939C21.3618 10.5401 21.5256 10.6071 21.594 10.7449L21.6063 10.7757L22.8946 14.2922C22.9486 14.4398 22.8814 14.6022 22.7429 14.6701L22.7111 14.6826L9.81905 19.3443C9.65978 19.4019 9.483 19.321 9.42495 19.1628C9.37086 19.0152 9.43893 18.8526 9.57762 18.7847L9.60909 18.7712L22.2128 14.2138L21.128 11.2529L21.0828 11.263C19.9408 11.4985 18.7684 10.889 18.3281 9.78764L18.3027 9.72103C17.8837 8.57758 18.3866 7.3234 19.4344 6.76348L19.4755 6.74237L18.3908 3.7815L14.4648 5.20113C14.3162 5.25483 14.1532 5.18751 14.0848 5.04983L14.0716 5.01931C14.0175 4.87158 14.0854 4.70898 14.2242 4.64116L14.2548 4.62803L18.4692 3.10413Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5431 10.7869C11.537 10.7812 11.5359 10.779 11.534 10.7779C11.5275 10.7741 11.5206 10.7705 11.5136 10.7672C10.9444 10.5014 10.3735 10.4995 9.8015 10.762C9.64675 10.8327 9.50731 10.9283 9.37834 11.0385C8.79205 11.5426 8.56344 12.3392 8.79521 13.0696C8.79769 13.0774 8.80275 13.0842 8.80834 13.0945C9.72115 12.3246 10.631 11.5565 11.5431 10.7869ZM13.7806 10.7647C12.4317 11.9028 11.0846 13.0395 9.73333 14.1799C9.74722 14.1878 9.7564 14.1939 9.76707 14.1991C10.03 14.3297 10.3097 14.3975 10.6048 14.4032C10.8362 14.4079 11.0621 14.3717 11.2811 14.2965C11.4208 14.2486 11.5548 14.1871 11.6785 14.1066C12.1793 13.7804 12.478 13.3257 12.5678 12.7393C12.5733 12.7034 12.5728 12.7032 12.6106 12.6935C13.1383 12.5565 13.6663 12.4194 14.1945 12.2829C14.2015 12.2811 14.2093 12.2804 14.22 12.2787C14.2212 12.2928 14.2225 12.3053 14.223 12.3177C14.2704 13.3774 13.8375 14.3973 13.0375 15.1116C12.9123 15.2229 12.7797 15.3254 12.6399 15.4189C12.5111 15.5045 12.3784 15.5835 12.2388 15.6517C12.1342 15.7029 12.0278 15.7497 11.9186 15.791C11.6609 15.8892 11.3955 15.9566 11.1222 15.9933C10.6047 16.0631 10.0955 16.0248 9.59629 15.8753C8.96344 15.6853 8.42576 15.3467 7.98245 14.8624C7.8586 14.727 7.74647 14.5827 7.64522 14.4301C7.55569 14.2946 7.47451 14.1538 7.40535 14.007C7.15194 13.471 7.03534 12.9062 7.07092 12.3145C7.12899 11.3579 7.5142 10.5495 8.22081 9.89065C8.34115 9.77889 8.47058 9.67793 8.60638 9.58441C8.75649 9.48102 8.91225 9.38606 9.07758 9.30822C9.91856 8.91133 10.7877 8.83575 11.68 9.09302C12.3663 9.29117 12.9363 9.66927 13.3962 10.2086C13.5111 10.3425 13.6149 10.4852 13.7059 10.6368C13.7265 10.6701 13.7463 10.7041 13.7663 10.7377C13.7711 10.7457 13.7751 10.7548 13.7806 10.7647Z" fill="#041C1E"/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.13132 9.44103C3.78734 8.49178 4.26758 7.4385 5.20098 7.09502L16.2114 3.04332C17.1626 2.69328 18.2089 3.21727 18.5189 4.19894L20.7249 11.1853C20.7622 11.3035 20.786 11.4255 20.7905 11.5496C20.8729 13.8348 19.7958 17.1791 16.4626 18.8049L13.4843 20.0198L8.84373 21.5974C8.69123 21.6492 8.52591 21.5683 8.47018 21.4145L4.13132 9.44103Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0822 5.89798L18.5802 4.4037C18.2325 3.37534 17.125 2.83821 16.1234 3.2118L5.48928 7.17956C4.50755 7.54552 3.99815 8.65027 4.34798 9.65535L4.85112 11.0604L19.0822 5.89798Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.805 2.02062C13.9672 1.96053 14.1455 2.03616 14.22 2.19054L14.2347 2.22472L14.6253 3.31945L15.8538 2.86144C17.0088 2.43066 18.2837 3.0299 18.7181 4.19347L18.7425 4.26281L20.5521 9.61144C21.9441 13.4928 19.9874 17.7902 16.182 19.2101C16.0082 19.2749 15.8149 19.1842 15.7513 19.007C15.6877 18.8297 15.7777 18.6333 15.9515 18.5685C18.1612 17.7441 19.6829 15.8515 20.1644 13.671C19.4714 14.2168 18.3856 14.52 17.0687 14.4835L16.9866 14.4806L16.9769 14.5001L16.9466 14.5665C16.909 14.6518 16.8651 14.7644 16.8079 14.92L16.4798 15.8321L16.4486 15.9181C16.3098 16.2921 16.1767 16.6259 16.0335 16.9503C15.3602 18.4758 14.5563 19.5854 13.4847 20.2081C13.4568 20.2243 13.4265 20.234 13.3968 20.2413C13.3784 20.2528 13.3592 20.2638 13.3382 20.2716L8.80403 21.963C8.48907 22.077 8.14479 21.9209 8.01301 21.6134L7.99543 21.5704L3.87825 9.74035C3.47518 8.5823 4.04236 7.31194 5.15168 6.85656L5.22004 6.82922L6.38215 6.39465L6.00032 5.32336C5.93704 5.14605 6.02668 4.95043 6.20051 4.88586C6.36281 4.82561 6.54104 4.90124 6.61555 5.05578L6.6302 5.08996L7.01204 6.16027L13.9954 3.55382L13.6048 2.4591C13.5415 2.28176 13.6311 2.08517 13.805 2.02062ZM18.1097 4.48547C17.83 3.65872 16.954 3.21766 16.1448 3.48254L16.0843 3.50304L14.8538 3.96105L15.2396 5.04113C15.3029 5.21843 15.2132 5.41502 15.0394 5.47961C14.8771 5.53983 14.6989 5.46414 14.6243 5.30968L14.6097 5.2755L14.2239 4.1964L7.24055 6.80187L7.63508 7.90636C7.69826 8.08361 7.60862 8.27927 7.43489 8.34386C7.27259 8.40411 7.09436 8.32848 7.01985 8.17394L7.0052 8.13976L6.61164 7.03722L5.45051 7.47082C4.6623 7.76464 4.24316 8.63672 4.4886 9.44738L4.51008 9.51183L8.61457 21.3058L13.1077 19.629C13.1199 19.6245 13.1326 19.6223 13.1448 19.6193C13.1474 19.6177 13.1501 19.6159 13.1527 19.6144C14.0753 19.0783 14.8017 18.0757 15.4222 16.67C15.5592 16.3596 15.6873 16.0379 15.8216 15.6759L15.9495 15.3253L16.1292 14.8224L16.2376 14.5216C16.4782 13.8735 16.566 13.7746 16.8792 13.7921C19.0149 13.9117 20.3567 13.1065 20.3216 11.9982C20.321 11.9797 20.3223 11.9612 20.3245 11.9435C20.3016 11.2788 20.1823 10.6067 19.9573 9.9464L19.9202 9.83996L18.1097 4.48547Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1949 15.1869C10.6214 15.028 11.0935 15.2513 11.2495 15.686C11.4058 16.1211 11.1858 16.603 10.7593 16.7621C10.3331 16.9211 9.86064 16.6978 9.70462 16.2631C9.54863 15.8282 9.76836 15.346 10.1949 15.1869ZM13.9527 13.7846C14.3794 13.6254 14.8514 13.8496 15.0074 14.2846C15.1633 14.7197 14.9444 15.2007 14.5181 15.3598C14.0916 15.5189 13.6195 15.2956 13.4634 14.8608C13.3072 14.4259 13.5263 13.9439 13.9527 13.7846ZM8.96048 11.9067C9.38705 11.7476 9.85888 11.9713 10.0152 12.4057C10.1712 12.8408 9.95217 13.3225 9.52591 13.4819C9.09955 13.6409 8.62736 13.4175 8.47122 12.9828C8.31523 12.5479 8.53398 12.0658 8.96048 11.9067ZM12.645 10.5326C13.0717 10.3736 13.5438 10.5968 13.6997 11.0317C13.8558 11.4669 13.636 11.9488 13.2095 12.1078C12.7831 12.2668 12.3108 12.0427 12.1548 11.6078C11.9989 11.1729 12.2186 10.6917 12.645 10.5326ZM16.3286 9.15765C16.7553 8.99853 17.2273 9.22271 17.3833 9.65765C17.5392 10.0927 17.3195 10.5738 16.8931 10.7328C16.4667 10.8917 15.9944 10.6687 15.8384 10.2338C15.6824 9.79886 15.9022 9.31696 16.3286 9.15765Z" fill="#041C1E"/>
<path d="M9.01498 17.4377C9.01498 19.2138 7.53345 20.6536 5.70562 20.6536C3.87802 20.6536 2.39648 19.2138 2.39648 17.4377C2.39648 15.6617 3.87802 14.2217 5.70562 14.2217C7.53345 14.2217 9.01498 15.6617 9.01498 17.4377Z" fill="#56F58E"/>
<path d="M5.55122 17.4624L4.39404 17.4303L7.01913 15.084L5.87026 17.1809L7.02744 17.213L4.56454 19.7544L5.55122 17.4624Z" fill="#041C1E"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.13132 9.44103C3.78734 8.49178 4.26758 7.4385 5.20098 7.09502L16.2114 3.04332C17.1626 2.69328 18.2089 3.21727 18.5189 4.19894L20.7249 11.1853C20.7622 11.3035 20.786 11.4255 20.7905 11.5496C20.8729 13.8348 19.7958 17.1791 16.4626 18.8049L13.4843 20.0198L8.84373 21.5974C8.69123 21.6492 8.52591 21.5683 8.47018 21.4145L4.13132 9.44103Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0822 5.89798L18.5802 4.4037C18.2325 3.37534 17.125 2.83821 16.1234 3.2118L5.48928 7.17956C4.50755 7.54552 3.99815 8.65027 4.34798 9.65535L4.85112 11.0604L19.0822 5.89798Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.805 2.02062C13.9672 1.96053 14.1455 2.03616 14.22 2.19054L14.2347 2.22472L14.6253 3.31945L15.8538 2.86144C17.0088 2.43066 18.2837 3.0299 18.7181 4.19347L18.7425 4.26281L20.5521 9.61144C21.9441 13.4928 19.9874 17.7902 16.182 19.2101C16.0082 19.2749 15.8149 19.1842 15.7513 19.007C15.6877 18.8297 15.7777 18.6333 15.9515 18.5685C18.1612 17.7441 19.6829 15.8515 20.1644 13.671C19.4714 14.2168 18.3856 14.52 17.0687 14.4835L16.9866 14.4806L16.9769 14.5001L16.9466 14.5665C16.909 14.6518 16.8651 14.7644 16.8079 14.92L16.4798 15.8321L16.4486 15.9181C16.3098 16.2921 16.1767 16.6259 16.0335 16.9503C15.3602 18.4758 14.5563 19.5854 13.4847 20.2081C13.4568 20.2243 13.4265 20.234 13.3968 20.2413C13.3784 20.2528 13.3592 20.2638 13.3382 20.2716L8.80403 21.963C8.48907 22.077 8.14479 21.9209 8.01301 21.6134L7.99543 21.5704L3.87825 9.74035C3.47518 8.5823 4.04236 7.31194 5.15168 6.85656L5.22004 6.82922L6.38215 6.39465L6.00032 5.32336C5.93704 5.14605 6.02668 4.95043 6.20051 4.88586C6.36281 4.82561 6.54104 4.90124 6.61555 5.05578L6.6302 5.08996L7.01204 6.16027L13.9954 3.55382L13.6048 2.4591C13.5415 2.28176 13.6311 2.08517 13.805 2.02062ZM18.1097 4.48547C17.83 3.65872 16.954 3.21766 16.1448 3.48254L16.0843 3.50304L14.8538 3.96105L15.2396 5.04113C15.3029 5.21843 15.2132 5.41502 15.0394 5.47961C14.8771 5.53983 14.6989 5.46414 14.6243 5.30968L14.6097 5.2755L14.2239 4.1964L7.24055 6.80187L7.63508 7.90636C7.69826 8.08361 7.60862 8.27927 7.43489 8.34386C7.27259 8.40411 7.09436 8.32848 7.01985 8.17394L7.0052 8.13976L6.61164 7.03722L5.45051 7.47082C4.6623 7.76464 4.24316 8.63672 4.4886 9.44738L4.51008 9.51183L8.61457 21.3058L13.1077 19.629C13.1199 19.6245 13.1326 19.6223 13.1448 19.6193C13.1474 19.6177 13.1501 19.6159 13.1527 19.6144C14.0753 19.0783 14.8017 18.0757 15.4222 16.67C15.5592 16.3596 15.6873 16.0379 15.8216 15.6759L15.9495 15.3253L16.1292 14.8224L16.2376 14.5216C16.4782 13.8735 16.566 13.7746 16.8792 13.7921C19.0149 13.9117 20.3567 13.1065 20.3216 11.9982C20.321 11.9797 20.3223 11.9612 20.3245 11.9435C20.3016 11.2788 20.1823 10.6067 19.9573 9.9464L19.9202 9.83996L18.1097 4.48547Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1942 15.1879C10.6209 15.0288 11.0929 15.252 11.2489 15.687C11.4052 16.122 11.186 16.603 10.7596 16.7622C10.3332 16.9215 9.86094 16.6981 9.70495 16.2631C9.54901 15.8283 9.76796 15.3472 10.1942 15.1879ZM13.953 13.7856C14.3797 13.6266 14.8517 13.8498 15.0077 14.2846C15.1638 14.7198 14.9439 15.2017 14.5174 15.3608C14.0911 15.5195 13.6187 15.2956 13.4628 14.8608C13.3068 14.426 13.5267 13.9449 13.953 13.7856ZM8.96081 11.9077C9.38746 11.7487 9.85939 11.9721 10.0155 12.4067C10.1714 12.8418 9.9516 13.3236 9.52526 13.4829C9.09887 13.6417 8.62652 13.4177 8.47057 12.9829C8.31467 12.5479 8.53435 12.0667 8.96081 11.9077ZM12.6444 10.5327C13.0711 10.3735 13.5431 10.5977 13.6991 11.0327C13.8549 11.4677 13.636 11.9487 13.2098 12.1079C12.7835 12.2669 12.3113 12.0435 12.1551 11.6088C11.9992 11.1739 12.218 10.6918 12.6444 10.5327ZM16.328 9.15767C16.7547 8.9985 17.2277 9.22271 17.3837 9.65767C17.5396 10.0928 17.3199 10.5748 16.8934 10.7338C16.467 10.8928 15.9947 10.6688 15.8387 10.2338C15.6829 9.79902 15.9018 9.31705 16.328 9.15767Z" fill="#041C1E"/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.191 2.6748H2.9834L5.16052 5.25409L5.17136 5.25273L5.20789 5.25075H16.6459C16.8311 5.25075 16.9812 5.40185 16.9812 5.58824C16.9812 5.76221 16.8505 5.90543 16.6825 5.92376L16.6459 5.92574L5.72705 5.92571L9.46373 10.3532C9.52071 10.4238 9.55918 10.5071 9.5765 10.5953L9.58435 10.6488L9.58699 10.7031L9.5867 16.8006L14.5877 21.1796L14.5877 10.7031C14.5877 10.6072 14.6124 10.5133 14.6603 10.4288L14.6862 10.3873L14.716 10.3476L21.191 2.6748Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9345 21.6865L9.32497 17.0196L9.11328 15.0061H15.2237V18.9692L14.9345 21.6865Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.4497 2C21.8985 2.00004 22.1528 2.50703 21.8989 2.87402L21.8725 2.90918L15.2583 10.7471V21.4443C15.2583 21.89 14.7694 22.1423 14.4145 21.915L14.3794 21.8906L14.3462 21.8633L9.10594 17.2744C8.99878 17.181 8.93194 17.0498 8.91844 16.9092L8.91551 16.8555V10.7471L2.29637 2.90332C2.02325 2.56421 2.22992 2.06932 2.63719 2.00684L2.68015 2.00195L2.7241 2H21.4497ZM5.15965 5.25391L5.17039 5.25293L5.2075 5.25098H16.645C16.8301 5.25098 16.9808 5.40165 16.9809 5.58789C16.9809 5.76181 16.85 5.90544 16.6821 5.92383L16.645 5.92578H5.72605L9.46336 10.3535C9.52022 10.424 9.55835 10.5076 9.57566 10.5957L9.58347 10.6484L9.5864 10.7031V16.8008L14.5874 21.1797V10.7031C14.5874 10.6072 14.6117 10.5133 14.6596 10.4287L14.686 10.3877L14.7153 10.3477L21.1909 2.6748H2.98289L5.15965 5.25391Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.1792 14.9781C9.1792 14.7917 9.32932 14.6406 9.51451 14.6406H14.8339C15.0191 14.6406 15.1692 14.7917 15.1692 14.9781C15.1692 15.1645 15.0191 15.3156 14.8339 15.3156H9.51451C9.32932 15.3156 9.1792 15.1645 9.1792 14.9781Z" fill="#041C1E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,4 @@
<svg width="46" height="47" viewBox="0 0 46 47" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 10.3376C0 4.89366 4.41319 0.480469 9.85714 0.480469H36.1428C41.5868 0.480469 46 4.89366 46 10.3376V36.6233C46 42.0673 41.5868 46.4805 36.1428 46.4805H9.85714C4.41319 46.4805 0 42.0673 0 36.6233V10.3376Z" fill="#041C1E"/>
<path d="M41.0713 22.8521C36.1862 23.0545 36.8261 22.9993 22.6523 23.0729C22.5192 23.0729 22.2531 23.1281 22.2531 23.3918C22.2531 23.6188 22.2658 25.2872 22.2721 25.7717C22.2721 25.8515 22.2404 25.9312 22.4432 25.9312C22.6143 25.9312 29.4382 25.8638 34.1966 25.4773C33.3539 30.5684 28.7983 34.4572 23.3049 34.4572C17.8115 34.4572 12.92 30.2739 12.3308 24.913C12.3308 24.8824 12.3245 24.8578 12.3245 24.8271C12.2864 24.4837 12.2674 24.1279 12.2674 23.7783C12.2674 23.447 12.2864 23.1158 12.3181 22.7907C12.4829 21.196 13.0278 19.6012 13.9212 18.1781C13.9338 18.1597 13.9465 18.1413 13.9592 18.1229C14.0922 17.9144 14.238 17.712 14.3837 17.5157C14.4851 17.3807 14.5864 17.2397 14.7005 17.1109C14.7575 17.0372 14.8209 16.9636 14.8842 16.89C16.6773 14.8414 19.2498 13.449 22.1581 13.1607C22.5319 13.1239 22.9121 13.1055 23.3049 13.1055C23.6724 13.1055 24.0335 13.13 24.3884 13.1668C24.4897 13.1791 24.5848 13.1852 24.6862 13.1975C24.9966 13.2343 25.3008 13.2834 25.5986 13.3447C25.7633 13.3815 25.9154 13.4183 26.0738 13.4613C26.2005 13.4919 26.3335 13.5287 26.4603 13.5655C26.8024 13.6637 27.1319 13.7802 27.4487 13.9029C27.4867 13.9152 27.5184 13.9336 27.5564 13.9458C27.8352 14.0562 28.1013 14.1789 28.3548 14.3077C28.5765 14.4181 28.8046 14.5285 29.02 14.6573C29.0264 14.6573 29.0391 14.6635 29.0454 14.6696C30.0465 15.2585 30.9462 16.0006 31.7002 16.8594L34.7035 14.6512C31.8333 11.0139 27.3283 8.69531 22.2658 8.69531C14.7892 8.69531 8.53548 13.7618 6.95779 20.5396C6.94512 20.5948 6.93245 20.65 6.91977 20.7114C6.88809 20.8586 6.86275 21.0058 6.83107 21.153C6.80572 21.2757 6.78038 21.3984 6.76137 21.521C6.74236 21.6253 6.72969 21.7357 6.71702 21.84C6.69801 21.9933 6.67267 22.1467 6.65366 22.3062C6.64099 22.4227 6.63465 22.5392 6.62198 22.6619C6.60931 22.803 6.59663 22.9441 6.5903 23.0913C6.57763 23.355 6.57129 23.6249 6.57129 23.8948C6.57129 32.2858 13.598 39.0882 22.2658 39.0882C30.4393 39.0882 37.1429 33.0403 37.8906 25.324C37.9159 25.2258 37.9413 25.1277 37.9603 25.0296C40.8242 24.8517 40.9446 25.0296 40.9446 25.0296" fill="#FFFDF7"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
<text x="16" y="22" text-anchor="middle" fill="#22c55e" font-size="20" font-family="sans-serif" font-weight="bold">G</text>
</svg>

After

Width:  |  Height:  |  Size: 206 B

View File

@@ -0,0 +1,5 @@
<svg width="46" height="46" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="give-title">
<title id="give-title">GiveWP logo</title>
<path d="M0 9.85714C0 4.41319 4.41319 0 9.85714 0H36.1429C41.5868 0 46 4.41319 46 9.85714V36.1429C46 41.5868 41.5868 46 36.1429 46H9.85714C4.41319 46 0 41.5868 0 36.1429V9.85714Z" fill="#3D7BF5"/>
<path d="M41.0713 22.3716C36.1862 22.574 36.8261 22.5188 22.6523 22.5924C22.5192 22.5924 22.2531 22.6476 22.2531 22.9114C22.2531 23.1383 22.2658 24.8067 22.2721 25.2913C22.2721 25.371 22.2404 25.4508 22.4432 25.4508C22.6143 25.4508 29.4382 25.3833 34.1966 24.9969C33.3539 30.0879 28.7983 33.9767 23.3049 33.9767C17.8115 33.9767 12.92 29.7935 12.3308 24.4326C12.3308 24.4019 12.3245 24.3773 12.3245 24.3467C12.2864 24.0032 12.2674 23.6474 12.2674 23.2978C12.2674 22.9666 12.2864 22.6354 12.3181 22.3103C12.4829 20.7155 13.0278 19.1207 13.9212 17.6977C13.9338 17.6793 13.9465 17.6609 13.9592 17.6425C14.0922 17.4339 14.238 17.2315 14.3837 17.0352C14.4851 16.9003 14.5864 16.7592 14.7005 16.6304C14.7575 16.5568 14.8209 16.4832 14.8842 16.4096C16.6773 14.3609 19.2498 12.9685 22.1581 12.6802C22.5319 12.6434 22.9121 12.625 23.3049 12.625C23.6724 12.625 24.0335 12.6496 24.3884 12.6864C24.4897 12.6986 24.5848 12.7048 24.6862 12.717C24.9966 12.7538 25.3008 12.8029 25.5986 12.8642C25.7633 12.901 25.9154 12.9379 26.0738 12.9808C26.2005 13.0115 26.3335 13.0483 26.4603 13.0851C26.8024 13.1832 27.1319 13.2997 27.4487 13.4224C27.4867 13.4347 27.5184 13.4531 27.5564 13.4654C27.8352 13.5758 28.1013 13.6984 28.3548 13.8272C28.5765 13.9377 28.8046 14.0481 29.02 14.1769C29.0264 14.1769 29.0391 14.183 29.0454 14.1891C30.0465 14.778 30.9462 15.5202 31.7002 16.3789L34.7035 14.1707C31.8333 10.5334 27.3283 8.21484 22.2658 8.21484C14.7892 8.21484 8.53548 13.2813 6.95779 20.0592C6.94512 20.1144 6.93245 20.1696 6.91977 20.2309C6.88809 20.3781 6.86275 20.5253 6.83107 20.6725C6.80572 20.7952 6.78038 20.9179 6.76137 21.0406C6.74236 21.1448 6.72969 21.2553 6.71702 21.3595C6.69801 21.5129 6.67267 21.6662 6.65366 21.8257C6.64099 21.9422 6.63465 22.0588 6.62198 22.1815C6.60931 22.3225 6.59663 22.4636 6.5903 22.6108C6.57763 22.8746 6.57129 23.1445 6.57129 23.4143C6.57129 31.8053 13.598 38.6077 22.2658 38.6077C30.4393 38.6077 37.1429 32.5598 37.8906 24.8435C37.9159 24.7454 37.9413 24.6472 37.9603 24.5491C40.8242 24.3712 40.9446 24.5491 40.9446 24.5491" fill="#FEFBF2"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,8 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 5C21.6582 5 26.3192 9.27755 26.9297 14.7734C24.69 15.6098 21.3698 16.5215 17.4971 16.5215C17.4971 16.5215 17.5104 16.6586 17.5625 16.7822C17.607 16.8775 17.6952 16.9601 17.6992 16.9639C19.6454 17.2047 22.3986 16.9378 24.501 16.6514C23.297 19.2352 21.1492 20.9666 19.1641 20.9668C15.467 20.9668 12.6025 16.4756 12.6025 16.4756C13.7481 15.4601 15.6424 12.1603 18.376 12.1602C21.0993 12.1602 22.286 13.6459 22.2949 13.6572L22.6006 13.1758C22.5909 13.1422 21.3106 8.71094 17.7119 8.71094C14.0996 8.71127 10.266 14.6272 8.02051 15.9941C8.02051 15.9941 11.1061 23.3037 17.8428 23.3037C23.5052 23.3035 24.9236 17.8945 25.1904 16.5537C25.9455 16.4431 26.5707 16.3323 26.9873 16.2607C26.9895 16.2557 26.991 16.2503 26.9932 16.2451C26.8626 22.2047 21.9909 27 16 27C9.92724 27 5.00002 22.0727 5 16C5 9.92724 9.92722 5 16 5Z" fill="black"/>
<mask id="mask0_65_24395" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="5" y="5" width="22" height="22">
<path d="M27 16C27 9.9272 22.0728 5 16 5C9.92722 5 5 9.9272 5 16C5 22.0727 9.92722 27 16 27C22.0728 27 27 22.0727 27 16Z" fill="black"/>
</mask>
<g mask="url(#mask0_65_24395)">
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,8 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 4.99951C21.6578 4.99951 26.3187 9.27655 26.9297 14.772C24.69 15.6084 21.37 16.521 17.4971 16.521C17.4977 16.5276 17.5118 16.6602 17.5625 16.7808C17.6081 16.8784 17.6992 16.9634 17.6992 16.9634C19.6454 17.2042 22.3987 16.9373 24.501 16.6509C23.297 19.2347 21.1491 20.966 19.1641 20.9663C15.467 20.9663 12.6025 16.4751 12.6025 16.4751C13.7481 15.4597 15.6423 12.1597 18.376 12.1597C21.0891 12.1597 22.2762 13.6345 22.2939 13.6567L22.6006 13.1753C22.6006 13.1753 21.3243 8.71045 17.7119 8.71045C14.0996 8.71064 10.266 14.6268 8.02051 15.9937C8.0381 16.0352 11.1247 23.3029 17.8418 23.3032C23.5045 23.3032 24.9235 17.8941 25.1904 16.5532C25.9455 16.4426 26.5707 16.3319 26.9873 16.2603C26.9896 16.2549 26.9908 16.2491 26.9932 16.2437C26.8631 22.2037 21.9912 26.9995 16 26.9995C9.92724 26.9995 5.00002 22.0722 5 15.9995C5 9.92676 9.92722 4.99951 16 4.99951Z" fill="black"/>
<mask id="mask0_620_9148" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="5" y="4" width="22" height="23">
<path d="M27 15.9995C27 9.92671 22.0728 4.99951 16 4.99951C9.92722 4.99951 5 9.92671 5 15.9995C5 22.0722 9.92722 26.9995 16 26.9995C22.0728 26.9995 27 22.0722 27 15.9995Z" fill="black"/>
</mask>
<g mask="url(#mask0_620_9148)">
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,4 @@
<svg width="46" height="47" viewBox="0 0 46 47" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 10.3376C0 4.89366 4.41299 0.480469 9.8567 0.480469H36.1412C41.5849 0.480469 45.9979 4.89366 45.9979 10.3376V36.6233C45.9979 42.0673 41.5849 46.4805 36.1412 46.4805H9.8567C4.413 46.4805 0 42.0673 0 36.6233V10.3376Z" fill="#041C1E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.8079 36.5375H23.4089C22.6078 36.5375 21.9575 35.8865 21.9575 35.0848C21.9575 34.2833 22.6078 33.6323 23.4089 33.6323H28.2421C28.9963 33.5806 29.5929 32.9512 29.5929 32.1835C29.5929 31.3818 28.9425 30.731 28.1414 30.731H24.6078V30.7308H20.5785C19.7785 30.7308 19.129 30.0809 19.129 29.2804C19.129 28.4798 19.7785 27.8297 20.5785 27.8297H21.4914L21.4895 27.8279H22.8468C23.6464 27.8279 24.2957 27.1783 24.2957 26.3781C24.2957 25.578 23.6464 24.9284 22.8468 24.9284H19.433V24.9271H12.2744C11.4748 24.9271 10.8255 24.2774 10.8255 23.4771C10.8255 22.6769 11.4748 22.027 12.2744 22.027H19.433V22.0231H22.236C23.0349 22.0231 23.6835 21.374 23.6835 20.5745C23.6835 19.7749 23.0349 19.1258 22.236 19.1258H21.5971L21.5972 19.1256H18.6205C17.8205 19.1256 17.1711 18.4756 17.1711 17.6752C17.1711 16.8745 17.8205 16.2245 18.6205 16.2245H25.494C25.5284 16.2245 25.5623 16.2257 25.5959 16.228H28.0529C28.8538 16.228 29.5042 15.5772 29.5042 14.7755C29.5042 13.9739 28.8538 13.3232 28.0529 13.3232H24.4412C23.6895 13.2686 23.0959 12.6404 23.0959 11.8745C23.0959 11.0727 23.7462 10.4219 24.5474 10.4219H30.2366C30.2428 10.4219 30.249 10.4219 30.2557 10.422H36.9637L25.21 23.267L39.4266 36.5388H30.8464L30.8443 36.5369C30.8323 36.5374 30.8202 36.5375 30.8079 36.5375ZM16.8813 33.6323C17.6796 33.6323 18.3277 34.2809 18.3277 35.0797C18.3277 35.8786 17.6796 36.527 16.8813 36.527H11.5736C10.7754 36.527 10.1273 35.8786 10.1273 35.0797C10.1273 34.2809 10.7754 33.6323 11.5736 33.6323H16.8813ZM14.7801 27.8297C15.5807 27.8297 16.2307 28.4802 16.2307 29.2815C16.2307 30.0826 15.5807 30.7332 14.7801 30.7332C13.9793 30.7332 13.3293 30.0826 13.3293 29.2815C13.3293 28.4802 13.9793 27.8297 14.7801 27.8297ZM7.20018 22.027C8.00095 22.027 8.65097 22.6775 8.65097 23.4789C8.65097 24.2801 8.00095 24.9305 7.20018 24.9305C6.39953 24.9305 5.74951 24.2801 5.74951 23.4789C5.74951 22.6775 6.39953 22.027 7.20018 22.027ZM12.8222 16.2245C13.6228 16.2245 14.273 16.875 14.273 17.6763C14.273 18.4775 13.6228 19.1279 12.8222 19.1279C12.0216 19.1279 11.3714 18.4775 11.3714 17.6763C11.3714 16.875 12.0216 16.2245 12.8222 16.2245ZM18.0248 10.4219C18.8231 10.4219 19.4711 11.0704 19.4711 11.8692C19.4711 12.6681 18.8231 13.3167 18.0248 13.3167H14.6698C13.8716 13.3167 13.2235 12.6681 13.2235 11.8692C13.2235 11.0704 13.8715 10.4219 14.6698 10.4219H18.0248Z" fill="#FFFDF7"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.8579 25.0984H16.585C16.0141 25.0984 15.5506 24.6348 15.5506 24.0638C15.5506 23.493 16.0141 23.0294 16.585 23.0294H20.0294C20.5669 22.9925 20.992 22.5443 20.992 21.9976C20.992 21.4266 20.5286 20.9632 19.9576 20.9632H17.4394V20.9631H14.5679C13.9978 20.9631 13.5349 20.5002 13.5349 19.9302C13.5349 19.36 13.9978 18.897 14.5679 18.897H15.2185L15.2171 18.8958H16.1844C16.7543 18.8958 17.2169 18.4332 17.2169 17.8633C17.2169 17.2935 16.7543 16.8309 16.1844 16.8309H13.7516V16.8299H8.64992C8.08009 16.8299 7.61734 16.3672 7.61734 15.7973C7.61734 15.2274 8.08009 14.7647 8.64992 14.7647H13.7516V14.7618H15.7491C16.3184 14.7618 16.7807 14.2996 16.7807 13.7302C16.7807 13.1608 16.3184 12.6985 15.7491 12.6985H15.2938L15.2939 12.6984H13.1726C12.6024 12.6984 12.1396 12.2355 12.1396 11.6654C12.1396 11.0953 12.6024 10.6323 13.1726 10.6323H18.071C18.0954 10.6323 18.1195 10.6332 18.1435 10.6348H19.8945C20.4653 10.6348 20.9287 10.1714 20.9287 9.60051C20.9287 9.02962 20.4653 8.56618 19.8945 8.56618H17.3206C16.785 8.52733 16.3619 8.07988 16.3619 7.53444C16.3619 6.96355 16.8254 6.5 17.3964 6.5H21.4507C21.4552 6.5 21.4596 6.5 21.4644 6.50011H26.2449L17.8686 15.6477L28 25.0993H21.8853L21.8839 25.0979C21.8753 25.0982 21.8667 25.0984 21.8579 25.0984ZM11.9331 23.0294C12.502 23.0294 12.9639 23.4913 12.9639 24.0601C12.9639 24.6291 12.502 25.0908 11.9331 25.0908H8.15052C7.58169 25.0908 7.11981 24.6291 7.11981 24.0601C7.11981 23.4913 7.58169 23.0294 8.15052 23.0294H11.9331ZM10.4356 18.897C11.0062 18.897 11.4694 19.3603 11.4694 19.931C11.4694 20.5014 11.0062 20.9648 10.4356 20.9648C9.86494 20.9648 9.40169 20.5014 9.40169 19.931C9.40169 19.3603 9.86494 18.897 10.4356 18.897ZM5.0338 14.7647C5.60447 14.7647 6.06771 15.2279 6.06771 15.7986C6.06771 16.3692 5.60447 16.8324 5.0338 16.8324C4.46325 16.8324 4 16.3692 4 15.7986C4 15.2279 4.46325 14.7647 5.0338 14.7647ZM9.04031 10.6323C9.61089 10.6323 10.0742 11.0956 10.0742 11.6662C10.0742 12.2368 9.61089 12.7001 9.04031 12.7001C8.46975 12.7001 8.0064 12.2368 8.0064 11.6662C8.0064 11.0956 8.46975 10.6323 9.04031 10.6323ZM12.748 6.5C13.3169 6.5 13.7787 6.96188 13.7787 7.53071C13.7787 8.09963 13.3169 8.56151 12.748 8.56151H10.357C9.78819 8.56151 9.32628 8.09963 9.32628 7.53071C9.32628 6.96188 9.78808 6.5 10.357 6.5H12.748Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg width="46" height="46" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="kadence-title">
<title id="kadence-title">Kadence logo</title>
<path d="M0 9.85714C0 4.41319 4.41299 0 9.8567 0H36.1412C41.5849 0 46 4.41319 46 9.85714V36.1429C46 41.5868 41.5849 46 36.1412 46H9.8567C4.41299 46 0 41.5868 0 36.1429V9.85714Z" fill="#3D7BF5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.8082 36.1419H23.4092C22.608 36.1419 21.9577 35.4867 21.9577 34.6798C21.9577 33.8731 22.608 33.2179 23.4092 33.2179H28.2423C28.9966 33.1658 29.5932 32.5324 29.5932 31.7597C29.5932 30.9528 28.9428 30.2978 28.1416 30.2978H24.608V30.2976H20.5787C19.7787 30.2976 19.1292 29.6435 19.1292 28.8378C19.1292 28.0321 19.7787 27.3778 20.5787 27.3778H21.4917L21.4898 27.376H22.8471C23.6467 27.376 24.2959 26.7222 24.2959 25.9168C24.2959 25.1115 23.6467 24.4577 22.8471 24.4577H19.4333V24.4564H12.2746C11.475 24.4564 10.8257 23.8025 10.8257 22.997C10.8257 22.1916 11.475 21.5376 12.2746 21.5376H19.4333V21.5336H22.2363C23.0351 21.5336 23.6837 20.8803 23.6837 20.0756C23.6837 19.2709 23.0351 18.6177 22.2363 18.6177H21.5973L21.5975 18.6174H18.6208C17.8208 18.6174 17.1714 17.9632 17.1714 17.1576C17.1714 16.3517 17.8208 15.6976 18.6208 15.6976H25.4943C25.5286 15.6976 25.5625 15.6987 25.5961 15.7011H28.0531C28.854 15.7011 29.5044 15.0461 29.5044 14.2392C29.5044 13.4324 28.854 12.7775 28.0531 12.7775H24.4414C23.6897 12.7226 23.0962 12.0903 23.0962 11.3194C23.0962 10.5125 23.7465 9.85742 24.5476 9.85742H30.2368C30.2431 9.85742 30.2492 9.85742 30.256 9.85755H36.9639L25.2103 22.7856L39.4268 36.1431H30.8466L30.8446 36.1412C30.8326 36.1417 30.8204 36.1419 30.8082 36.1419ZM16.8815 33.2179C17.6798 33.2179 18.3279 33.8707 18.3279 34.6746C18.3279 35.4787 17.6798 36.1313 16.8815 36.1313H11.5739C10.7757 36.1313 10.1276 35.4787 10.1276 34.6746C10.1276 33.8707 10.7757 33.2179 11.5739 33.2179H16.8815ZM14.7803 27.3778C15.5809 27.3778 16.231 28.0324 16.231 28.839C16.231 29.6452 15.5809 30.3 14.7803 30.3C13.9795 30.3 13.3295 29.6452 13.3295 28.839C13.3295 28.0324 13.9795 27.3778 14.7803 27.3778ZM7.20042 21.5376C8.0012 21.5376 8.65122 22.1923 8.65122 22.9988C8.65122 23.8052 8.0012 24.4599 7.20042 24.4599C6.39978 24.4599 5.74976 23.8052 5.74976 22.9988C5.74976 22.1923 6.39978 21.5376 7.20042 21.5376ZM12.8224 15.6976C13.6231 15.6976 14.2732 16.3523 14.2732 17.1588C14.2732 17.9652 13.6231 18.6197 12.8224 18.6197C12.0218 18.6197 11.3716 17.9652 11.3716 17.1588C11.3716 16.3523 12.0218 15.6976 12.8224 15.6976ZM18.025 9.85742C18.8234 9.85742 19.4714 10.5102 19.4714 11.3141C19.4714 12.1182 18.8234 12.7709 18.025 12.7709H14.67C13.8718 12.7709 13.2237 12.1182 13.2237 11.3141C13.2237 10.5102 13.8717 9.85742 14.67 9.85742H18.025Z" fill="#FEFBF2"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,6 @@
<svg width="46" height="46" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 9.85714C0 4.41319 4.41301 0 9.85673 0H36.1413C41.5851 0 45.9981 4.41319 45.9981 9.85714V36.1429C45.9981 41.5868 41.5851 46 36.1413 46H9.85673C4.41301 46 0 41.5868 0 36.1429V9.85714Z" fill="#041C1E"/>
<path d="M22.6704 16.1001C22.2818 16.0992 21.8969 16.1791 21.5378 16.3352C21.1786 16.4913 20.8523 16.7205 20.5776 17.0097C20.3028 17.2989 20.085 17.6424 19.9367 18.0204C19.7884 18.3984 19.7125 18.8036 19.7134 19.2125V37.2106C19.7174 37.3618 19.7763 37.5056 19.8779 37.6125C19.9795 37.7195 20.1162 37.7814 20.2598 37.7857C21.6826 37.783 23.0464 37.1869 24.0524 36.128C25.0585 35.069 25.6248 33.6335 25.6274 32.1359V19.2125C25.6282 18.8036 25.5523 18.3984 25.404 18.0204C25.2557 17.6424 25.0379 17.2989 24.7632 17.0097C24.4884 16.7205 24.1621 16.4913 23.803 16.3352C23.4438 16.1791 23.0589 16.0992 22.6704 16.1001Z" fill="#FFFDF7"/>
<path d="M12.8139 23.9858C12.4254 23.9849 12.0405 24.0683 11.6813 24.2311C11.3222 24.394 10.9959 24.6331 10.7211 24.9348C10.4464 25.2365 10.2286 25.5948 10.0803 25.9892C9.932 26.3836 9.85609 26.8062 9.85694 27.2329V37.1858C9.86099 37.3435 9.91986 37.4935 10.0215 37.6051C10.1231 37.7167 10.2597 37.7813 10.4033 37.7858C11.8261 37.783 13.1899 37.1611 14.196 36.0563C15.202 34.9516 15.7684 33.454 15.7709 31.8917V27.2329C15.7718 26.8062 15.6959 26.3836 15.5476 25.9892C15.3993 25.5948 15.1815 25.2365 14.9067 24.9348C14.632 24.6331 14.3057 24.394 13.9465 24.2311C13.5874 24.0683 13.2025 23.9849 12.8139 23.9858Z" fill="#FFFDF7"/>
<path d="M32.5273 8.21485C32.1388 8.21395 31.7539 8.29441 31.3947 8.4516C31.0356 8.6088 30.7093 8.83963 30.4345 9.13086C30.1598 9.42208 29.942 9.76796 29.7937 10.1486C29.6454 10.5293 29.5695 10.9373 29.5703 11.3491V37.207C29.5744 37.3592 29.6332 37.504 29.7348 37.6117C29.8364 37.7194 29.9731 37.7818 30.1167 37.7861C31.5395 37.7834 32.9033 37.1831 33.9094 36.1168C34.9154 35.0504 35.4818 33.6048 35.4843 32.0967V11.3491C35.4851 10.9373 35.4092 10.5293 35.2609 10.1486C35.1126 9.76796 34.8949 9.42208 34.6201 9.13086C34.3454 8.83963 34.019 8.6088 33.6599 8.4516C33.3008 8.29441 32.9159 8.21395 32.5273 8.21485Z" fill="#FFFDF7"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9997 9.82911C15.6967 9.82841 15.3965 9.8907 15.1163 10.0124C14.8362 10.1341 14.5817 10.3128 14.3674 10.5383C14.1531 10.7638 13.9833 11.0315 13.8676 11.3263C13.7519 11.621 13.6927 11.9369 13.6934 12.2557V26.2878C13.6965 26.4057 13.7424 26.5178 13.8217 26.6012C13.9009 26.6846 14.0075 26.7329 14.1195 26.7362C15.2293 26.7341 16.293 26.2694 17.0777 25.4438C17.8624 24.6182 18.3041 23.499 18.3061 22.3314V12.2557C18.3068 11.9369 18.2476 11.621 18.1319 11.3263C18.0162 11.0315 17.8464 10.7638 17.6321 10.5383C17.4178 10.3128 17.1633 10.1341 16.8831 10.0124C16.603 9.8907 16.3028 9.82841 15.9997 9.82911Z" fill="#1E1E1E"/>
<path d="M9.30638 16.6865C9.00332 16.6858 8.70311 16.7481 8.42299 16.8698C8.14287 16.9915 7.88835 17.1702 7.67405 17.3957C7.45976 17.6212 7.28989 17.889 7.17422 18.1837C7.05855 18.4784 6.99934 18.7943 7.00001 19.1131V26.5512C7.00316 26.6691 7.04908 26.7812 7.12833 26.8646C7.20758 26.948 7.31415 26.9963 7.42618 26.9996C8.53592 26.9975 9.59964 26.5328 10.3843 25.7072C11.1691 24.8816 11.6108 23.7624 11.6128 22.5948V19.1131C11.6134 18.7943 11.5542 18.4784 11.4385 18.1837C11.3229 17.889 11.153 17.6212 10.9387 17.3957C10.7244 17.1702 10.4699 16.9915 10.1898 16.8698C9.90966 16.7481 9.60945 16.6858 9.30638 16.6865Z" fill="#1E1E1E"/>
<path d="M22.6936 4.00001C22.3905 3.99931 22.0903 4.0616 21.8102 4.1833C21.5301 4.30501 21.2756 4.48372 21.0613 4.70919C20.847 4.93466 20.6771 5.20245 20.5614 5.49717C20.4458 5.79189 20.3866 6.10775 20.3872 6.42661V26.4461C20.3904 26.564 20.4363 26.6761 20.5155 26.7595C20.5948 26.8429 20.7014 26.8912 20.8134 26.8945C21.9231 26.8924 22.9868 26.4277 23.7716 25.6021C24.5563 24.7765 24.998 23.6573 25 22.4897V6.42661C25.0006 6.10775 24.9414 5.79189 24.8258 5.49717C24.7101 5.20245 24.5402 4.93466 24.3259 4.70919C24.1116 4.48372 23.8571 4.30501 23.577 4.1833C23.2969 4.0616 22.9967 3.99931 22.6936 4.00001Z" fill="#1E1E1E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,7 @@
<svg width="46" height="46" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="learndash-title">
<title id="learndash-title">LearnDash logo</title>
<path d="M0 9.85714C0 4.41319 4.41301 0 9.85674 0H36.1414C41.5851 0 46 4.41319 46 9.85714V36.1429C46 41.5868 41.5851 46 36.1414 46H9.85674C4.41301 46 0 41.5868 0 36.1429V9.85714Z" fill="#3D7BF5"/>
<path d="M22.6701 16.0986C22.2816 16.0977 21.8967 16.1776 21.5375 16.3337C21.1784 16.4898 20.8521 16.7191 20.5773 17.0083C20.3026 17.2975 20.0848 17.6409 19.9365 18.0189C19.7882 18.397 19.7123 18.8021 19.7131 19.2111V37.2091C19.7172 37.3603 19.7761 37.5041 19.8777 37.6111C19.9793 37.718 20.1159 37.78 20.2595 37.7842V37.7842C21.6823 37.7816 23.0461 37.1855 24.0522 36.1265C25.0582 35.0675 25.6246 33.6321 25.6271 32.1345V19.2111C25.628 18.8021 25.5521 18.397 25.4038 18.0189C25.2555 17.6409 25.0377 17.2975 24.7629 17.0083C24.4882 16.7191 24.1619 16.4898 23.8027 16.3337C23.4436 16.1776 23.0587 16.0977 22.6701 16.0986V16.0986Z" fill="#FEFBF2"/>
<path d="M12.8137 23.9844C12.4251 23.9835 12.0402 24.0668 11.6811 24.2297C11.322 24.3925 10.9956 24.6316 10.7209 24.9333C10.4461 25.235 10.2284 25.5934 10.0801 25.9877C9.93176 26.3821 9.85585 26.8048 9.8567 27.2314V37.1843C9.86074 37.342 9.91961 37.4921 10.0212 37.6037C10.1228 37.7152 10.2595 37.7799 10.4031 37.7843V37.7843C11.8259 37.7815 13.1897 37.1596 14.1957 36.0549C15.2018 34.9501 15.7681 33.4526 15.7707 31.8902V27.2314C15.7715 26.8048 15.6956 26.3821 15.5473 25.9877C15.399 25.5934 15.1812 25.235 14.9065 24.9333C14.6317 24.6316 14.3054 24.3925 13.9463 24.2297C13.5871 24.0668 13.2022 23.9835 12.8137 23.9844V23.9844Z" fill="#FEFBF2"/>
<path d="M32.5271 8.21387C32.1385 8.21298 31.7536 8.29343 31.3945 8.45063C31.0353 8.60782 30.709 8.83866 30.4343 9.12988C30.1595 9.42111 29.9417 9.76698 29.7934 10.1477C29.6451 10.5283 29.5692 10.9363 29.5701 11.3482V37.206C29.5741 37.3582 29.633 37.5031 29.7346 37.6108C29.8362 37.7185 29.9728 37.7809 30.1165 37.7852C31.5393 37.7824 32.9031 37.1822 33.9091 36.1158C34.9152 35.0494 35.4815 33.6038 35.4841 32.0957V11.3482C35.4849 10.9363 35.409 10.5283 35.2607 10.1477C35.1124 9.76698 34.8946 9.42111 34.6199 9.12988C34.3451 8.83866 34.0188 8.60782 33.6597 8.45063C33.3005 8.29343 32.9156 8.21298 32.5271 8.21387V8.21387Z" fill="#FEFBF2"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5698 19.6473C15.7885 19.9643 15.0654 20.1353 14.5396 20.107C13.8586 20.0691 13.5679 19.7687 13.5668 19.6557C13.5663 19.5753 13.8202 19.3234 14.604 19.2664C14.7269 19.2576 14.8566 19.252 14.9902 19.252C15.5257 19.252 16.1217 19.3402 16.5698 19.6473ZM18.0326 26.5538C15.1675 27.3441 12.1399 26.6152 10.3183 24.6953C9.02919 23.3368 8.59699 21.6367 9.13216 20.031C10.2088 16.8004 15.3686 14.8613 18.6662 14.8613C18.7017 14.8613 18.737 14.8614 18.7719 14.862C20.0007 14.8777 20.8516 15.1747 20.9927 15.6375C21.0717 15.8965 20.8848 16.3498 20.4927 16.8499C19.8007 17.7323 18.7492 18.532 17.6797 19.1185C17.0017 18.3746 15.9141 18.0361 14.5231 18.1363C12.9745 18.2487 12.4287 19.0002 12.4343 19.6659C12.4404 20.3919 13.1443 21.1655 14.4778 21.2396C15.2848 21.2837 16.2494 21.0351 17.218 20.6113C17.3601 21.169 17.3161 21.9007 17.0835 22.7731C17.0029 23.0754 17.1827 23.3863 17.4851 23.4669C17.7873 23.5475 18.0981 23.3678 18.1789 23.0651C18.495 21.8797 18.5184 20.8854 18.2502 20.0955C19.5148 19.3859 20.6838 18.4431 21.3847 17.5496C22.0424 16.7109 22.2753 15.9563 22.0772 15.3066C21.9333 14.8349 21.5945 14.4607 21.084 14.1953C21.123 9.9356 21.8781 6.99328 22.5579 6.98926H22.562C22.6619 6.98926 22.797 7.01164 22.9626 7.17616C23.5149 7.72514 24.173 9.52736 24.173 14.9961C24.173 20.392 23.7111 24.9862 18.0326 26.5538ZM12.6219 13.968C12.6212 13.9538 12.623 13.9398 12.6212 13.9255C12.6188 13.9051 12.6163 13.8834 12.6137 13.8624C12.5322 12.6992 12.6718 11.7825 13.0265 11.2909C13.2027 11.0468 13.4237 10.916 13.7222 10.8793C14.1383 10.8292 14.4768 10.9365 14.761 11.2096C15.3116 11.7388 15.633 12.8831 15.6024 14.135C14.6584 14.3678 13.6919 14.6995 12.7737 15.1265C12.7013 14.7201 12.6513 14.333 12.6219 13.968ZM17.5063 11.9931C17.715 11.7659 17.9813 11.6554 18.321 11.6554C18.8609 11.6554 19.0955 11.832 19.2253 11.9649C19.5354 12.2826 19.7026 12.8987 19.7255 13.796C19.4351 13.7558 19.1219 13.7323 18.7855 13.7281C18.7473 13.7276 18.7091 13.7274 18.6705 13.7274C18.1588 13.7274 17.601 13.7718 17.0188 13.8565C16.9937 13.0342 17.1655 12.3642 17.5063 11.9931ZM23.7611 6.37152C23.421 6.03338 23.0059 5.85488 22.5601 5.85488H22.5501C21.0565 5.86379 20.5413 8.09226 20.2834 9.76771C20.2079 10.2594 20.1495 10.7611 20.104 11.2468C20.0818 11.222 20.0599 11.1963 20.0367 11.1725C19.615 10.7405 19.0377 10.5214 18.321 10.5214C17.6649 10.5214 17.0944 10.7648 16.6713 11.2257C16.5506 11.3572 16.4469 11.5094 16.3532 11.6727C16.1663 11.1809 15.905 10.7356 15.5478 10.3921C15.017 9.88206 14.3388 9.66124 13.5849 9.7539C12.9773 9.82856 12.4666 10.1303 12.108 10.6272C12.1066 10.6295 12.1054 10.6319 12.1038 10.6339C11.4826 7.53575 10.3016 3.79478 8.241 4.00878C7.73397 4.0621 7.28587 4.33502 6.94546 4.79764C5.35585 6.95862 6.1017 13.3846 6.79283 16.8407C6.84685 17.1101 7.08341 17.2967 7.34811 17.2967C7.385 17.2967 7.42242 17.293 7.46 17.2855C7.76684 17.2241 7.96598 16.9255 7.90461 16.6185C6.7472 10.8314 7.00543 6.6294 7.85881 5.46954C8.05934 5.19697 8.24065 5.14871 8.35971 5.1363C9.4972 5.01793 10.8985 8.99231 11.49 14.0085C11.5374 14.6297 11.6314 15.2114 11.7222 15.6635C11.7225 15.6656 11.7236 15.6676 11.7239 15.6697C10.0205 16.6515 8.6148 17.9941 8.05567 19.672C7.3836 21.6877 7.90829 23.8031 9.49493 25.4752C11.044 27.1075 13.3294 28 15.7285 28C16.5917 27.9998 17.4699 27.8843 18.3333 27.646C24.7727 25.8686 25.3059 20.6478 25.3059 14.9955C25.3059 10.1595 24.815 7.41931 23.7611 6.37152Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
<rect width="32" height="32" rx="6" fill="#3b82f6"/>
<text x="16" y="22" text-anchor="middle" fill="white" font-size="16" font-family="sans-serif" font-weight="bold">E</text>
</svg>

After

Width:  |  Height:  |  Size: 259 B

View File

@@ -0,0 +1,62 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\LiquidWeb\Harbor\API\Functions\Actions;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Config;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Notice\Notice;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Notice\Notice_Controller;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Traits\With_Debugging;
use Throwable;
/**
* Displays an informational notice on a plugin's legacy license settings page.
*
* Informs users that licensing is now managed centrally through Liquid Web's
* unified system while the legacy page remains available for older licenses.
*
* @since 1.0.0
*/
class Display_Legacy_License_Page_Notice {
use With_Debugging;
/**
* @since 1.0.0
*
* @param string $product_name Optional human-readable product name (e.g. "GiveWP", "Kadence").
* When omitted, a generic message is displayed.
*
* @return void
*/
public function __invoke( string $product_name = '' ): void {
try {
$url = lw_harbor_get_license_page_url();
if ( $product_name !== '' ) {
$message = sprintf(
/* translators: 1: product name (e.g. "GiveWP"), 2: URL to the Liquid Web Software Manager page. */
__(
'%1$s is now part of Liquid Web\'s software offerings. This page is still available for managing legacy licenses from your previous %1$s account. If you purchased a new plan through Liquid Web, your products are managed through the <a href="%2$s">Liquid Web Software Manager</a>.',
'kadence-blocks'
),
esc_html( $product_name ),
esc_url( $url )
);
} else {
$message = sprintf(
/* translators: %s is the URL to the Liquid Web Software Manager page. */
__(
'This plugin is now part of Liquid Web\'s software offerings. This page is still available for managing legacy licenses from your previous account. If you purchased a new plan through Liquid Web, your products are managed through the <a href="%s">Liquid Web Software Manager</a>.',
'kadence-blocks'
),
esc_url( $url )
);
}
$notice = new Notice( Notice::INFO, $message );
Config::get_container()->get( Notice_Controller::class )->render( $notice->to_array() );
} catch ( Throwable $e ) {
$this->debug_log_throwable( $e, 'Error displaying legacy license page notice' );
}
}
}

View File

@@ -0,0 +1,46 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\LiquidWeb\Harbor\API\Functions\Actions;
/**
* Registers a submenu item under a plugin's existing menu that links to the Harbor feature manager.
*
* @since 1.0.0
*/
class Register_Submenu {
/**
* @param string $parent_slug The slug of the parent top-level menu item.
*
* @return void
*/
public function __invoke( string $parent_slug ): void {
if (
! did_action( 'lw_harbor/loaded' )
/** This filter is documented in src/Harbor/Admin/Feature_Manager_Page.php */
|| apply_filters( 'lw-harbor/hide_menu_item', false )
) {
return;
}
add_action(
'admin_menu',
static function () use ( $parent_slug ): void {
$page_url = lw_harbor_get_license_page_url();
if ( $page_url === '' ) {
return;
}
add_submenu_page(
$parent_slug,
__( 'Liquid Web Software Manager', 'kadence-blocks' ),
__( 'Licensing', 'kadence-blocks' ),
'manage_options',
$page_url
);
},
PHP_INT_MAX
);
}
}

View File

@@ -0,0 +1,159 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\LiquidWeb\Harbor\API\Functions;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Admin\Feature_Manager_Page;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\API\Functions\Actions\Display_Legacy_License_Page_Notice;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\API\Functions\Actions\Register_Submenu;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Config;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Features\Manager;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Licensing\Repositories\License_Repository;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Site\Data;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Traits\With_Debugging;
use Throwable;
/**
* Registers this Harbor instance's callbacks into the global function registry.
*
* Each vendor-prefixed Harbor instance calls register() during init, storing
* version-keyed closures via _lw_harbor_global_function_registry(). The closures are defined
* here (inside the namespaced file) so Strauss-prefixed class references
* resolve correctly for this specific instance.
*
* @since 1.0.0
*/
class Global_Function_Registry {
use With_Debugging;
/**
* Registers this instance's callbacks into the global function registry.
*
* @since 1.0.0
*
* @param string $version The version of this Harbor instance.
*
* @return void
*/
public static function register( string $version ): void {
\_lw_harbor_global_function_registry(
'lw_harbor_has_unified_license_key',
$version,
static function (): bool {
try {
return Config::get_container()->get( License_Repository::class )->key_exists();
} catch ( Throwable $e ) {
self::debug_log_throwable( $e, 'Error checking unified license key existence' );
return false;
}
}
);
\_lw_harbor_global_function_registry(
'lw_harbor_get_unified_license_key',
$version,
static function (): ?string {
try {
return Config::get_container()->get( License_Repository::class )->get_key();
} catch ( Throwable $e ) {
self::debug_log_throwable( $e, 'Error getting unified license key' );
return null;
}
}
);
\_lw_harbor_global_function_registry(
'lw_harbor_is_product_license_active',
$version,
static function ( string $product ): bool {
try {
return Config::get_container()->get( License_Repository::class )->is_product_valid( $product );
} catch ( Throwable $e ) {
self::debug_log_throwable( $e, 'Error checking product license' );
return false;
}
}
);
\_lw_harbor_global_function_registry(
'lw_harbor_is_feature_enabled',
$version,
static function ( string $slug ) {
try {
$result = Config::get_container()->get( Manager::class )->is_enabled( $slug );
if ( is_wp_error( $result ) ) {
self::debug_log_wp_error( $result, 'Error checking feature enabled state' );
return false;
}
return $result;
} catch ( Throwable $e ) {
self::debug_log_throwable( $e, 'Error checking feature enabled state' );
return false;
}
}
);
\_lw_harbor_global_function_registry(
'lw_harbor_is_feature_available',
$version,
static function ( string $slug ) {
try {
$result = Config::get_container()->get( Manager::class )->is_available( $slug );
if ( is_wp_error( $result ) ) {
self::debug_log_wp_error( $result, 'Error checking feature availability' );
return false;
}
return $result;
} catch ( Throwable $e ) {
self::debug_log_throwable( $e, 'Error checking feature availability' );
return false;
}
}
);
\_lw_harbor_global_function_registry(
'lw_harbor_get_license_page_url',
$version,
static function (): string {
return admin_url( 'options-general.php?page=' . Feature_Manager_Page::PAGE_SLUG );
}
);
\_lw_harbor_global_function_registry(
'lw_harbor_display_legacy_license_page_notice',
$version,
new Display_Legacy_License_Page_Notice()
);
\_lw_harbor_global_function_registry(
'lw_harbor_register_submenu',
$version,
new Register_Submenu()
);
\_lw_harbor_global_function_registry(
'lw_harbor_get_licensed_domain',
$version,
static function (): string {
try {
return Config::get_container()->get( Data::class )->get_domain();
} catch ( Throwable $e ) {
self::debug_log_throwable( $e, 'Error getting site domain' );
return '';
}
}
);
}
}

View File

@@ -0,0 +1,22 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\LiquidWeb\Harbor\API\Functions;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Contracts\Abstract_Provider;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Harbor;
/**
* Registers global (non-namespaced) Harbor helper functions.
*
* @since 1.0.0
*/
final class Provider extends Abstract_Provider {
/**
* @inheritDoc
*/
public function register(): void {
require_once dirname( __DIR__, 2 ) . '/global-functions.php';
Global_Function_Registry::register( Harbor::VERSION );
}
}

View File

@@ -0,0 +1,173 @@
# Global Function Registry
## The Problem
StellarWP plugins (Give, The Events Calendar, Kadence, LearnDash, etc.) each ship their own vendor-prefixed copy of Harbor via Strauss. On a single WordPress site you may have:
```
GiveWP\Vendor\LiquidWeb\Harbor\... (version 3.0.0)
TEC\Vendor\LiquidWeb\Harbor\... (version 3.0.1)
Kadence\Vendor\LiquidWeb\Harbor\... (version 3.0.3)
```
Each copy is a completely separate PHP class tree. If a plugin calls its own namespaced `License_Manager` to check licensing, it runs that copy's logic — even if a newer copy with a bug fix is already loaded. Worse, the namespaced classes are not directly usable across copies.
Plugin consumers also need a simple, stable public API to answer questions like "does this site have an active license for Give?" without coupling to any particular Harbor copy or requiring knowledge of the internal class structure.
## The Solution
**Global (non-namespaced) PHP functions** are not Strauss-prefixed and are truly shared across all copies. The first copy to `require_once` the file defines the functions; subsequent copies skip the definition via `function_exists` guards.
**Version-keyed closures** allow each copy to register its own implementation under its version number. When a function is called, the registry resolves to the closure registered by the **highest-version** copy — so the most up-to-date logic always runs regardless of which copy's file defined the global function shell.
## How It Works
### 1. The Instance Registry (`_lw_harbor_instance_registry`)
`src/Harbor/global-functions.php` defines a function with a `static $versions` variable that tracks every active Harbor copy:
```php
function _lw_harbor_instance_registry( string $version = '' ): array {
static $versions = []; // one instance, shared across all callers
// Only accept registrations before wp_loaded (the bootstrap window).
if ( $version !== '' && ! did_action( 'wp_loaded' ) ) {
$versions[ $version ] = true;
}
return $versions;
}
```
Each Harbor instance calls `_lw_harbor_instance_registry( self::VERSION )` during `register_cross_instance_hooks()` (in `Harbor::init()`). Because the static variable lives inside the function, and the function is only defined once (via `function_exists` guard), all vendor-prefixed copies share the same registry. Registrations are only accepted before `wp_loaded`, so all real instances (which initialize on `plugins_loaded`) can register, but nothing can inject fake versions after the bootstrap window closes.
### 2. The Function Registry (`_lw_harbor_global_function_registry`)
A second global function stores version-keyed closures and resolves to the highest version's callable on read:
```php
function _lw_harbor_global_function_registry( string $key, string $version = '', ?callable $callback = null ): ?callable {
static $registry = []; // one instance, shared across all callers
if ( $callback !== null ) {
$registry[ $key ][ $version ] = $callback; // write mode
return null;
}
// Read mode: resolve to the highest registered version's callable.
$versions = array_keys( _lw_harbor_instance_registry() );
$highest = array_reduce( $versions, fn( $carry, $v ) => version_compare( $v, $carry, '>' ) ? $v : $carry, '0.0.0' );
return $registry[ $key ][ $highest ] ?? null;
}
```
The `static` variable lives inside the function — there is only ever one `$registry` array in the process, shared by every caller.
```
_lw_harbor_global_function_registry()
└── static $registry = [
'lw_harbor_has_unified_license_key' => [
'3.0.0' => Closure(GiveWP instance), ← registered by Give's copy
'3.0.1' => Closure(TEC instance), ← registered by TEC's copy
'3.0.3' => Closure(Kadence instance), ← registered by Kadence's copy
],
'lw_harbor_is_product_license_active' => [
'3.0.0' => Closure(GiveWP instance),
'3.0.1' => Closure(TEC instance),
'3.0.3' => Closure(Kadence instance),
],
...
]
Read path: _lw_harbor_instance_registry() → ['3.0.0', '3.0.1', '3.0.3']
array_reduce → '3.0.3'
$registry['lw_harbor_has_unified_license_key']['3.0.3'] → Kadence's Closure
```
**Write mode** — called with a version and callback:
```php
_lw_harbor_global_function_registry( 'lw_harbor_has_unified_license_key', '3.0.3', fn() => ... );
```
**Read mode** — called with a key only, resolves the leader's callable:
```php
$callback = _lw_harbor_global_function_registry( 'lw_harbor_has_unified_license_key' );
```
### 3. Registering Closures (`Global_Function_Registry`)
`Global_Function_Registry::register()` is called by `Provider::register()` during each Harbor instance's `init()`. It registers version-keyed closures for every public function key:
```php
\_lw_harbor_global_function_registry(
'lw_harbor_has_unified_license_key',
$version,
static function (): bool {
return Config::get_container()->get( License_Repository::class )->key_exists();
}
);
```
**Why closures are defined here (in a namespaced file) and not in `global-functions.php`:**
Strauss rewrites class references at parse time. `License_Repository::class` inside this file resolves to the correct Strauss-prefixed name for _this specific Harbor copy_ — e.g. `GiveWP\Vendor\LiquidWeb\Harbor\Licensing\Repositories\License_Repository`. Defining the closures in the global-namespace file would break this resolution.
### 4. The Public Functions
`src/Harbor/global-functions.php` exposes the public functions. Plugin consumers call these:
| Function | What it does |
| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `lw_harbor_has_unified_license_key()` | Whether any unified license key is stored locally (no API call) |
| `lw_harbor_get_unified_license_key()` | Returns the unified license key string, or null if not found |
| `lw_harbor_is_product_license_active( $product )` | Whether a product slug has `validation_status: valid` in the cached catalog |
| `lw_harbor_is_feature_enabled( $slug )` | Whether a feature is in the catalog AND currently enabled/active |
| `lw_harbor_is_feature_available( $slug )` | Whether a feature exists in the catalog, regardless of enabled state |
| `lw_harbor_get_license_page_url()` | Returns the admin URL for the Harbor Feature Manager page |
| `lw_harbor_get_licensed_domain()` | Returns the domain Harbor uses for licensing on the current site |
| `lw_harbor_register_submenu( $parent_slug )` | Appends a Licensing submenu item under a plugin's top-level admin menu |
| `lw_harbor_display_legacy_license_page_notice( $product_name = '' )` | Renders an info notice on a plugin's legacy license page pointing users to the unified system |
| `lw_harbor_has_consent()` | Whether the site owner has opted in to external Liquid Web API communications |
Each function looks up the registered callback and delegates, returning `false` if no callback is registered yet:
```php
function lw_harbor_has_unified_license_key(): bool {
$callback = _lw_harbor_global_function_registry( 'lw_harbor_has_unified_license_key' );
return $callback ? (bool) $callback() : false;
}
```
## Registration Flow
```
Harbor::init()
└─ API\Functions\Provider::register() ← runs unconditionally (outside is_enabled())
├─ require_once global-functions.php ← defines _lw_harbor_instance_registry(),
│ _lw_harbor_global_function_registry(),
│ and public wrapper functions
└─ Global_Function_Registry::register() ← stores version-keyed closures in the registry
└─ register_cross_instance_hooks() ← calls _lw_harbor_instance_registry( VERSION )
to register this instance in the version registry
└─ is_enabled() block
├─ Licensing\Provider::register() ← binds License_Repository
└─ Features\Provider::register() ← binds Features\Manager
```
`API\Functions\Provider` runs unconditionally so the global functions and version-keyed closures are always registered, even when the legacy PUE/update machinery is disabled via `TRIBE_DISABLE_PUE` or `STELLARWP_LICENSING_DISABLED`. The closures themselves resolve services lazily from the container and are wrapped in `try/catch (Throwable)`, so if the container cannot resolve a dependency (because `is_enabled()` was false) the functions fall back gracefully to `false`.
## Security
The callbacks are stored in a PHP `static` variable inside `_lw_harbor_global_function_registry()`. The version registry is locked after `wp_loaded`, so no code running after the bootstrap window can inject a fake high version to hijack the leader callable. An attacker cannot simply hook a filter to force `lw_harbor_is_product_license_active()` to return `true` — they would need to both register a fake version before `wp_loaded` **and** know the internal registry key, making trivial overrides impractical.
`_lw_harbor_instance_registry` and `_lw_harbor_global_function_registry` are marked `@internal` to signal that plugin consumers should not call them directly and should only use the public wrapper functions.
## Adding a New Global Function
1. If the implementation is non-trivial, create an invokable class in `API/Functions/Actions/` and implement the logic in `__invoke()`. For simple one-liners a closure directly in `Global_Function_Registry::register()` is fine.
2. Register the callable in `Global_Function_Registry::register()` under a new key.
3. Add a public wrapper function in `global-functions.php` inside a `function_exists` guard that delegates to `_lw_harbor_global_function_registry`.
4. Add tests in `tests/wpunit/API/Functions/GlobalFunctionsTest.php`. If you added an Actions class, add a dedicated test in `tests/wpunit/API/Functions/Actions/`.

View File

@@ -0,0 +1,284 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\LiquidWeb\Harbor\API\REST\V1;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Portal\Catalog_Repository;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
/**
* WP REST API controller for reading the product catalog.
*
* @since 1.0.0
*/
final class Catalog_Controller extends WP_REST_Controller {
/**
* The REST API namespace.
*
* @since 1.0.0
*
* @var string
*/
protected $namespace = 'liquidweb/harbor/v1';
/**
* The REST API route base.
*
* @since 1.0.0
*
* @var string
*/
protected $rest_base = 'catalog';
/**
* The catalog repository.
*
* @since 1.0.0
*
* @var Catalog_Repository
*/
private Catalog_Repository $repository;
/**
* Constructor.
*
* @since 1.0.0
*
* @param Catalog_Repository $repository The catalog repository.
*
* @return void
*/
public function __construct( Catalog_Repository $repository ) {
$this->repository = $repository;
}
/**
* Registers the routes.
*
* @since 1.0.0
*
* @return void
*/
public function register_routes(): void {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_items' ],
'permission_callback' => [ $this, 'check_permissions' ],
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/refresh',
[
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'refresh_items' ],
'permission_callback' => [ $this, 'check_permissions' ],
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
}
/**
* Permission callback: require manage_options capability.
*
* @since 1.0.0
*
* @return bool
*/
public function check_permissions(): bool {
return current_user_can( 'manage_options' );
}
/**
* Returns all product catalogs.
*
* @since 1.0.0
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response|\WP_Error
*/
public function get_items( $request ) {
$catalog = $this->repository->get();
if ( is_wp_error( $catalog ) ) {
return $catalog;
}
return new WP_REST_Response( $catalog->to_array() );
}
/**
* Force refreshes the catalog from the upstream API and returns the result.
*
* @since 1.0.0
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response|\WP_Error
*/
public function refresh_items( $request ) {
$catalog = $this->repository->refresh();
if ( is_wp_error( $catalog ) ) {
return $catalog;
}
return new WP_REST_Response( $catalog->to_array() );
}
/**
* Gets the schema for a single catalog item.
*
* @since 1.0.0
*
* @return array<string, mixed>
*/
public function get_item_schema(): array {
if ( $this->schema ) {
/** @var array<string, mixed> */
return $this->add_additional_fields_schema( $this->schema );
}
$this->schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'catalog',
'type' => 'object',
'properties' => [
'product_id' => [
'description' => __( 'The product ID from the Commerce Portal.', 'kadence-blocks' ),
'type' => 'string',
'readonly' => true,
'context' => [ 'view' ],
],
'product_slug' => [
'description' => __( 'The product slug.', 'kadence-blocks' ),
'type' => 'string',
'readonly' => true,
'context' => [ 'view' ],
],
'product_name' => [
'description' => __( 'The product display name.', 'kadence-blocks' ),
'type' => 'string',
'readonly' => true,
'context' => [ 'view' ],
],
'tiers' => [
'description' => __( 'The product tiers ordered by rank.', 'kadence-blocks' ),
'type' => 'array',
'readonly' => true,
'context' => [ 'view' ],
'items' => [
'type' => 'object',
'properties' => [
'slug' => [
'type' => 'string',
],
'name' => [
'type' => 'string',
],
'rank' => [
'type' => 'integer',
],
'price' => [
'type' => 'integer',
],
'currency' => [
'type' => 'string',
],
'herald_slugs' => [
'type' => 'array',
'items' => [
'type' => 'string',
],
],
'purchase_url' => [
'type' => 'string',
],
'upgrade_url' => [
'type' => 'string',
],
],
],
],
'features' => [
'description' => __( 'The product features.', 'kadence-blocks' ),
'type' => 'array',
'readonly' => true,
'context' => [ 'view' ],
'items' => [
'type' => 'object',
'properties' => [
'slug' => [
'type' => 'string',
],
'kind' => [
'type' => 'string',
],
'minimum_tier' => [
'type' => 'string',
],
'top_dir' => [
'type' => [ 'string', 'null' ],
],
'main_file' => [
'type' => [ 'string', 'null' ],
],
'plugin_file' => [
'type' => [ 'string', 'null' ],
],
'wporg_slug' => [
'type' => [ 'string', 'null' ],
],
'version' => [
'type' => [ 'string', 'null' ],
],
'release_date' => [
'type' => [ 'string', 'null' ],
],
'changelog' => [
'type' => [ 'string', 'null' ],
],
'name' => [
'type' => 'string',
],
'description' => [
'type' => 'string',
],
'category' => [
'type' => 'string',
],
'authors' => [
'type' => [ 'array', 'null' ],
'items' => [
'type' => 'string',
],
],
'documentation_url' => [
'type' => 'string',
],
'homepage' => [
'type' => [ 'string', 'null' ],
],
],
],
],
],
];
/** @var array<string, mixed> */
return $this->add_additional_fields_schema( $this->schema );
}
}

View File

@@ -0,0 +1,550 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\LiquidWeb\Harbor\API\REST\V1;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Features\Error_Code;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Features\Feature_Resource;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Features\Manager;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Features\Types\Feature;
use KadenceWP\KadenceBlocks\LiquidWeb\Harbor\Utils\Cast;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
/**
* WP REST API controller for managing features.
*
* Supports listing, retrieving, enabling, disabling, and updating features.
* Restricted to logged-in Administrators (manage_options capability).
*
* @since 1.0.0
*/
class Feature_Controller extends WP_REST_Controller {
/**
* The REST API namespace.
*
* @since 1.0.0
*
* @var string
*/
protected $namespace = 'liquidweb/harbor/v1';
/**
* The REST API route base.
*
* @since 1.0.0
*
* @var string
*/
protected $rest_base = 'features';
/**
* The feature manager instance.
*
* @since 1.0.0
*
* @var Manager
*/
private Manager $manager;
/**
* Constructor for the feature REST API controller.
*
* @since 1.0.0
*
* @param Manager $manager The feature manager.
*
* @return void
*/
public function __construct( Manager $manager ) {
$this->manager = $manager;
}
/**
* Registers the routes.
*
* @since 1.0.0
*
* @return void
*/
public function register_routes(): void {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_items' ],
'permission_callback' => [ $this, 'check_permissions' ],
'args' => $this->get_collection_params(),
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<slug>[a-zA-Z0-9_-]+)',
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_item' ],
'permission_callback' => [ $this, 'check_permissions' ],
'args' => [
'slug' => [
'required' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
],
],
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<slug>[a-zA-Z0-9_-]+)/enable',
[
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'enable' ],
'permission_callback' => [ $this, 'check_permissions' ],
'args' => $this->get_slug_args(),
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<slug>[a-zA-Z0-9_-]+)/disable',
[
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'disable' ],
'permission_callback' => [ $this, 'check_permissions' ],
'args' => $this->get_slug_args(),
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<slug>[a-zA-Z0-9_-]+)/update',
[
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'update_item' ],
'permission_callback' => [ $this, 'check_permissions' ],
'args' => $this->get_slug_args(),
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
}
/**
* Permission callback: require manage_options capability.
*
* @since 1.0.0
*
* @return bool
*/
public function check_permissions(): bool {
return current_user_can( 'manage_options' );
}
/**
* Lists features with optional filters.
*
* @since 1.0.0
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response|WP_Error
*/
public function get_items( $request ) {
$features = $this->manager->get_all();
if ( is_wp_error( $features ) ) {
return $this->ensure_error_status( $features );
}
$product = $request->get_param( 'product' );
$tier = $request->get_param( 'tier' );
$available = $request->get_param( 'available' );
$type = $request->get_param( 'type' );
if ( $product !== null || $tier !== null || $available !== null || $type !== null ) {
$features = $features->filter(
$product !== null ? Cast::to_string( $product ) : null,
$tier !== null ? Cast::to_string( $tier ) : null,
$available !== null ? Cast::to_bool( $available ) : null,
$type !== null ? Cast::to_string( $type ) : null
);
}
$data = [];
foreach ( $features as $feature ) {
$data[] = Feature_Resource::from_feature( $feature )->to_array();
}
return new WP_REST_Response( $data );
}
/**
* Retrieves a single feature by slug.
*
* @since 1.0.0
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response|WP_Error
*/
public function get_item( $request ) {
$slug = Cast::to_string( $request->get_param( 'slug' ) );
$features = $this->manager->get_all();
if ( is_wp_error( $features ) ) {
return $this->ensure_error_status( $features );
}
$feature = $features->get( $slug );
if ( ! $feature ) {
return new WP_Error(
Error_Code::FEATURE_NOT_FOUND,
sprintf( 'Feature "%s" not found.', $slug ),
[ 'status' => 404 ]
);
}
return new WP_REST_Response( Feature_Resource::from_feature( $feature )->to_array() );
}
/**
* Enables a feature.
*
* @since 1.0.0
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response|WP_Error
*/
public function enable( WP_REST_Request $request ) {
$slug = Cast::to_string( $request->get_param( 'slug' ) );
$feature = $this->manager->enable( $slug );
if ( is_wp_error( $feature ) ) {
return $this->ensure_error_status( $feature );
}
return new WP_REST_Response(
Feature_Resource::from_feature( $feature )->to_array()
);
}
/**
* Disables a feature.
*
* @since 1.0.0
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response|WP_Error
*/
public function disable( WP_REST_Request $request ) {
$slug = Cast::to_string( $request->get_param( 'slug' ) );
$feature = $this->manager->disable( $slug );
if ( is_wp_error( $feature ) ) {
return $this->ensure_error_status( $feature );
}
return new WP_REST_Response(
Feature_Resource::from_feature( $feature )->to_array()
);
}
/**
* Triggers an update for a feature.
*
* @since 1.0.0
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response|WP_Error
*/
public function update_item( $request ) {
$slug = Cast::to_string( $request->get_param( 'slug' ) );
$feature = $this->manager->update( $slug );
if ( is_wp_error( $feature ) ) {
return $this->ensure_error_status( $feature );
}
return new WP_REST_Response(
Feature_Resource::from_feature( $feature )->to_array()
);
}
/**
* Gets the schema for a single feature response.
*
* @since 1.0.0
*
* @return array<string, mixed>
*/
public function get_item_schema(): array {
if ( $this->schema ) {
/** @var array<string, mixed> */
return $this->add_additional_fields_schema( $this->schema );
}
$base_properties = [
'slug' => [
'description' => __( 'The feature slug.', 'kadence-blocks' ),
'type' => 'string',
'readonly' => true,
'context' => [ 'view' ],
],
'name' => [
'description' => __( 'The feature display name.', 'kadence-blocks' ),
'type' => 'string',
'readonly' => true,
'context' => [ 'view' ],
],
'description' => [
'description' => __( 'The feature description.', 'kadence-blocks' ),
'type' => 'string',
'readonly' => true,
'context' => [ 'view' ],
],
'product' => [
'description' => __( 'The product the feature belongs to.', 'kadence-blocks' ),
'type' => 'string',
'readonly' => true,
'context' => [ 'view' ],
],
'tier' => [
'description' => __( 'The feature tier.', 'kadence-blocks' ),
'type' => 'string',
'readonly' => true,
'context' => [ 'view' ],
],
'type' => [
'description' => __( 'The feature type identifier.', 'kadence-blocks' ),
'type' => 'string',
'readonly' => true,
'context' => [ 'view' ],
],
'is_available' => [
'description' => __( 'Whether the feature is available for the current site.', 'kadence-blocks' ),
'type' => 'boolean',
'readonly' => true,
'context' => [ 'view' ],
],
'in_catalog_tier' => [
'description' => __( "True when the user's licensed tier covers this feature's minimum tier.", 'kadence-blocks' ),
'type' => 'boolean',
'readonly' => true,
'context' => [ 'view' ],
],
'documentation_url' => [
'description' => __( 'The URL to the feature documentation.', 'kadence-blocks' ),
'type' => 'string',
'format' => 'uri',
'readonly' => true,
'context' => [ 'view' ],
],
'is_enabled' => [
'description' => __( 'Whether the feature is currently enabled.', 'kadence-blocks' ),
'type' => 'boolean',
'readonly' => true,
'context' => [ 'view' ],
],
];
$installable_properties = [
'release_date' => [
'description' => __( 'Release date of the latest version, or null if not applicable.', 'kadence-blocks' ),
'type' => [ 'string', 'null' ],
'readonly' => true,
'context' => [ 'view' ],
],
'version' => [
'description' => __( 'Latest available version from the catalog, or null. Only present for installable features.', 'kadence-blocks' ),
'type' => [ 'string', 'null' ],
'readonly' => true,
'context' => [ 'view' ],
],
'changelog' => [
'description' => __( 'Changelog HTML for the latest version, or null. Only present for installable features.', 'kadence-blocks' ),
'type' => [ 'string', 'null' ],
'readonly' => true,
'context' => [ 'view' ],
],
'wporg_slug' => [
'description' => __( 'The WordPress.org slug for plugins_api() lookups, or null if not on WordPress.org.', 'kadence-blocks' ),
'type' => [ 'string', 'null' ],
'readonly' => true,
'context' => [ 'view' ],
],
'installed_version' => [
'description' => __( 'The currently installed version, or null if not installed.', 'kadence-blocks' ),
'type' => [ 'string', 'null' ],
'readonly' => true,
'context' => [ 'view' ],
],
'update_version' => [
'description' => __( 'Version available in the WordPress update transient, or null when no update is pending.', 'kadence-blocks' ),
'type' => [ 'string', 'null' ],
'readonly' => true,
'context' => [ 'view' ],
],
];
$plugin_properties = [
'plugin_file' => [
'description' => __( 'The plugin file path relative to the plugins directory.', 'kadence-blocks' ),
'type' => 'string',
'readonly' => true,
'context' => [ 'view' ],
],
];
$this->schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'feature',
'oneOf' => [
[
'title' => 'plugin',
'type' => 'object',
'additionalProperties' => true,
'properties' => array_merge(
$base_properties,
[ 'type' => array_merge( $base_properties['type'], [ 'enum' => [ Feature::TYPE_PLUGIN ] ] ) ],
$plugin_properties,
$installable_properties
),
],
[
'title' => 'theme',
'type' => 'object',
'additionalProperties' => true,
'properties' => array_merge(
$base_properties,
[ 'type' => array_merge( $base_properties['type'], [ 'enum' => [ Feature::TYPE_THEME ] ] ) ],
$installable_properties
),
],
[
'title' => 'service',
'type' => 'object',
'additionalProperties' => true,
'properties' => array_merge(
$base_properties,
[ 'type' => array_merge( $base_properties['type'], [ 'enum' => [ Feature::TYPE_SERVICE ] ] ) ]
),
],
],
];
/** @var array<string, mixed> */
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Gets the query parameters for the features collection.
*
* @since 1.0.0
*
* @return array<string, array<string, mixed>>
*/
public function get_collection_params(): array {
return [
'product' => [
'description' => __( 'Filter by product.', 'kadence-blocks' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
],
'tier' => [
'description' => __( 'Filter by feature tier.', 'kadence-blocks' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
],
'available' => [
'description' => __( 'Filter by availability.', 'kadence-blocks' ),
'type' => 'boolean',
],
'type' => [
'description' => __( 'Filter by feature type.', 'kadence-blocks' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
],
];
}
/**
* Ensures a WP_Error has an HTTP status code in its data.
*
* Errors from the Manager and its strategies do not carry HTTP
* status codes. This method maps known error codes to the most
* appropriate HTTP status before the error reaches the REST
* infrastructure (which defaults to 500 when no status is set).
*
* @since 1.0.0
*
* @param WP_Error $error The error to enrich.
*
* @return WP_Error The same instance, with a status code guaranteed.
*/
private function ensure_error_status( WP_Error $error ): WP_Error {
$data = $error->get_error_data();
if ( is_array( $data ) && isset( $data['status'] ) ) {
return $error;
}
$status = Error_Code::http_status( (string) $error->get_error_code() );
$error->add_data(
is_array( $data )
? array_merge( $data, [ 'status' => $status ] )
: [ 'status' => $status ]
);
return $error;
}
/**
* Gets the slug argument definition for toggle endpoints.
*
* @since 1.0.0
*
* @return array<string, array<string, mixed>>
*/
private function get_slug_args(): array {
return [
'slug' => [
'required' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => function ( $slug ): bool {
return Cast::to_bool(
$this->manager->exists(
Cast::to_string( $slug )
)
);
},
],
];
}
}

View File

@@ -0,0 +1,103 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\LiquidWeb\Harbor\API\REST\V1;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
/**
* WP REST API controller for reading the active Harbor host plugins.
*
* Returns the flat list of plugin basenames that registered themselves in the
* Harbor instance registry during this request's bootstrap. Because this is a
* fresh HTTP request, all currently active Harbor-bundled plugins are present.
*
* @since 1.0.0
*/
final class Harbor_Hosts_Controller extends WP_REST_Controller {
/**
* @since 1.0.0
* @var string
*/
protected $namespace = 'liquidweb/harbor/v1';
/**
* @since 1.0.0
* @var string
*/
protected $rest_base = 'hosts';
/**
* @inheritDoc
*/
public function register_routes(): void {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_items' ],
'permission_callback' => [ $this, 'check_permissions' ],
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
}
/**
* Returns the list of plugin basenames registered in the Harbor instance registry.
*
* @since 1.0.0
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response
*/
public function get_items( $request ): WP_REST_Response {
$registry = _lw_harbor_instance_registry();
$basenames = array_reduce(
$registry,
static function ( array $carry, array $files ): array {
return array_merge( $carry, $files );
},
[]
);
return new WP_REST_Response( $basenames, 200 );
}
/**
* Checks whether the current user has permission to read the hosts list.
*
* @since 1.0.0
*
* @return bool
*/
public function check_permissions(): bool {
return current_user_can( 'manage_options' );
}
/**
* Returns the JSON schema for the hosts list response.
*
* @since 1.0.0
*
* @return array<string, mixed>
*/
public function get_public_item_schema(): array {
return [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'harbor-hosts',
'type' => 'array',
'description' => __( 'Plugin basenames of all active Harbor-bundled plugins registered during this request.', 'kadence-blocks' ),
'items' => [
'type' => 'string',
'description' => __( 'Plugin basename (e.g. give/give.php).', 'kadence-blocks' ),
],
];
}
}

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